diff --git a/.clang-format b/.clang-format index adcb8acec..cba03056f 100644 --- a/.clang-format +++ b/.clang-format @@ -1,230 +1,72 @@ +# https://clang-format-configurator.site +# Copyright 2020-2025 Alfredo A. Correa +# this format specification requires clang-format version 15, it is optimized for clang-format version 19 --- -Language: Cpp -# BasedOnStyle: Google -#AccessModifierOffset: -1 -AlignAfterOpenBracket: BlockIndent # Align +Language: Cpp +AccessModifierOffset: -3 +AlignAfterOpenBracket: BlockIndent AlignArrayOfStructures: Right -#AlignConsecutiveMacros: None -AlignConsecutiveAssignments: Consecutive # None -#AlignConsecutiveBitFields: None -AlignConsecutiveDeclarations: Consecutive -#AlignEscapedNewlines: Left -AlignOperands: AlignAfterOperator -AlignTrailingComments: false -#AllowAllArgumentsOnNextLine: true -#AllowAllParametersOfDeclarationOnNextLine: true -#AllowShortEnumsOnASingleLine: true -#AllowShortBlocksOnASingleLine: Never -AllowShortCaseLabelsOnASingleLine: true # false -#AllowShortFunctionsOnASingleLine: All -#AllowShortLambdasOnASingleLine: All -#AllowShortIfStatementsOnASingleLine: WithoutElse -#AllowShortLoopsOnASingleLine: true -#AlwaysBreakAfterDefinitionReturnType: None -#AlwaysBreakAfterReturnType: None -#AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: No # Yes -#AttributeMacros: -# - __capability -#BinPackArguments: true -#BinPackParameters: true -#BraceWrapping: -# AfterCaseLabel: false -# AfterClass: false -# AfterControlStatement: Never -# AfterEnum: false -# AfterFunction: false -# AfterNamespace: false -# AfterObjCDeclaration: false -# AfterStruct: false -# AfterUnion: false -# AfterExternBlock: false -# BeforeCatch: false -# BeforeElse: false -# BeforeLambdaBody: false -# BeforeWhile: false -# IndentBraces: false -# SplitEmptyFunction: true -# SplitEmptyRecord: true -# SplitEmptyNamespace: true -#BreakBeforeBinaryOperators: None -#BreakBeforeConceptDeclarations: true -#BreakBeforeBraces: Attach -#BreakBeforeInheritanceComma: false +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false +AlignConsecutiveMacros: None +AlignOperands: AlignAfterOperator +AllowAllArgumentsOnNextLine: false +AllowShortLambdasOnASingleLine: All +AllowShortCaseLabelsOnASingleLine: true +AlwaysBreakTemplateDeclarations: No +# BreakConstructorInitializers: BeforeComma BreakInheritanceList: BeforeComma -#BreakBeforeTernaryOperators: true -#BreakConstructorInitializersBeforeComma: false -#BreakConstructorInitializers: BeforeColon -#BreakAfterJavaFieldAnnotations: false -#BreakStringLiterals: true -ColumnLimit: 0 -#CommentPragmas: '^ IWYU pragma:' -QualifierAlignment: Right # Leave -#CompactNamespaces: false +BraceWrapping: + BeforeLambdaBody: false +ColumnLimit: 0 +QualifierAlignment: Right ConstructorInitializerIndentWidth: 0 -ContinuationIndentWidth: 99 -#Cpp11BracedListStyle: true -#DeriveLineEnding: true -#DerivePointerAlignment: true -#DisableFormat: false -#EmptyLineAfterAccessModifier: Never -#EmptyLineBeforeAccessModifier: LogicalBlock -#ExperimentalAutoDetectBinPacking: false -#PackConstructorInitializers: NextLine -#BasedOnStyle: '' -#ConstructorInitializerAllOnOneLineOrOnePerLine: false -#AllowAllConstructorInitializersOnNextLine: true +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true FixNamespaceComments: true -#ForEachMacros: -# - foreach -# - Q_FOREACH -# - BOOST_FOREACH -#IfMacros: -# - KJ_IF_MAYBE -#IncludeBlocks: Regroup -#IncludeCategories: -# - Regex: '^' -# Priority: 2 -# SortPriority: 0 -# CaseSensitive: false -# - Regex: '^<.*\.h>' -# Priority: 1 -# SortPriority: 0 -# CaseSensitive: false -# - Regex: '^<.*' -# Priority: 2 -# SortPriority: 0 -# CaseSensitive: false -# - Regex: '.*' -# Priority: 3 -# SortPriority: 0 -# CaseSensitive: false -#IncludeIsMainRegex: '([-_](test|unittest))?$' -#IncludeIsMainSourceRegex: '' -#IndentAccessModifiers: true # false -AccessModifierOffset: -98 # 2 -#IndentCaseLabels: true -#IndentCaseBlocks: false -#IndentGotoLabels: true -#IndentPPDirectives: None -#IndentExternBlock: AfterExternBlock -#IndentRequires: false -IndentWidth: 99 -#IndentWrappedFunctionNames: false -#InsertTrailingCommas: None -#JavaScriptQuotes: Leave -#JavaScriptWrapImports: true -#KeepEmptyLinesAtTheStartOfBlocks: false -#LambdaBodyIndentation: Signature -#MacroBlockBegin: '' -#MacroBlockEnd: '' -#MaxEmptyLinesToKeep: 1 -#NamespaceIndentation: None -#ObjCBinPackProtocolList: Never -#ObjCBlockIndentWidth: 2 -#ObjCBreakBeforeNestedBlockParam: true -#ObjCSpaceAfterProperty: false -#ObjCSpaceBeforeProtocolList: true -#PenaltyBreakAssignment: 2 -#PenaltyBreakBeforeFirstCallParameter: 1 -#PenaltyBreakComment: 300 -#PenaltyBreakFirstLessLess: 120 -#PenaltyBreakOpenParenthesis: 0 -#PenaltyBreakString: 1000 +IncludeBlocks: Regroup +IncludeCategories: + - Regex: <(boost\/multi)\/ + Priority: 1 + - Regex: <([A-Za-z0-9\Q/-_\E])+> + Priority: 5 + - Regex: <(boost)\/ + Priority: 4 + - Regex: <([A-Za-z0-9.\Q/-_\E])+> + Priority: 3 + - Regex: '"([A-Za-z0-9.\Q/-_\E])+"' + Priority: 2 +IndentPPDirectives: None +IndentWidth: 4 +# LambdaBodyIndentation: Signature +# OneLineFormatOffRegex: ^(\#ifdef|\#endif) # for clang-format version 21 +# PackConstructorInitializers: CurrentLine PenaltyBreakTemplateDeclaration: 10 -#PenaltyExcessCharacter: 1000000 -#PenaltyReturnTypeOnItsOwnLine: 200 -#PenaltyIndentedWhitespace: 0 PointerAlignment: Left -#PPIndentWidth: -1 -#RawStringFormats: -# - Language: Cpp -# Delimiters: -# - cc -# - CC -# - cpp -# - Cpp -# - CPP -# - 'c++' -# - 'C++' -# CanonicalDelimiter: '' -# BasedOnStyle: google -# - Language: TextProto -# Delimiters: -# - pb -# - PB -# - proto -# - PROTO -# EnclosingFunctions: -# - EqualsProto -# - EquivToProto -# - PARSE_PARTIAL_TEXT_PROTO -# - PARSE_TEST_PROTO -# - PARSE_TEXT_PROTO -# - ParseTextOrDie -# - ParseTextProtoOrDie -# - ParseTestProto -# - ParsePartialTestProto -# CanonicalDelimiter: pb -# BasedOnStyle: google -#ReferenceAlignment: Pointer -#ReflowComments: true -#RemoveBracesLLVM: false -#SeparateDefinitionBlocks: Leave -#ShortNamespaceLines: 1 -#SortIncludes: CaseSensitive -#SortJavaStaticImport: Before -#SortUsingDeclarations: true -#SpaceAfterCStyleCast: false -#SpaceAfterLogicalNot: false -SpaceAfterTemplateKeyword: false # true -#SpaceBeforeAssignmentOperators: true -#SpaceBeforeCaseColon: false -#SpaceBeforeCpp11BracedList: false -#SpaceBeforeCtorInitializerColon: true -#SpaceBeforeInheritanceColon: true -SpaceBeforeParens: Custom # ControlStatements +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Custom SpaceBeforeParensOptions: - AfterControlStatements: false # true -# AfterForeachMacros: true -# AfterFunctionDefinitionName: false -# AfterFunctionDeclarationName: false -# AfterIfMacros: true -# AfterOverloadedOperator: false -# BeforeNonEmptyParentheses: false -#SpaceAroundPointerQualifiers: Default -#SpaceBeforeRangeBasedForLoopColon: true -#SpaceInEmptyBlock: false -#SpaceInEmptyParentheses: false + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + AfterIfMacros: false # AfterNot: false + AfterOverloadedOperator: false # AfterPlacementOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false SpacesBeforeTrailingComments: 2 -#SpacesInAngles: Never -#SpacesInConditionalStatement: false -#SpacesInContainerLiterals: true -#SpacesInCStyleCastParentheses: false SpacesInLineCommentPrefix: - Minimum: 1 -# Maximum: -1 -#SpacesInParentheses: false -#SpacesInSquareBrackets: false -#SpaceBeforeSquareBrackets: false -#BitFieldColonSpacing: Both -Standard: c++17 -#StatementAttributeLikeMacros: -# - Q_EMIT -#StatementMacros: -# - Q_UNUSED -# - QT_REQUIRE_VERSION -TabWidth: 99 -#UseCRLF: false -UseTab: ForContinuationAndIndentation # Never + Minimum: 1 +Standard: c++17 +TabWidth: 4 +UseTab: ForContinuationAndIndentation WhitespaceSensitiveMacros: - - BOOST_REQUIRE - BOOST_TEST - - BOOST_TEST_REQUIRE -#WhitespaceSensitiveMacros: -# - STRINGIZE -# - PP_STRINGIZE -# - BOOST_PP_STRINGIZE -# - NS_SWIFT_NAME -# - CF_SWIFT_NAME -... diff --git a/.clang-tidy b/.clang-tidy index 0dc6444ea..652fa2310 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,47 +1,62 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:2;autowrap:nil;-*- +# Copyright 2020-2025 Alfredo A. Correa WarningsAsErrors: '*' HeaderFilterRegex: '.' FormatStyle: file CheckOptions: - - { key: readability-identifier-naming.ClassCase , value: lower_case } - - { key: readability-identifier-naming.FunctionCase , value: lower_case } - - { key: readability-identifier-naming.GlobalConstantCase , value: aNy_CasE } - - { key: readability-identifier-naming.NamespaceCase , value: lower_case } - - { key: readability-identifier-naming.PrivateMemberPrefix , value: '' } - - { key: readability-identifier-naming.PrivateMemberSufix , value: _ } - - { key: readability-identifier-naming.StructCase , value: lower_case } - - { key: readability-identifier-naming.TemplateParameterCase , value: CamelCase } - - { key: readability-identifier-length.MinimumLoopCounterNameLength, value: 2 } - - { key: readability-identifier-length.MinimumParameterNameLength , value: 2 } - - { key: readability-identifier-length.MinimumVariableNameLength , value: 2 } - - { key: readability-identifier-length.IgnoredLoopCounterNames , value: "^[ijk_]$" } - - { key: readability-identifier-length.IgnoredVariableNames , value: "^[ijk]$" } - - { key: readability-identifier-naming.GlobalConstantCase , value: aNy_CasE } - - { key: readability-identifier-naming.PrivateMemberSufix , value: _ } - -# for range-for loops, e.g. `for(auto i : is) ...` -# - { key: readability-identifier-length.IgnoredVariableNames , value: "^[ijk]$" } + - { key: readability-identifier-naming.ClassCase , value: lower_case } + - { key: readability-identifier-naming.FunctionCase , value: lower_case } + - { key: readability-identifier-naming.GlobalConstantCase , value: aNy_CasE } + - { key: readability-identifier-length.IgnoredLoopCounterNames , value: "^[ijk_]$" } + - { key: readability-identifier-length.IgnoredParameterNames , value: "^[xyzabcdnml]$" } + - { key: readability-identifier-length.IgnoredVariableNames , value: "^[ijk_]$" } + - { key: readability-identifier-length.MethodCase , value: lower_case } + - { key: readability-identifier-length.MinimumLoopCounterNameLength , value: 2 } + - { key: readability-identifier-length.MinimumParameterNameLength , value: 2 } + - { key: readability-identifier-length.MinimumVariableNameLength , value: 2 } + - { key: readability-identifier-naming.NamespaceCase , value: lower_case } + - { key: readability-identifier-naming.PrivateMemberPrefix , value: '' } + - { key: readability-identifier-naming.PrivateMemberSuffix , value: _ } + - { key: readability-identifier-naming.PrivateMethodSuffix , value: _ } + - { key: readability-identifier-naming.StructCase , value: lower_case } + - { key: readability-identifier-naming.TemplateParameterCase , value: CamelCase } + - { key: readability-identifier-naming.TypeTemplateParameterIgnoredRegexp, value: expr-type} + - { key: readability-operators-representation.BinaryOperators , value: '&&;&=;&;|;~;!;!=;||;|=;^;^='} +# value: expr-type , bug in clang 16 in c++20 mode https://stackoverflow.com/a/75157215/225186 Checks: '*, -altera-struct-pack-align, + -boost-use-ranges, + -cert-dcl21-cpp, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-macro-usage, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -fuchsia-overloaded-operator, + -fuchsia-default-arguments-calls, -fuchsia-trailing-return, + -hicpp-no-array-decay, + -llvm-prefer-static-over-anonymous-namespace, -llvmlibc-*, -modernize-use-nodiscard, - -readability-magic-numbers -' - + -modernize-use-trailing-return-type, + -readability-magic-numbers, + -readability-simplify-boolean-expr' ## explanation of the suppressed rules above: # -altera-struct-pack-align, // this asks to use non-standard pragmas such as __attribute__((aligned(0))) and _attribute__((packed)), may interfeere with gcc/nvcc compatibility? +# -boost-use-ranges // would introduce a dependency on Boost +# -cert-dcl21-cpp // this asks postfix operator++ to return a const value # -cppcoreguidelines-avoid-magic-numbers, // all tests use magic numbers +# -cppcoreguidelines-pro-bounds-array-to-pointer-decay // for Boost Lightweight test BOOST_TEST # -cppcoreguidelines-macro-usage, // BLAS adaptors uses a lot of macros +# -fuchsia-default-arguments-calls, // this library uses STL functions, which use default arguments # -fuchsia-overloaded-operator, // this library overloads operators (==, <, +, &) -# -fuchsia-trailing-return, // to allow any '-> decltype(auto)', contradicts modernize trailing +# -hicpp-no-array-decay, // for Boost Lightweight Test +# -llvm-prefer-static-over-anonymous-namespace, // contradicts other rules # -llvmlibc-*, // nonsense warnings, for llvm developers +# -llvm-header-guard, // allow PCH +# -misc-include-cleaner // allow PCH # -modernize-use-nodiscard, // this would add [[nodiscard]] to almost all functions, adding a lot of noise -# -readability-magic-numbers, // all tests use magic numbers - +# -modernize-use-trailing-return-type // mandates all lambdas end with -> auto +# -readability-magic-numbers, // many tests use magic numbers +# -readability-simplify-boolean-expr, // for Boost Lightweight Test BOOST_TEST diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 000000000..be25539bb --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,9 @@ +version = 1 + +test_patterns = ["test/**"] + +[[analyzers]] +name = "test-coverage" + +[[analyzers]] +name = "cxx" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..035cb60d1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,5 @@ +Gitlab's [![gitlabci](https://gitlab.com/correaa/boost-multi/badges/master/pipeline.svg)](https://gitlab.com/correaa/boost-multi/-/pipelines?page=1&scope=all) + +This PR was open from a [Gitlab Merge request](https://gitlab.com/correaa/boost-multi/-/merge_requests/new), +it will be probably be merged from Gitlab, and then in can be closed here. + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..906294c9d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,620 @@ +# Copyright 2021-2022 Andrey Semashev +# Copyright 2023 Matt Borland +# Copyright 2025 Alfredo A. Correa +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt) + +name: CI + +on: + pull_request: + push: + branches: + - '**' # matches every branch + # - master + # - develop + # - feature/** + +concurrency: + group: ${{format('{0}:{1}', github.repository, github.ref)}} + cancel-in-progress: true + +env: + GIT_FETCH_JOBS: 8 + NET_RETRY_COUNT: 5 + DEFAULT_BUILD_VARIANT: debug,release + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true + +jobs: + posix: + defaults: + run: + shell: bash + + strategy: + fail-fast: false + matrix: + include: + - toolset: gcc-7 + cxxstd: "03,11,14,17" + address_model: 32,64 + os: ubuntu-latest + container: ubuntu:18.04 + install: + - g++-7-multilib + - toolset: gcc-8 + cxxstd: "03,11,14,17,2a" + address_model: 32,64 + os: ubuntu-latest + container: ubuntu:18.04 + install: + - g++-8-multilib + - toolset: gcc-9 + cxxstd: "03,11,14,17,2a" + address_model: 32,64 + os: ubuntu-latest + container: ubuntu:20.04 + install: + - g++-9-multilib + - toolset: gcc-9 + cxxstd: "03-gnu,11-gnu,14-gnu,17-gnu,2a-gnu" + address_model: 32,64 + os: ubuntu-latest + container: ubuntu:20.04 + install: + - g++-9-multilib + - toolset: gcc-10 + cxxstd: "03,11,14,17,20" + address_model: 32,64 + os: ubuntu-latest + container: ubuntu:20.04 + install: + - g++-10-multilib + - toolset: gcc-11 + cxxstd: "03,11,14,17,20,23" + address_model: 32,64 + os: ubuntu-latest + container: ubuntu:22.04 + install: + - g++-11-multilib + - toolset: gcc-12 + cxxstd: "03,11,14,17,20,23" + address_model: 32,64 + os: ubuntu-latest + container: ubuntu:22.04 + install: + - g++-12-multilib + # Gitub deleted support from 22.04 and 24.04 has internal warnings about futex + # Disable until the runner is out of beta + #- toolset: gcc-13 + # cxxstd: "03,11,14,17,20,23" + # address_model: 32,64 + # os: ubuntu-24.04 + # install: + # - g++-13-multilib + # cxxflags: -Wno-dangling-reference # Boost test hits error + - toolset: gcc-12 + cxxstd: "03-gnu,11-gnu,14-gnu,17-gnu,20-gnu,23-gnu" + address_model: "32" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - g++-12-multilib + - toolset: gcc-12 + cxxstd: "03-gnu,11-gnu,14-gnu,17-gnu,20-gnu,23-gnu" + address_model: "64" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - g++-12-multilib + - name: 32-bit UBSAN + toolset: gcc-12 + cxxstd: "03,11,14,17,20,23" + address_model: "32" + ubsan: 1 + os: ubuntu-latest + container: ubuntu:22.04 + install: + - g++-12-multilib + - toolset: gcc-14 + cxxstd: "03,11,14,17,20,23,2c" + address_model: 32,64 + os: ubuntu-24.04 + install: + - g++-14-multilib + - name: 32-bit UBSAN + toolset: gcc-14 + cxxstd: "03,11,14,17,20,23" + address_model: "32" + ubsan: 1 + os: ubuntu-24.04 + install: + - g++-14-multilib + - name: 64-bit UBSAN + toolset: gcc-14 + cxxstd: "03,11,14,17,20,23" + address_model: "64" + ubsan: 1 + os: ubuntu-24.04 + install: + - g++-14-multilib + + # Linux, clang + - toolset: clang + compiler: clang++-6.0 + cxxstd: "03,11,14,17" + os: ubuntu-latest + container: ubuntu:18.04 + install: + - clang-6.0 + - toolset: clang + compiler: clang++-7 + cxxstd: "03,11,14,17" + os: ubuntu-latest + container: ubuntu:18.04 + install: + - clang-7 + # Note: clang-8 does not fully support C++20, so it is not compatible with libstdc++-8 in this mode + - toolset: clang + compiler: clang++-8 + cxxstd: "03,11,14,17,2a" + os: ubuntu-latest + container: ubuntu:18.04 + install: + - clang-8 + - g++-7 + gcc_toolchain: 7 + - toolset: clang + compiler: clang++-9 + cxxstd: "03,11,14,17,2a" + os: ubuntu-latest + container: ubuntu:20.04 + install: + - clang-9 + - toolset: clang + compiler: clang++-10 + cxxstd: "03,11,14,17,20" + os: ubuntu-latest + container: ubuntu:20.04 + install: + - clang-10 + - toolset: clang + compiler: clang++-11 + cxxstd: "03,11,14,17" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-11 + - toolset: clang + compiler: clang++-12 + cxxstd: "03,11,14,17" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-12 + - toolset: clang + compiler: clang++-13 + cxxstd: "03,11,14,17" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-13 + - toolset: clang + compiler: clang++-14 + cxxstd: "03,11,14,17" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-14 + - toolset: clang + compiler: clang++-14 + cxxstd: "03-gnu,11-gnu,14-gnu,17-gnu" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-14 + - toolset: clang + compiler: clang++-15 + cxxstd: "03,11,14,17,20" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-15 + sources: + - "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + - toolset: clang + compiler: clang++-15 + cxxstd: "03,11,14,17,20,2b" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-15 + - libc++-15-dev + - libc++abi-15-dev + sources: + - "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + cxxflags: -stdlib=libc++ + linkflags: -stdlib=libc++ + - toolset: clang + compiler: clang++-16 + cxxstd: "03,11,14,17,20,2b" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-16 + sources: + - "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + - toolset: clang + compiler: clang++-17 + cxxstd: "03,11,14,17,20,2b" + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-17 + sources: + - "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + - name: UBSAN + toolset: clang + compiler: clang++-14 + cxxstd: "03,11,14,17,20,2b" + cxxflags: -stdlib=libc++ + linkflags: -stdlib=libc++ + ubsan: 1 + os: ubuntu-latest + container: ubuntu:22.04 + install: + - clang-14 + - libc++-14-dev + - libc++abi-14-dev + - toolset: clang + compiler: clang++-18 + cxxstd: "03,11,14,17,20,2b" + os: ubuntu-24.04 + install: + - clang-18 + sources: + - "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + - toolset: clang + compiler: clang++-19 + cxxstd: "03,11,14,17,20,2b" + os: ubuntu-24.04 + install: + - clang-19 + sources: + - "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + - toolset: clang + compiler: clang++-20 + cxxstd: "03,11,14,17,20,23,2c" + os: ubuntu-24.04 + install: + - clang-20 + sources: + - "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + + - toolset: clang + cxxstd: "03,11,14,17,20,2b" + os: macos-14 + - toolset: clang + cxxstd: "03,11,14,17,20,2b" + os: macos-15 + + timeout-minutes: 120 + runs-on: ${{matrix.os}} + container: + image: ${{matrix.container}} + volumes: + - /node20217:/node20217:rw,rshared + - ${{ startsWith(matrix.container, 'ubuntu:1') && '/node20217:/__e/node20:ro,rshared' || ' ' }} + + steps: + - name: Setup environment + run: | + if [ -f "/etc/debian_version" ] + then + echo "DEBIAN_FRONTEND=noninteractive" >> $GITHUB_ENV + export DEBIAN_FRONTEND=noninteractive + fi + if [ -n "${{matrix.container}}" ] + then + echo "GHA_CONTAINER=${{matrix.container}}" >> $GITHUB_ENV + if [ -f "/etc/debian_version" ] + then + apt-get -o Acquire::Retries=$NET_RETRY_COUNT update + if [ "$(apt-cache search "^python-is-python3$" | wc -l)" -ne 0 ] + then + PYTHON_PACKAGE="python-is-python3" + else + PYTHON_PACKAGE="python" + fi + apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y sudo software-properties-common tzdata wget curl apt-transport-https ca-certificates make build-essential g++ $PYTHON_PACKAGE python3 perl git cmake + fi + if [[ "${{matrix.container}}" == "ubuntu:1"* ]]; then + # Node 20 doesn't work with Ubuntu 16/18 glibc: https://github.com/actions/checkout/issues/1590 + curl -sL https://archives.boost.io/misc/node/node-v20.9.0-linux-x64-glibc-217.tar.xz | tar -xJ --strip-components 1 -C /node20217 + fi + fi + git config --global pack.threads 0 + - uses: actions/checkout@v4 + + - name: Install packages + if: matrix.install + run: | + declare -a SOURCE_KEYS SOURCES + if [ -n "${{join(matrix.source_keys, ' ')}}" ] + then + SOURCE_KEYS=("${{join(matrix.source_keys, '" "')}}") + fi + if [ -n "${{join(matrix.sources, ' ')}}" ] + then + SOURCES=("${{join(matrix.sources, '" "')}}") + fi + for key in "${SOURCE_KEYS[@]}" + do + for i in {1..$NET_RETRY_COUNT} + do + echo "Adding key: $key" + wget -O - "$key" | sudo apt-key add - && break || sleep 2 + done + done + if [ ${#SOURCES[@]} -gt 0 ] + then + APT_ADD_REPO_COMMON_ARGS=("-y") + APT_ADD_REPO_SUPPORTED_ARGS="$(apt-add-repository --help | perl -ne 'if (/^\s*-n/) { print "n"; } elsif (/^\s*-P/) { print "P"; } elsif (/^\s*-S/) { print "S"; } elsif (/^\s*-U/) { print "U"; }')" + if [ -n "$APT_ADD_REPO_SUPPORTED_ARGS" -a -z "${APT_ADD_REPO_SUPPORTED_ARGS##*n*}" ] + then + APT_ADD_REPO_COMMON_ARGS+=("-n") + fi + APT_ADD_REPO_HAS_SOURCE_ARGS="$([ -n "$APT_ADD_REPO_SUPPORTED_ARGS" -a -z "${APT_ADD_REPO_SUPPORTED_ARGS##*P*}" -a -z "${APT_ADD_REPO_SUPPORTED_ARGS##*S*}" -a -z "${APT_ADD_REPO_SUPPORTED_ARGS##*U*}" ] && echo 1 || echo 0)" + for source in "${SOURCES[@]}" + do + for i in {1..$NET_RETRY_COUNT} + do + APT_ADD_REPO_ARGS=("${APT_ADD_REPO_COMMON_ARGS[@]}") + if [ $APT_ADD_REPO_HAS_SOURCE_ARGS -ne 0 ] + then + case "$source" in + "ppa:"*) + APT_ADD_REPO_ARGS+=("-P") + ;; + "deb "*) + APT_ADD_REPO_ARGS+=("-S") + ;; + *) + APT_ADD_REPO_ARGS+=("-U") + ;; + esac + fi + APT_ADD_REPO_ARGS+=("$source") + echo "apt-add-repository ${APT_ADD_REPO_ARGS[@]}" + sudo -E apt-add-repository "${APT_ADD_REPO_ARGS[@]}" && break || sleep 2 + done + done + fi + sudo apt-get -o Acquire::Retries=$NET_RETRY_COUNT update + sudo apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y ${{join(matrix.install, ' ')}} locales + sudo locale-gen de_DE.UTF-8 + sudo update-locale + - name: Setup GCC Toolchain + if: matrix.gcc_toolchain + run: | + GCC_TOOLCHAIN_ROOT="$HOME/gcc-toolchain" + echo "GCC_TOOLCHAIN_ROOT=\"$GCC_TOOLCHAIN_ROOT\"" >> $GITHUB_ENV + MULTIARCH_TRIPLET="$(dpkg-architecture -qDEB_HOST_MULTIARCH)" + mkdir -p "$GCC_TOOLCHAIN_ROOT" + ln -s /usr/include "$GCC_TOOLCHAIN_ROOT/include" + ln -s /usr/bin "$GCC_TOOLCHAIN_ROOT/bin" + mkdir -p "$GCC_TOOLCHAIN_ROOT/lib/gcc/$MULTIARCH_TRIPLET" + ln -s "/usr/lib/gcc/$MULTIARCH_TRIPLET/${{matrix.gcc_toolchain}}" "$GCC_TOOLCHAIN_ROOT/lib/gcc/$MULTIARCH_TRIPLET/${{matrix.gcc_toolchain}}" + - name: Setup Boost + run: | + echo GITHUB_REPOSITORY: $GITHUB_REPOSITORY + LIBRARY=${GITHUB_REPOSITORY#*/} + echo LIBRARY: $LIBRARY + echo "LIBRARY=$LIBRARY" >> $GITHUB_ENV + echo GITHUB_BASE_REF: $GITHUB_BASE_REF + echo GITHUB_REF: $GITHUB_REF + REF=${GITHUB_BASE_REF:-$GITHUB_REF} + REF=${REF#refs/heads/} + echo REF: $REF + BOOST_BRANCH=develop && [ "$REF" = "master" ] && BOOST_BRANCH=master || true + echo BOOST_BRANCH: $BOOST_BRANCH + BUILD_JOBS=$((nproc || sysctl -n hw.ncpu) 2> /dev/null) + echo "BUILD_JOBS=$BUILD_JOBS" >> $GITHUB_ENV + echo "CMAKE_BUILD_PARALLEL_LEVEL=$BUILD_JOBS" >> $GITHUB_ENV + DEPINST_ARGS=() + GIT_VERSION="$(git --version | sed -e 's/git version //')" + GIT_HAS_JOBS=1 + if [ -f "/etc/debian_version" ] + then + if $(dpkg --compare-versions "$GIT_VERSION" lt 2.8.0) + then + GIT_HAS_JOBS=0 + fi + else + declare -a GIT_VER=(${GIT_VERSION//./ }) + declare -a GIT_MIN_VER=(2 8 0) + for ((i=0; i<${#GIT_VER[@]}; i++)) + do + if [ -z "${GIT_MIN_VER[i]}" ] + then + GIT_MIN_VER[i]=0 + fi + if [ "${GIT_VER[i]}" -lt "${GIT_MIN_VER[i]}" ] + then + GIT_HAS_JOBS=0 + break + fi + done + fi + if [ "$GIT_HAS_JOBS" -ne 0 ] + then + DEPINST_ARGS+=("--git_args" "--jobs $GIT_FETCH_JOBS") + fi + cd .. + git clone -b "$BOOST_BRANCH" --depth 1 "https://github.com/boostorg/boost.git" "boost-root" + cd boost-root + mkdir -p libs/$LIBRARY + cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY + git submodule update --init tools/boostdep + DEPINST_ARGS+=("$LIBRARY") + python tools/boostdep/depinst/depinst.py "${DEPINST_ARGS[@]}" + if [ -z "${{matrix.cmake_tests}}" ] + then + ./bootstrap.sh + ./b2 headers + if [ -n "${{matrix.compiler}}" -o -n "$GCC_TOOLCHAIN_ROOT" ] + then + echo -n "using ${{matrix.toolset}} : : ${{matrix.compiler}}" > ~/user-config.jam + if [ -n "$GCC_TOOLCHAIN_ROOT" ] + then + echo -n " : \"--gcc-toolchain=$GCC_TOOLCHAIN_ROOT\" \"--gcc-toolchain=$GCC_TOOLCHAIN_ROOT\"" >> ~/user-config.jam + fi + echo " ;" >> ~/user-config.jam + fi + fi + - name: Run tests + if: matrix.cmake_tests == '' + run: | + cd ../boost-root + B2_ARGS=("-j" "$BUILD_JOBS" "toolset=${{matrix.toolset}}" "cxxstd=${{matrix.cxxstd}}" "link=static,shared") + if [ -n "${{matrix.build_variant}}" ] + then + B2_ARGS+=("variant=${{matrix.build_variant}}") + else + B2_ARGS+=("variant=$DEFAULT_BUILD_VARIANT") + fi + if [ -n "${{matrix.threading}}" ] + then + B2_ARGS+=("threading=${{matrix.threading}}") + fi + if [ -n "${{matrix.ubsan}}" ] + then + export UBSAN_OPTIONS="print_stacktrace=1" + B2_ARGS+=("cxxflags=-fsanitize=undefined -fno-sanitize-recover=undefined" "linkflags=-fsanitize=undefined -fuse-ld=gold" "define=UBSAN=1" "debug-symbols=on" "visibility=global") + fi + if [ -n "${{matrix.cxxflags}}" ] + then + B2_ARGS+=("cxxflags=${{matrix.cxxflags}}") + fi + if [ -n "${{matrix.linkflags}}" ] + then + B2_ARGS+=("linkflags=${{matrix.linkflags}}") + fi + if [ -n "${{matrix.address_model}}" ] + then + B2_ARGS+=("address-model=${{matrix.address_model}}") + fi + B2_ARGS+=("libs/$LIBRARY/test") + ./b2 "${B2_ARGS[@]}" cxxflags="-Wall -Wextra -Werror" + + windows: + strategy: + fail-fast: false + matrix: + include: + - toolset: msvc-14.3 + cxxstd: "14,17,20,latest" + addrmd: 32,64 + cxxflags: "/permissive-" + os: windows-2022 + - toolset: gcc + cxxstd: "03,11,14,17,2a" + addrmd: "64" + os: windows-latest + + timeout-minutes: 120 + runs-on: ${{matrix.os}} + + steps: + - uses: actions/checkout@v3 + + - name: Setup Boost + shell: cmd + run: | + echo GITHUB_REPOSITORY: %GITHUB_REPOSITORY% + for /f %%i in ("%GITHUB_REPOSITORY%") do set LIBRARY=%%~nxi + echo LIBRARY: %LIBRARY% + echo LIBRARY=%LIBRARY%>>%GITHUB_ENV% + echo GITHUB_BASE_REF: %GITHUB_BASE_REF% + echo GITHUB_REF: %GITHUB_REF% + if "%GITHUB_BASE_REF%" == "" set GITHUB_BASE_REF=%GITHUB_REF% + set BOOST_BRANCH=develop + for /f %%i in ("%GITHUB_BASE_REF%") do if "%%~nxi" == "master" set BOOST_BRANCH=master + echo BOOST_BRANCH: %BOOST_BRANCH% + cd .. + git clone -b %BOOST_BRANCH% --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + xcopy /s /e /q %GITHUB_WORKSPACE% libs\%LIBRARY%\ + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY% + cmd /c bootstrap + b2 -d0 headers + + - name: Run tests + shell: cmd + run: | + cd ../boost-root + b2 -j3 libs/%LIBRARY%/test toolset=${{matrix.toolset}} cxxflags=${{matrix.cxxflags}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release link=static,shared embed-manifest-via=linker + + windows-clang: + strategy: + fail-fast: false + matrix: + include: + - toolset: clang-win + cxxstd: "14,17,latest" + addrmd: "32" + os: windows-latest + - toolset: clang-win + cxxstd: "14,17,latest" + addrmd: "64" + os: windows-latest + + timeout-minutes: 120 + runs-on: ${{matrix.os}} + + steps: + - uses: actions/checkout@v3 + + - name: Setup Boost + shell: cmd + run: | + echo GITHUB_REPOSITORY: %GITHUB_REPOSITORY% + for /f %%i in ("%GITHUB_REPOSITORY%") do set LIBRARY=%%~nxi + echo LIBRARY: %LIBRARY% + echo LIBRARY=%LIBRARY%>>%GITHUB_ENV% + echo GITHUB_BASE_REF: %GITHUB_BASE_REF% + echo GITHUB_REF: %GITHUB_REF% + if "%GITHUB_BASE_REF%" == "" set GITHUB_BASE_REF=%GITHUB_REF% + set BOOST_BRANCH=develop + for /f %%i in ("%GITHUB_BASE_REF%") do if "%%~nxi" == "master" set BOOST_BRANCH=master + echo BOOST_BRANCH: %BOOST_BRANCH% + cd .. + git clone -b %BOOST_BRANCH% --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + xcopy /s /e /q %GITHUB_WORKSPACE% libs\%LIBRARY%\ + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY% + cmd /c bootstrap + b2 -d0 headers + + - name: Run tests + shell: cmd + run: | + cd ../boost-root + b2 -j3 libs/%LIBRARY%/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release link=static,shared embed-manifest-via=linker define=_CRT_SECURE_NO_WARNINGS cxxflags="-Wno-reserved-macro-identifier -Wno-microsoft-cpp-macro -Wno-documentation -Wno-inconsistent-missing-destructor-override -Wno-global-constructors -Wno-zero-as-null-pointer-constant -Wno-suggest-destructor-override -Wno-redundant-parens -Wno-disabled-macro-expansion -Wno-missing-prototypes -Wno-used-but-marked-unused -Wno-reserved-identifier -Wno-unsafe-buffer-usage -Wno-exit-time-destructors" diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 000000000..c6707f0ef --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,218 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake + +on: + push: + branches: + - '**' # matches every branch + # pull_request: + # branches: [ "master" ] + +# on: +# push: +# branches: [ "master" ] +# pull_request: +# branches: [ "master" ] + +env: + BUILD_TYPE: Release + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true + +jobs: + latest-cpp: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure system + run: | + sudo apt-get -y update + sudo apt-get -y install libboost-dev + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure + + latest-cpp32: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure system + run: | + sudo dpkg --add-architecture i386 + sudo apt-get -y update + sudo apt-get -y install g++-multilib gfortran libboost-serialization-dev:i386 wget + # wget https://downloads.sourceforge.net/project/boost/boost/1.84.0/boost_1_84_0.tar.gz --no-verbose + # tar -xf boost_1_84_0.tar.gz + # cd boost_1_84_0 + # ./bootstrap.sh + # sudo ./b2 cxxflags=-m32 --with-serialization --with-timer install -j4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_FLAGS="-m32" -DDISABLE_THRUST_OMP=1 + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure + + latest-clangpp: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure system + run: | + sudo apt-get -y update + sudo apt-get -y install clang clang-tidy libboost-dev + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_CLANG_TIDY=clang-tidy + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure + + macos-cpp: + runs-on: macos-14 + + steps: + - uses: actions/checkout@v4 + + - name: Configure system + run: | + brew install boost + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure + + # macos-gpp: + # runs-on: macos-14 + + # steps: + # - uses: actions/checkout@v4 + + # - name: Configure system + # run: | + # brew install boost gcc@13 + + # - name: Configure CMake + # run: | + # g++-13 --version + # cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=g++-13 -DCMAKE_CXX_STANDARD=23 -DCMAKE_CXX_FLAGS="-Wl,-ld_classic" # https://developer.apple.com/forums/thread/737707?answerId=765203022#765203022 + + # - name: Build + # run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --verbose + + # - name: Test + # working-directory: ${{github.workspace}}/build + # run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure + + windows: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up CMake + uses: lukka/get-cmake@latest + + - name: Set up MSVC + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Install vcpkg + run: | + git clone https://github.com/microsoft/vcpkg.git + .\vcpkg\bootstrap-vcpkg.bat + + - name: Install FFTW + run: .\vcpkg\vcpkg install fftw3 + + - name: Install Boost + run: .\vcpkg\vcpkg install boost-multi-array boost-timer boost-serialization fftw3 + + - name: Set up Boost environment variables + run: | + echo "BOOST_ROOT=$(Get-Location)\vcpkg\installed\x64-windows" >> $env:GITHUB_ENV + echo "BOOST_INCLUDEDIR=$(Get-Location)\vcpkg\installed\x64-windows\include" >> $env:GITHUB_ENV + echo "BOOST_LIBRARYDIR=$(Get-Location)\vcpkg\installed\x64-windows\lib" >> $env:GITHUB_ENV + echo "PATH=$(Get-Location)\vcpkg\installed\x64-windows\bin;$env:PATH" >> $env:GITHUB_ENV + + - name: Configure CMake + run: cmake -S . -B build -A x64 -DCMAKE_CXX_STANDARD=20 -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DBOOST_ROOT=.\vcpkg\installed\x64-windows + + - name: Build + run: cmake --build build --config Release + + - name: Run tests + run: ctest --test-dir build --timeout 120 --output-on-failure -C Release + timeout-minutes: 10 + + windows17: + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up CMake + uses: lukka/get-cmake@latest + + - name: Set up MSVC + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Install vcpkg + run: | + git clone https://github.com/microsoft/vcpkg.git + .\vcpkg\bootstrap-vcpkg.bat + + - name: Install FFTW + run: .\vcpkg\vcpkg install fftw3 + + - name: Install Boost + run: .\vcpkg\vcpkg install boost-multi-array boost-timer boost-serialization + + - name: Set up Boost environment variables + run: | + echo "BOOST_ROOT=$(Get-Location)\vcpkg\installed\x64-windows" >> $env:GITHUB_ENV + echo "BOOST_INCLUDEDIR=$(Get-Location)\vcpkg\installed\x64-windows\include" >> $env:GITHUB_ENV + echo "BOOST_LIBRARYDIR=$(Get-Location)\vcpkg\installed\x64-windows\lib" >> $env:GITHUB_ENV + echo "PATH=$(Get-Location)\vcpkg\installed\x64-windows\bin;$env:PATH" >> $env:GITHUB_ENV + + - name: Configure CMake + run: cmake -S . -B build -A x64 -DCMAKE_CXX_STANDARD=17 -DCMAKE_TOOLCHAIN_FILE=.\vcpkg\scripts\buildsystems\vcpkg.cmake -DBOOST_ROOT=.\vcpkg\installed\x64-windows + + - name: Build + run: cmake --build build --config Release + + - name: Run tests + run: ctest --test-dir build --timeout 120 --output-on-failure -C Release + timeout-minutes: 10 diff --git a/.github/workflows/gitlab-sync.yml b/.github/workflows/gitlab-sync.yml new file mode 100644 index 000000000..47cb272a6 --- /dev/null +++ b/.github/workflows/gitlab-sync.yml @@ -0,0 +1,22 @@ +name: GitlabSync # https://dev.to/brunorobert/github-and-gitlab-sync-44mn + +on: + - push + - delete + +jobs: + sync: + runs-on: ubuntu-latest + name: Git Repo Sync + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: wangchucheng/git-repo-sync@v0.1.0 + with: + # Such as https://github.com/wangchucheng/git-repo-sync.git + target-url: ${{ secrets.TARGET_URL }} + # Such as wangchucheng + target-username: ${{ secrets.TARGET_USERNAME }} + # You can store token in your project's 'Setting > Secrets' and reference the name here. Such as ${{ secrets.ACCESS\_TOKEN }} + target-token: ${{ secrets.TARGET_TOKEN }} diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 000000000..e01425d45 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,65 @@ +name: Build and Deploy Docs + +on: + push: + branches: ["master"] # main Trigger on push to the main branch + workflow_dispatch: # Allow manual runs + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build-docs: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby (for Asciidoctor) + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + + - name: Install Asciidoctor + run: | + sudo apt-get -qq update + sudo apt-get -qq install --no-install-recommends -y --quiet ca-certificates asciidoctor pandoc ruby-rouge + + - name: Install Boost Build (b2) + run: | + pandoc --version + asciidoctor --version + + - name: Build Docs with b2 + run: | + mkdir .public + cd doc + asciidoctor --failure-level=WARN -a source-highlighter=rouge -b xhtml5 multi.adoc + cd .. + cp -r doc/multi.html .public + # cp -r doc/multi.html .public/index.html + + - name: Upload artifact for GitHub Pages + uses: actions/upload-pages-artifact@v3 + with: + path: .public # e.g. bin.v2/libs/foo/doc/html or similar + + deploy-docs: + needs: build-docs + runs-on: ubuntu-latest + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pull_request_template.md b/.github/workflows/pull_request_template.md new file mode 100644 index 000000000..cb7616181 --- /dev/null +++ b/.github/workflows/pull_request_template.md @@ -0,0 +1,2 @@ +[![gitlab](https://gitlab.com/correaa/boost-multi/badges/constexpr-fill/pipeline.svg)](https://gitlab.com/correaa/boost-multi/-/pipelines) +https://gitlab.com/correaa/boost-multi diff --git a/.gitignore b/.gitignore index cefeafcc1..63dbe2757 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,44 @@ build* .build* .vscode +.devcontainer/* +.cache/clangd/* + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Cmake +cmake-build-debug + +# Clion +.idea diff --git a/.gitlab-ci-correaa.yml b/.gitlab-ci-correaa.yml new file mode 100644 index 000000000..1060f98b8 --- /dev/null +++ b/.gitlab-ci-correaa.yml @@ -0,0 +1,1282 @@ +# Copyright 2020-2025 Alfredo A. Correa + +# Install docker (for local CI or to set up CI machine) +# sudo apt install docker.io +# Install gitlab-runner +# curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash +# sudo apt-get install gitlab-runner + +image: debian:stable +# image: npneq/debian_inq_deps:bookworm + +workflow: + auto_cancel: + on_new_commit: interruptible + rules: + - if: $CI_COMMIT_TAG + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + +# https://stackoverflow.com/a/78636529/225186 + +variables: + GIT_SUBMODULE_STRATEGY: recursive + CODECOV_TOKEN: "999feb5b-a599-4d02-b9c5-46d977247f3a" + RT_VERSION: "0.1" + NVIDIA_DISABLE_REQUIRE: 1 # disable nvidia driver check + SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache + GIT_STRATEGY: fetch # git fetch + GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task + CMAKE_COMPILE_WARNING_AS_ERROR: "ON" + +stages: + - basic + - build + - test + +g++: # debian-stable: default is gcc 12.2.0 as of April 2024 + stage: basic + interruptible: false + # tags: + # - non-shared + script: + - apt-get clean + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake g++ make libboost-timer-dev libboost-serialization-dev libblas-dev liblapack-dev libfftw3-dev pkg-config + - g++ --version + - mkdir build && cd build + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_CXX_FLAGS="-D_GLIBCXX_ASSERTIONS -D_GLIBCXX_DEBUG=1 -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -ftrivial-auto-var-init=zero -fPIE -pie -Wl,-z,relro,-z,now -fstack-protector-strong -fstack-clash-protection -fcf-protection=full" + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest --parallel 2 || ctest --verbose --rerun-failed --output-on-failure + +g++-omp-thrust: + stage: build + interruptible: true + tags: + - non-shared + script: + - apt-get clean + - apt-get -qq update --allow-unauthenticated + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates libthrust-dev cmake g++ make libboost-timer-dev libboost-serialization-dev libblas-dev liblapack-dev libfftw3-dev pkg-config + - g++ --version + - mkdir build && cd build + - cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=ON + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest --parallel 2 || ctest --verbose --rerun-failed --output-on-failure + needs: ["g++"] + +g++-m32 sanitize: + stage: build + image: debian:latest + tags: + - non-shared + - high-bandwidth # for boost source download + - x86_64 # for g++-multilib in image + interruptible: true + script: + - dpkg --add-architecture i386 + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates g++ g++-multilib cmake libboost-serialization-dev:i386 libfftw3-dev:i386 make tar wget + - mkdir build && cd build + - c++ --version + - cmake .. -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_CXX_FLAGS="-m32 -fsanitize=address -fsanitize=signed-integer-overflow -D_GLIBCXX_SANITIZE_VECTOR" -DCMAKE_BUILD_TYPE=Debug + - cmake --build . + - ctest -j 1 --output-on-failure + needs: ["g++"] + +arm64: + stage: build + image: arm64v8/debian:latest + allow_failure: true + tags: + - non-shared + - arm # for image + interruptible: true + script: + - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates g++ cmake make libblas-dev liblapack-dev libboost-timer-dev libboost-serialization-dev libopenmpi-dev pkg-config libfftw3-dev tar wget + - mkdir build && cd build + - c++ --version + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_COMPILE_WARNING_AS_ERROR=ON + - cmake --build . + - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest -j 2 --output-on-failure + needs: ["g++"] + +coverage: + stage: build + only: + refs: + - master + tags: + - non-shared + - docker + allow_failure: true + interruptible: true + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake curl g++ gcovr git make lcov libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config wget + - mkdir build && cd build + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DDISABLE_THRUST_OMP=1 -DCMAKE_CXX_FLAGS="--coverage -O0 -fno-inline -fno-inline-small-functions -fno-default-inline" -DCMAKE_EXE_LINKER_FLAGS="--coverage" + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest -j 1 --output-on-failure -T Test + - ctest -j 1 --output-on-failure -T Coverage + - gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml --root ${CI_PROJECT_DIR} + - bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' + coverage: /^\s*Percentage\s+Coverage:\s*\d+.\d+\%/ + # coverage: /^\s*lines:\s*\d+.\d+\%/ + artifacts: + name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} + expire_in: 2 days + reports: + coverage_report: + coverage_format: cobertura + path: build/coverage.xml + needs: ["g++"] + +g++ cppcheck cpplint memcheck lto: + stage: build + allow_failure: false + interruptible: true + tags: + - non-shared + - docker + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake cppcheck cpplint g++ make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev liblapack-dev libmpich-dev pkg-config valgrind + - g++ --version + - cpplint --version + - cppcheck --version + - valgrind --version + - mkdir build && cd build + # - export VALGRIND_EXE="valgrind --trace-children=yes --leak-check=full --track-origins=yes --show-leak-kinds=all --suppressions=.valgrind-suppressions --gen-suppressions=all --error-exitcode=1 " + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=TRUE -DDISABLE_MPI=1 -DCMAKE_CXX_CPPLINT="cpplint;--quiet" -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;--suppress=missingIncludeSystem;--suppress=syntaxError;--suppress=unmatchedSuppression;--inline-suppr;--std=c++17;--check-config;--error-exitcode=1" + - cmake --build . --parallel 2 || cmake --build . --verbose + - export OMPI_ALLOW_RUN_AS_ROOT=1 + - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + - (ctest -j 2 --output-on-failure --overwrite MemoryCheckCommandOptions=" --trace-children=yes --leak-check=full --track-origins=yes --show-leak-kinds=all --gen-suppressions=all --error-exitcode=1" --overwrite MemoryCheckSuppressionFile=$PWD/../.valgrind-suppressions -T memcheck) || (cat /builds/correaa/boost-multi/build/Testing/Temporary/MemoryChecker.*.log && exit 666) + needs: ["g++"] + +# clang++ sanitizer: +# stage: build +# tags: +# - non-shared +# - docker +# # image: debian:testing +# allow_failure: true +# interruptible: true +# script: +# - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake clang make libasan8 libasan6 libclang-rt-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config +# - mkdir build && cd build +# - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" # ,undefined,pointer-compare,pointer-subtract,float-divide-by-zero -fno-omit-frame-pointer -fno-sanitize-recover=all" +# - cmake --build . --parallel 2 || cmake --build . --verbose +# - ctest -j 2 --output-on-failure -T Test +# needs: ["clang++"] + +# g++ sanitizer: +# stage: build +# # image: debian:testing +# tags: +# - non-shared +# - docker +# interruptible: true +# script: +# - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake g++ make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config +# - mkdir build && cd build +# - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined,pointer-compare,pointer-subtract,float-divide-by-zero -fno-sanitize-recover=all" +# - cmake --build . --parallel 2 || cmake --build . --verbose +# - ctest -j 2 --output-on-failure -T Test +# needs: ["g++"] + +g++-9-bullseye: + stage: build + image: debian:bullseye + tags: + - non-shared + - docker + interruptible: true + allow_failure: false + script: + - arch + - apt-get -qq update + - apt-get install --no-install-recommends --assume-yes ca-certificates cmake g++ g++-9 make libboost-serialization-dev libblas-dev libfftw3-dev libopenmpi-dev pkg-config wget + - cmake --version + - g++ --version + - g++-9 --version + - CXX=g++-9 cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Debug -DDISABLE_MPI=ON + - cmake --build build/ --parallel 2 || cmake --build . --verbose + - cmake --build build/ --target test # - ctest -j 2 --output-on-failure + needs: ["g++"] + +# g++-9: +# stage: build +# image: debian:oldstable # default is gcc 8 as of Dec 2023 +# tags: +# - non-shared +# - docker +# interruptible: true +# script: +# - arch +# - apt-get -qq update +# - apt-get install --no-install-recommends --assume-yes ca-certificates cmake g++-11 make libboost-serialization-dev libblas-dev libfftw3-dev libopenmpi-dev pkg-config wget +# # - wget https://github.com/Kitware/CMake/releases/download/v3.27.0-rc3/cmake-3.27.0-rc3-linux-`arch`.sh --no-verbose +# # - sh ./cmake-*.sh --skip-license --prefix=/usr +# - cmake --version +# - g++ --version +# - g++-11 --version +# - mkdir build && cd build +# - CXX=g++-12 cmake .. -DCMAKE_BUILD_TYPE=Debug -DDISABLE_MPI=ON +# - cmake --build . --parallel 2 || cmake --build . --verbose +# - ctest -j 2 --output-on-failure +# needs: ["g++"] + +g++-unstable c++23 par: # debian-testing: default is gcc 14.2.0 as of Sep 2022 + stage: build + image: debian:unstable + tags: + - non-shared + - docker + allow_failure: true + interruptible: true + script: + - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet g++ pkg-config cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev libopenmpi-dev libtbb-dev + - mkdir build && cd build + - g++ --version + - CXX=g++ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=23 -DCMAKE_CXX_FLAGS="-fimplicit-constexpr" -DDISABLE_MPI=1 + - cmake --build . --parallel 2 || cmake --build . --verbose + - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest --output-on-failure + needs: ["g++-testing c++20"] + +clang++: + stage: basic + # tags: + # - non-shared + interruptible: false + script: + - apt-get clean + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet clang cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev liblapack-dev pkg-config + - mkdir build && cd build + - clang++ --version + - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-D_GLIBCXX_ASSERTIONS -D_GLIBCXX_DEBUG=1" + - cmake --build . --parallel 2 || cmake --build . --parallel 1 --verbose + - ctest --parallel 2 || ctest --verbose --rerun-failed --output-on-failure + +clang++ ubsan: # more UB is detected on Release + stage: build + tags: + - non-shared + interruptible: true + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet clang libclang-rt-dev cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev liblapack-dev pkg-config + - export UBSAN_OPTIONS=print_stacktrack=1 + - mkdir build && cd build + - clang++ --version + - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-g -fsanitize=undefined -fsanitize=signed-integer-overflow -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=undefined -DUBSAN=1" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=undefined" + - cmake --build . --parallel 4 || cmake --build . --parallel 1 --verbose + - ctest -j 2 --output-on-failure + needs: ["clang++"] + +clang++-18 mull-18: # mull-18 crashes + image: ubuntu:24.04 + stage: build + tags: + - x86_64 # for mull + - large-memory-space + - non-shared + interruptible: true + allow_failure: true + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet gnupg curl wget ca-certificates clang-18 cmake file libclang-rt-18-dev make libboost-timer-dev libboost-serialization-dev > /dev/null + - curl -1sLf 'https://dl.cloudsmith.io/public/mull-project/mull-stable/setup.deb.sh' | bash + - apt-get -qq update + - apt search mull + - apt-get -qq install --no-install-recommends -y --quiet mull-18 libclang-cpp18 + - mkdir build && cd build + - clang++-18 --version + - which mull-runner-18 + - ls -all `which mull-runner-18` + - file `which mull-runner-18` + - mull-runner-18 --version + - CXX=clang++-18 cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -fpass-plugin=/usr/lib/mull-ir-frontend-18 -g -grecord-command-line" + - cmake --build . --parallel 8 || cmake --build . --parallel 1 --verbose + - ctest + - cd test + - ls *.x | xargs -n 1 sh -c 'echo $0 && ((mull-runner-18 --timeout 30000 --ld-search-path=/usr/lib/x86_64-linux-gnu $0 -test-program=ctest -- -j2 --stop-on-failure) || exit 255)' + needs: ["clang++", "g++"] + +# deb: +# stage: build +# script: +# - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake g++ make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config +# - mkdir build && cd build +# - cmake .. -DCMAKE_BUILD_TYPE=Release +# - cmake --build . --parallel 2 || cmake --build . --verbose +# - ctest -j 2 --output-on-failure +# - cpack -G DEB +# - ls -all +# - dpkg -i multi_all.deb +# - ls -all /usr/include/multi +# - c++ ../test/main.cpp +# artifacts: +# paths: +# - build/multi_all.deb +# needs: ["g++", "clang++"] + +clang++-latest libc++ format: # Aug2025 clang 19 + stage: build + image: debian:latest + tags: + - non-shared + - docker + interruptible: true + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang clang-format cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libc++-dev libc++abi-dev libfftw3-dev liblapack-dev pkg-config tar wget + - mkdir build && cd build + - clang++ --version + - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-stdlib=libc++ -DBOOST_NO_CXX98_FUNCTION_BASE -D_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" + - cmake --build . + - ctest -j 2 --output-on-failure + - cd .. + - clang-format --version + - clang-format --dry-run -Werror test/*.cpp include/**/*.hpp || echo "errors in formatting" + needs: ["clang++"] + +clang++-16 std-20: + stage: build + image: ubuntu:24.04 # debian:latest + tags: + - non-shared + - docker + interruptible: true + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang-16 cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libc++-dev libc++abi-dev libfftw3-dev liblapack-dev pkg-config tar wget + - mkdir build && cd build + - clang-16 --version + - clang++-16 --version + - CXX=clang++-16 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=20 + - cmake --build . + - ctest -j 2 --output-on-failure + needs: ["clang++"] + +vs2019-windows: + stage: build + only: + refs: + - master + allow_failure: true + interruptible: true + variables: + BOOST_ROOT: ${CI_PROJECT_DIR}/vcpkg/installed/x64-windows + # BOOST_INCLUDEDIR: ${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/include + # BOOST_LIBRARYDIR: ${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/lib + script: + - choco --version + - choco install poshgit -y + - choco install visualstudio2019community -y --params "'--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended'" + - git clone --depth=1 https://github.com/microsoft/vcpkg.git + - .\vcpkg\bootstrap-vcpkg.bat + - .\vcpkg\vcpkg install boost-core boost-multi-array boost-timer pkgconf + - mkdir build + - $env:Path += ";${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/bin" + - cmake --version + - cmake -S . -B build -DCMAKE_CXX_STANDARD=23 + - cmake --build build --config Release --parallel 2 --verbose + - ctest --test-dir build --output-on-failure -C Release + tags: + - saas-windows-medium-amd64 + needs: ["clang++", "g++"] + timeout: 30 minutes + +vs2022-shell: + stage: build + allow_failure: false + interruptible: true + script: + # - winget install --id=Microsoft.VisualStudio.2022.Community -e + # - winget install Microsoft.VisualStudio.2022.BuildTools + # - winget install Git.Git Kitware.CMake Microsoft.PowerShell Microsoft.msmpi Microsoft.msmpisdk + # - choco --version + # - choco install -y visualstudio2019community poshgit + - pwd + # - git clone --depth=1 https://github.com/microsoft/vcpkg.git + # - .\vcpkg\bootstrap-vcpkg.bat + # - .\vcpkg\vcpkg install blas fftw3 boost-multi-array boost-timer + - $env:Path += ";C:/vcpkg/installed/x64-windows/bin;${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/bin;C:\Program Files\CMake\bin;C:\Program Files\7-Zip;C:\local\boost_1_86_0\lib" + - $Env:Path -split ';' + - mkdir build + - cmake --version + - cmake -S . -B build -DCMAKE_CXX_FLAGS="/wd4514 /wd5045" -DCMAKE_CXX_STANDARD=20 -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" # -DMPIEXEC_EXECUTABLE="C:/Program Files/Microsoft MPI/Bin/mpiexec.exe" + - cmake --build build --config Debug --parallel 4 ; if (!$?) { cmake --build build --config Debug --parallel 1 --verbose } + - ctest -j 4 --test-dir build --output-on-failure -C Debug + tags: + - windows, shell + needs: ["clang++", "g++"] + timeout: 20 minutes + +vs2022-shell c++17: + stage: build + allow_failure: false + interruptible: true + script: + # - choco --version + # - choco install -y visualstudio2019community poshgit + - $env:Path += ";C:/vcpkg/installed/x64-windows/bin;${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/bin;C:\Program Files\CMake\bin;C:\Program Files\7-Zip;C:\local\boost_1_86_0\lib" + # - git clone --depth=1 https://github.com/microsoft/vcpkg.git + # - .\vcpkg\bootstrap-vcpkg.bat + # - .\vcpkg\vcpkg install fftw3 boost-multi-array boost-timer + - mkdir build + - cmake --version + - cmake -S . -B build -DCMAKE_CXX_FLAGS="/wd4514 /wd5045" -DCMAKE_CXX_STANDARD=17 -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" # -DDISABLE_MPI=1 + - cmake --build build --config Debug --parallel 4 ; if (!$?) { cmake --build build --config Debug --parallel 1 --verbose } + - ctest -j 4 --test-dir build --output-on-failure -C Debug + tags: + - windows, shell + needs: ["vs2022-shell"] + timeout: 20 minutes + +vs2022-shell c++23: + stage: build + allow_failure: false + interruptible: true + script: + #- git clone --depth=1 https://github.com/microsoft/vcpkg.git + #- .\vcpkg\bootstrap-vcpkg.bat + #- .\vcpkg\vcpkg install boost-multi-array boost-timer + - $env:Path += ";C:/vcpkg/installed/x64-windows/bin;${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/bin;C:\Program Files\CMake\bin;C:\Program Files\7-Zip;C:\local\boost_1_86_0\lib" + - mkdir build + - cmake --version + - cmake -S . -B build -DCMAKE_CXX_FLAGS="/wd4514 /wd5045" -DCMAKE_CXX_STANDARD=23 -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" # -DDISABLE_MPI=1 + - cmake --build build --config Release --parallel 4 ; if (!$?) { cmake --build build --config Release --parallel 1 --verbose } + - ctest -j 4 --test-dir build --output-on-failure -C Release + tags: + - windows, shell + needs: ["vs2022-shell"] + timeout: 20 minutes + +vs2022-shell cudatk-17: + stage: build + allow_failure: false + interruptible: true + script: + - whoami + - $env:Path += ";C:/vcpkg/installed/x64-windows/bin;${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/bin;C:\Program Files\CMake\bin;C:\Program Files\7-Zip;C:\local\boost_1_86_0\lib" + # - git clone --depth=1 https://github.com/microsoft/vcpkg.git + # - .\vcpkg\bootstrap-vcpkg.bat + # - .\vcpkg\vcpkg install fftw3 boost-multi-array boost-timer + - mkdir build + - cmake --version + # -DCMAKE_CXX_FLAGS="/Wall /wd4668 /wd5045" + # -DCMAKE_CUDA_FLAGS=-Xcompiler=/Wall -Xcompiler=/wd4061,/wd4191,/wd4244,/wd4296,/wd4365,/wd4464 -Xcompiler=/wd4555 -Xcompiler=/wd4619 -Xcompiler=/wd4668 -Xcompiler=/wd4820 -Xcompiler=/wd5026 -Xcompiler=/wd5027 -Xcompiler=/wd5039 -Xcompiler=/wd5031 -Xcompiler=/wd5032 -Xcompiler=/wd5045 -Xcompiler=/wd5220 -Xcompiler=/wd5246 + - cmake -S . -B build -DCMAKE_CUDA_STANDARD=17 -DCMAKE_CXX_STANDARD=17 -DENABLE_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES=75 "-Wno-deprecated-gpu-targets" -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + - cmake --build build --config Debug --parallel ; if (!$?) { cmake --build build --config Debug --parallel 1 --verbose } + tags: + - windows, shell, cudatk + needs: ["vs2022-shell"] # , "cuda"] + timeout: 60 minutes + +vs2022-shell cudatk: + stage: build + allow_failure: false + interruptible: true + script: + - whoami + - $env:Path += ";C:/vcpkg/installed/x64-windows/bin;${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/bin;C:\Program Files\CMake\bin;C:\Program Files\7-Zip;C:\local\boost_1_86_0\lib" + # - git clone --depth=1 https://github.com/microsoft/vcpkg.git + # - .\vcpkg\bootstrap-vcpkg.bat + # - .\vcpkg\vcpkg install fftw3 boost-multi-array boost-timer + - mkdir build + - cmake --version + # -DCMAKE_CXX_FLAGS="/Wall /wd4668 /wd5045" + # -DCMAKE_CUDA_FLAGS=-Xcompiler=/Wall -Xcompiler=/wd4061,/wd4191,/wd4244,/wd4296,/wd4365,/wd4464 -Xcompiler=/wd4555 -Xcompiler=/wd4619 -Xcompiler=/wd4668 -Xcompiler=/wd4820 -Xcompiler=/wd5026 -Xcompiler=/wd5027 -Xcompiler=/wd5039 -Xcompiler=/wd5031 -Xcompiler=/wd5032 -Xcompiler=/wd5045 -Xcompiler=/wd5220 -Xcompiler=/wd5246 + - cmake -S . -B build -DCMAKE_CUDA_STANDARD=20 -DCMAKE_CXX_STANDARD=20 -DENABLE_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES=75 "-Wno-deprecated-gpu-targets" -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + - cmake --build build --config Debug --parallel ; if (!$?) { cmake --build build --config Debug --parallel 1 --verbose } + tags: + - windows, shell, cudatk + needs: ["vs2022-shell"] # , "cuda"] + timeout: 60 minutes + +vs2022-shell cuda: + stage: build + allow_failure: false + interruptible: true + script: + # - choco --version + # - choco install -y visualstudio2019community poshgit + - whoami + - nvidia-smi + - $env:Path += ";C:/vcpkg/installed/x64-windows/bin;${CI_PROJECT_DIR}/vcpkg/installed/x64-windows/bin;C:\Program Files\CMake\bin;C:\Program Files\7-Zip;C:\local\boost_1_86_0\lib" + # - git clone --depth=1 https://github.com/microsoft/vcpkg.git + # - .\vcpkg\bootstrap-vcpkg.bat + # - .\vcpkg\vcpkg install fftw3 boost-multi-array boost-timer + - mkdir build + - cmake --version + - cmake -S . -B build -DCMAKE_CXX_FLAGS="/Wall /wd4668 /wd5045" -DCMAKE_CUDA_STANDARD=20 -DCMAKE_CXX_STANDARD=20 -DENABLE_CUDA=1 -DCMAKE_CUDA_FLAGS="-Xcompiler=/Wall -Xcompiler=/wd5045 -Wno-deprecated-gpu-targets" -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + - cmake --build build --config Debug --parallel ; if (!$?) { cmake --build build --config Debug --parallel 1 --verbose } + - ctest -j 4 --test-dir build --output-on-failure -C Debug + tags: + - windows, shell, cudatk, cuda + needs: ["vs2022-shell cudatk"] + timeout: 60 minutes + +clang++-m32: + stage: build + image: debian:latest + tags: + - non-shared + - high-bandwidth # for boost source download + - x86_64 # for g++-multilib in image + interruptible: true + script: + - dpkg --add-architecture i386 + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang g++ g++-multilib git cmake make libboost-serialization-dev:i386 tar wget pkg-config > /dev/null + - mkdir build && cd build + - c++ --version + - CXX=clang++ cmake .. -DCMAKE_CXX_FLAGS="-m32" -DCMAKE_BUILD_TYPE=Debug + - cmake --build . + - ctest -j 2 --output-on-failure + needs: ["clang++"] + +clang++-latest tidy iwyu everything: + stage: build + image: debian:latest # clang 18 as of Apr 2024 + tags: + - non-shared + - docker + interruptible: true + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet clang clang-tidy cmake iwyu make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev liblapack-dev pkg-config + - mkdir build && cd build + - clang++ --version + - clang-tidy --version + - iwyu --version + - CXX=clang++ CXX_FLAGS="-Weverything" cmake .. -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_CLANG_TIDY=clang-tidy -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="iwyu;-Xiwyu;--mapping_file=$PWD/../.iwyu-test.imp;-Xiwyu;--no_fwd_decls;-Xiwyu;--error" + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest -j 2 --output-on-failure + needs: ["clang++"] + +g++-testing c++20: + stage: build + image: debian:testing + tags: + - non-shared + - docker + interruptible: true + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet g++ pkg-config cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev + - mkdir build && cd build + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=20 -DCMAKE_CXX_FLAGS="-fimplicit-constexpr" + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest --output-on-failure + needs: ["g++"] + +# clang++-19-unstable libc++ c++26 boost_1_87: +# stage: build +# image: debian:unstable # clang 17 as of March 2024 +# tags: +# - non-shared +# - docker +# - high-bandwidth +# interruptible: true +# script: # clang 17 doesn't work with gcc 13 libstd +# - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang clang-19 cmake make libblas-dev libc++-19-dev libc++abi-19-dev libfftw3-dev pkg-config tar wget +# - wget https://downloads.sourceforge.net/project/boost/boost/1.87.0/boost_1_87_0.tar.gz --no-verbose +# - tar -xf boost_1_87_0.tar.gz +# - cd boost_1_87_0 +# - ./bootstrap.sh --with-toolset=clang +# - ./b2 toolset=clang cxxflags=-stdlib=libc++ linkflags=-stdlib=libc++ --with-serialization --with-timer install -j4 +# - cd .. +# - mkdir build && cd build +# - CXX=clang++-19 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=26 -DCMAKE_CXX_FLAGS="-stdlib=libc++ -D_LIBCPP_ENABLE_DEBUG_MODE=1 -D_LIBCPP_DEBUG_RANDOMIZE_UNSPECIFIED_STABILITY=1 -D_LIBCPP_DEBUG_STRICT_WEAK_ORDERING_CHECK=1" # -DBoost_NO_SYSTEM_PATHS=ON +# - cmake --build . --parallel 2 || cmake --build . --verbose +# - ctest --output-on-failure +# needs: ["clang++-latest libc++", "g++-testing c++20"] + +clang++-oldstable-cmake4: + stage: build + image: debian:oldstable # clang 7.0.1 as of Jan 2025 + tags: + - non-shared + - docker + interruptible: true + script: + - arch + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang-13 cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config wget + - cmake --version + - wget --no-verbose -O cmake-install.sh https://github.com/Kitware/CMake/releases/download/v4.1.0-rc2/cmake-4.1.0-rc2-linux-`arch`.sh # https://cmake.org/files/v3.18/cmake-3.18.0-rc1-Linux-`arch`.sh --no-verbose + - sh cmake-install.sh --skip-license --prefix=/usr + - cmake --version + - mkdir build && cd build + - clang++-13 --version + - CXX=clang++-13 cmake .. -DCMAKE_BUILD_TYPE=Release + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest -j 2 --output-on-failure + needs: ["clang++"] + +oneapi: + stage: build + image: intel/oneapi-hpckit:2023.0.0-devel-ubuntu22.04 # Intel(R) oneAPI DPC++/C++ Compiler 2023.0.0 (2023.0.0.20221201) + tags: + - non-shared + - large-disk-space + - x86_64 + interruptible: true + script: + - apt-get update + - apt-get install --no-install-recommends -y --quiet ca-certificates cmake curl libboost-serialization-dev libboost-timer-dev libblas-dev liblapack-dev libfftw3-dev make pkg-config + - mkdir build && cd build + - icpx --version + - CXX=icpx cmake .. -DCMAKE_BUILD_TYPE=Release -DDISABLE_MPI=ON # Intel MPI doesn't work on CI + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest --parallel 1 || ctest --verbose --rerun-failed --output-on-failure + +oneapi-2022.2: + stage: build + image: intel/oneapi-hpckit:2022.2-devel-ubuntu20.04 + tags: + - non-shared + - large-disk-space + - x86_64 + allow_failure: true + interruptible: true + script: + - apt-get update --allow-unauthenticated=false && apt-get install --no-install-recommends -y --quiet ca-certificates cmake curl g++ libboost-serialization-dev libboost-timer-dev libblas-dev liblapack-dev libfftw3-dev make pkg-config + - mkdir build && cd build + - icpx --version + - CXX=icpx CXXFLAGS="-DPSTL_USE_PARALLEL_POLICIES=0" cmake .. -DCMAKE_BUILD_TYPE=Release -DDISABLE_MPI=ON # Intel MPI doesn't work on CI + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest --parallel 2 || ctest --verbose --rerun-failed --output-on-failure + needs: ["oneapi"] + +oneapi-latest c++20: + stage: build + image: intel/oneapi-hpckit:latest # icpx --version (2023.2.0.20230721) as of Dec 2023 + allow_failure: false + interruptible: true + tags: + - non-shared + - large-disk-space + - high-bandwidth + - x86_64 + script: + # - wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor | tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null + # - echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | tee /etc/apt/sources.list.d/oneAPI.list + - apt-get update && apt-get install --no-install-recommends -y --quiet ca-certificates cmake curl g++ libboost-serialization-dev libboost-timer-dev libblas-dev liblapack-dev libfftw3-dev make pkg-config + - mkdir build && cd build + - icpx --version + - CXX=icpx cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 -DDISABLE_MPI=ON # Intel MPI doesn't work on CI + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest --parallel 2 || ctest --verbose --rerun-failed --output-on-failure + needs: ["oneapi"] + +nvhpc: + stage: build + image: nvcr.io/nvidia/nvhpc:23.11-devel-cuda12.3-ubuntu22.04 # https://catalog.ngc.nvidia.com/orgs/nvidia/containers/nvhpc/tags + tags: + - non-shared + - large-disk-space + - x86_64 + script: + - apt-get update && apt-get install --no-install-recommends -y cmake make libboost-serialization-dev + - /opt/nvidia/hpc_sdk/Linux_x86_64/2023/compilers/bin/nvc++ --version + - mkdir build && cd build + - CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2023/compilers/bin/nvc++ cmake .. + - cmake --build . --parallel 2 || cmake --build . --verbose + - export OMPI_ALLOW_RUN_AS_ROOT=1 + - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + - ctest --parallel 2 --output-on-failure || ctest --verbose --rerun-failed --output-on-failure + +nvhpc-22.7: + stage: build + image: nvcr.io/nvidia/nvhpc:22.7-devel-cuda11.7-ubuntu22.04 # https://catalog.ngc.nvidia.com/orgs/nvidia/containers/nvhpc/tags + tags: + - non-shared + - large-disk-space + - x86_64 + interruptible: true + script: + - apt-get update && apt-get install --no-install-recommends -y cmake make libboost-timer-dev libboost-serialization-dev + - /opt/nvidia/hpc_sdk/Linux_x86_64/2022/compilers/bin/nvc++ --version + - wget --no-verbose -O cmake-install.sh https://github.com/Kitware/CMake/releases/download/v3.30.0-rc4/cmake-3.30.0-rc4-linux-`arch`.sh # https://cmake.org/files/v3.18/cmake-3.18.0-rc1-Linux-`arch`.sh --no-verbose + - sh cmake-install.sh --skip-license --prefix=/usr + - cmake --version + - mkdir build && cd build + - CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2022/compilers/bin/nvc++ cmake .. -DCMAKE_BUILD_TYPE=Release + - cmake --build . --parallel 2 || cmake --build . --verbose + - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest --output-on-failure + needs: ["nvhpc"] + +# nvhpc-25.1: +# stage: build +# image: nvcr.io/nvidia/nvhpc:25.1-devel-cuda12.6-ubuntu24.04 # https://catalog.ngc.nvidia.com/orgs/nvidia/containers/nvhpc/tags +# tags: +# - non-shared +# - large-disk-space +# - x86_64 +# interruptible: true +# script: +# - apt-get update && apt-get install --no-install-recommends -y cmake make libboost-timer-dev libboost-serialization-dev libfftw3-dev pkg-config +# - /opt/nvidia/hpc_sdk/Linux_x86_64/2025/compilers/bin/nvc++ --version +# - mkdir build && cd build +# - CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2025/compilers/bin/nvc++ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 # -DCMAKE_CXX_FLAGS="-stdpar=multicore" +# - cmake --build . --parallel 2 || cmake --build . --verbose +# - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest --output-on-failure +# needs: ["nvhpc"] + +nvhpc-25.5: + stage: build + image: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu24.04 # https://catalog.ngc.nvidia.com/orgs/nvidia/containers/nvhpc/tags + tags: + - non-shared + - large-disk-space + - x86_64 + interruptible: true + script: + - apt-get update && apt-get install --no-install-recommends -y cmake make libboost-timer-dev libboost-serialization-dev libfftw3-dev pkg-config + - /opt/nvidia/hpc_sdk/Linux_x86_64/2025/compilers/bin/nvc++ --version + - mkdir build && cd build + - CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2025/compilers/bin/nvc++ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-std=c++23" # -DCMAKE_CXX_FLAGS="-stdpar=multicore" + - cmake --build . --parallel 2 || cmake --build . --verbose + - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest --output-on-failure + needs: ["nvhpc"] + +cuda: + stage: build + allow_failure: false + image: nvcr.io/nvidia/cuda:12.0.1-devel-ubuntu22.04 + tags: + - non-shared + - nvidia-gpu + interruptible: true + script: + - nvidia-smi + - apt-get update + - apt-get install --no-install-recommends -y cmake g++-12 wget pkg-config make libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev + - mkdir build && cd build + - g++-12 --version + - /usr/local/cuda/bin/nvcc --version + - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES=61 -DCMAKE_CUDA_HOST_COMPILER=g++-12 -DCMAKE_CUDA_FLAGS="-allow-unsupported-compiler" + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest || ctest --rerun-failed --output-on-failure + needs: ["g++"] + +cuda-11.5.2: + stage: build + image: nvcr.io/nvidia/cuda:11.5.2-devel-ubuntu20.04 # 11.4.3-devel-ubuntu20.04 + tags: + - non-shared + - nvidia-gpu + - x86_64 + interruptible: true + script: + - nvidia-smi + # - export CUDA_VISIBLE_DEVICES=2 + - apt-get -qq update + - DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev libtbb-dev + - wget --no-verbose -O cmake-install.sh https://github.com/Kitware/CMake/releases/download/v3.27.0-rc3/cmake-3.27.0-rc3-linux-x86_64.sh + - sh ./cmake-install.sh --skip-license --prefix=/usr + - cmake --version + - mkdir build && cd build + - /usr/local/cuda/bin/nvcc --version + - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES=61 + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest -j 2 --output-on-failure + needs: ["cuda"] + +culang++-16 cuda-11.4.3: + stage: build + image: nvcr.io/nvidia/cuda:11.4.3-devel-ubuntu20.04 + tags: + - non-shared + - nvidia-gpu + interruptible: true + script: + - nvidia-smi + - apt-get -qq update + - DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev + - apt-get install --no-install-recommends -y lsb-release software-properties-common + - wget https://github.com/Kitware/CMake/releases/download/v3.27.0-rc3/cmake-3.27.0-rc3-linux-x86_64.sh --no-verbose + - sh ./cmake-3.27.0-rc3-linux-x86_64.sh --skip-license --prefix=/usr + - cmake --version + - wget https://apt.llvm.org/llvm.sh + - chmod u+x llvm.sh + - ./llvm.sh 16 + - mkdir build && cd build + - clang++-16 --version + - cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_COMPILER=clang++-16 -DCMAKE_CXX_COMPILER=clang++-16 + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest -j 2 --output-on-failure + needs: ["cuda", "clang++"] + +cuda-12.6.3: + stage: build + allow_failure: true + image: nvcr.io/nvidia/cuda:12.6.3-devel-ubuntu24.04 # nvcr.io/nvidia/cuda:12.5.0-devel-ubuntu22.04 + tags: + - non-shared + - nvidia-gpu + interruptible: true + script: + - nvidia-smi + - apt-get -qq update + - apt-get install --no-install-recommends -y cmake g++ wget pkg-config make libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev + - cmake --version + - mkdir build && cd build + - g++ --version + - /usr/local/cuda/bin/nvcc --version + - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CXX_STANDARD=20 -DCMAKE_CUDA_STANDARD=20 -DCMAKE_CUDA_ARCHITECTURES=native -DCMAKE_CUDA_HOST_COMPILER=g++ + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest || ctest --rerun-failed --output-on-failure + needs: ["cuda"] + +# cuda-12.9.0: +# stage: build +# allow_failure: false +# image: nvcr.io/nvidia/cuda:12.9.0-devel-ubuntu24.04 # https://catalog.ngc.nvidia.com/orgs/nvidia/containers/cuda/tags +# tags: +# - non-shared +# - nvidia-gpu +# interruptible: true +# script: +# - nvidia-smi +# - apt-get -qq update +# - apt-get install --no-install-recommends -y cmake g++ wget pkg-config make libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev +# - cmake --version +# - mkdir build && cd build +# - g++ --version +# - /usr/local/cuda/bin/nvcc --version +# - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CXX_STANDARD=20 -DCMAKE_CUDA_STANDARD=20 -DCMAKE_CUDA_ARCHITECTURES=native -DCMAKE_CUDA_HOST_COMPILER=g++ +# - cmake --build . --verbose --parallel 2 || cmake --build . --verbose +# - ctest || ctest --rerun-failed --output-on-failure +# needs: ["g++", "clang++", "cuda"] + +cuda-13.0.1: + stage: build + allow_failure: true + image: nvcr.io/nvidia/cuda:13.0.1-devel-ubuntu24.04 # nvcr.io/nvidia/cuda:12.9.0-devel-ubuntu24.04 # https://catalog.ngc.nvidia.com/orgs/nvidia/containers/cuda/tags + tags: + - non-shared + - nvidia-gpu + - arch75 + interruptible: true + script: + - nvidia-smi + - apt-get -qq update + - apt-get install --no-install-recommends -y cmake g++ wget pkg-config make libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev + - cmake --version + - mkdir build && cd build + - g++ --version + - /usr/local/cuda/bin/nvcc --version + - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_FLAGS="-ftemplate-backtrace-limit=0" -DENABLE_CUDA=1 -DCMAKE_CXX_STANDARD=20 -DCMAKE_CUDA_STANDARD=20 -DCMAKE_CUDA_ARCHITECTURES=native -DCMAKE_CUDA_HOST_COMPILER=g++ + - cmake --build . --verbose --parallel 2 || cmake --build . --verbose + - ctest || ctest --rerun-failed --output-on-failure + needs: ["g++", "clang++", "cuda"] + +culang++-19 cuda-12.6.3 tidy: # https://catalog.ngc.nvidia.com/orgs/nvidia/containers/cuda/tags + stage: build + image: nvcr.io/nvidia/cuda:12.6.3-devel-ubuntu24.04 + allow_failure: false + tags: + - non-shared + - nvidia-gpu + - high-bandwidth + interruptible: true + script: + - nvidia-smi + - apt-get -qq update + - apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev + - apt-get install --no-install-recommends -y lsb-release software-properties-common + - wget https://apt.llvm.org/llvm.sh + - chmod u+x llvm.sh + - ./llvm.sh 19 + - apt-get install --no-install-recommends -y clang-19 clang-tidy-19 + - mkdir build && cd build + - clang++-19 --version + - clang-tidy-19 --version + - cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_FLAGS="-Wno-unknown-cuda-version" -DCMAKE_CXX_CLANG_TIDY=clang-tidy-19 -DENABLE_CUDA=1 -DCMAKE_CUDA_COMPILER=clang++-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_CUDA_ARCHITECTURES=native + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest -j 2 --output-on-failure + needs: ["cuda", "clang++"] + +rocm: + stage: build + image: rocm/dev-ubuntu-24.04 # rocm/dev-ubuntu-22.04 + allow_failure: false + tags: + - non-shared + - docker + - high-bandwidth # for download rocm extras + - x86_64 # for rocm image + interruptible: true + script: + - apt-get -qq update + - apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev wget gpg + - apt-get install --no-install-recommends -y rocthrust-dev hipblas-dev hipfft-dev rocm-device-libs + - /opt/rocm/bin/hipconfig --full + - HIPCC_VERBOSE=1 /opt/rocm/bin/hipcc --version + - cmake --version + - mkdir build && cd build + - export PATH=/opt/rocm/bin:/opt/rocm/llvm/bin:${PATH} + - export CMAKE_PREFIX_PATH=/opt/rocm:${CMAKE_PREFIX_PATH} + - export ROCM_PATH=/opt/rocm + - export HIP_PATH=/opt/rocm + - cmake .. -DCMAKE_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ -DCMAKE_CXX_FLAGS="-DMULTI_USE_HIP" -DCMAKE_HIP_FLAGS="-DMULTI_USE_HIP" -DENABLE_HIP=1 -DCMAKE_HIP_ARCHITECTURES=gfx90a # TODO(correaa) add CMAKE_BUILD_TYPE + - cmake --build . --parallel 2 || cmake --build . --verbose --parallel 1 + - ctest || ctest --rerun-failed --output-on-failure || echo "ctest failed, probably due to lack of hardware" +# needs: ["clang++", "g++"] + +circle: + stage: build + allow_failure: false + tags: + - non-shared + - x86_64 # for circle executable + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang cmake g++-11 gzip libboost-timer-dev libboost-serialization-dev libblas-dev libelf1 libfftw3-dev make pkg-config tar wget + - mkdir -p build_latest ; cd build_latest + - wget https://www.circle-lang.org/linux/build_202.tgz --no-verbose + - tar -zxvf build_*.tgz + - cd .. + - ./build_latest/circle --version + - mkdir build && cd build + - CXX=`pwd`/../build_latest/circle cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_CIRCLE=1 + - cmake --build . --parallel 4 || make VERBOSE=1 + - ctest -j 2 --output-on-failure + +circle-latest c++20: + stage: build + allow_failure: false + tags: + - non-shared + - x86_64 # for circle executable + interruptible: true + script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang cmake g++ gzip libboost-timer-dev libboost-serialization-dev libblas-dev libelf1 libfftw3-dev make pkg-config tar wget + - mkdir -p build_latest ; cd build_latest + - wget https://www.circle-lang.org/linux/build_latest.tgz --no-verbose + - tar -zxvf build_*.tgz + - cd .. + - ls + - ./build_latest/circle --version + - mkdir build && cd build + - CXX=`pwd`/../build_latest/circle cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_EXTENSIONS=OFF -DCMAKE_CXX_STANDARD=20 -DENABLE_CIRCLE=1 + - cmake --build . --parallel 2 || make VERBOSE=1 + - ctest -j 2 --output-on-failure +# needs: ["circle"] + +inq: + stage: test + # image: debian:stable + tags: + - non-shared + - large-memory-space + interruptible: true + script: + - export CMAKE_GENERATOR="Unix Makefiles" # "Ninja" + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y ca-certificates cmake g++ git gfortran libopenmpi-dev libblas-dev libboost-filesystem-dev libboost-iostreams-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev make ninja-build pkg-config python3-dev + - export PREFIX=`mktemp -d` + - git clone --recurse-submodules https://gitlab.com/npneq/inq.git # --branch update-multi-get + - cd inq + - git submodule update + - cd external_libs/multi + - git checkout $CI_COMMIT_SHA # check that multi repo is mirrored correctly + - cd ../.. + - mkdir build && cd build + - cmake .. --install-prefix=$PREFIX -DCMAKE_BUILD_TYPE=Release + - cmake --build . --parallel 4 || cmake --build . --parallel 1 + - apt-get -qq --no-install-recommends -y install python3-numpy + - cmake --install . + - export OMPI_ALLOW_RUN_AS_ROOT=1 + - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + - export OMPI_MCA_btl_vader_single_copy_mechanism=none + - export OMPI_MCA_rmaps_base_oversubscribe=1 + - ctest -j 2 --output-on-failure --timeout 600 + - INQ_EXEC_ENV="mpirun --oversubscribe -n 4" ctest --output-on-failure --timeout 600 + needs: ["g++"] + +inq-debug: + stage: test + # image: debian:stable + tags: + - non-shared + - large-memory-space + interruptible: true + script: + - export CMAKE_GENERATOR="Unix Makefiles" # "Ninja" + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y ca-certificates cmake g++ git gfortran libopenmpi-dev libblas-dev libboost-filesystem-dev libboost-iostreams-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev make ninja-build pkg-config python3-dev + - export PREFIX=`mktemp -d` + - git clone --recurse-submodules https://gitlab.com/npneq/inq.git # --branch update-multi-get + - cd inq + - git submodule update + - cd external_libs/multi + - git checkout $CI_COMMIT_SHA # check that multi repo is mirrored correctly + - cd ../.. + - mkdir build && cd build + - cmake .. --install-prefix=$PREFIX -DCMAKE_BUILD_TYPE=Debug + - cmake --build . --parallel 4 || cmake --build . --parallel 1 + - apt-get -qq --no-install-recommends -y install python3-numpy + - cmake --install . + - export OMPI_ALLOW_RUN_AS_ROOT=1 + - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + - export OMPI_MCA_btl_vader_single_copy_mechanism=none + - export OMPI_MCA_rmaps_base_oversubscribe=1 + - ctest -j 2 --output-on-failure --timeout 600 + - INQ_EXEC_ENV="mpirun --oversubscribe -n 4" ctest --output-on-failure --timeout 600 + needs: ["g++"] + +inq cuda: + allow_failure: false + stage: test + image: nvcr.io/nvidia/cuda:12.0.1-devel-ubuntu22.04 # nvcr.io/nvidia/cuda:11.8.0-devel-ubuntu22.04 + tags: + - non-shared + - nvidia-gpu + interruptible: true + before_script: + - apt-get -qq update && apt-get -qq install --no-install-recommends -y ca-certificates cmake git gfortran libopenmpi-dev libblas-dev libboost-filesystem-dev libboost-iostreams-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev pkg-config python3-dev + script: + - export CMAKE_GENERATOR="Unix Makefiles" # "Ninja" + - nvidia-smi + # - export CUDA_VISIBLE_DEVICES=0,1 + - __nvcc_device_query + - export PREFIX=`mktemp -d` + - git clone --recurse-submodules https://gitlab.com/npneq/inq.git # --branch update-multi-get + - cd inq + - cd external_libs/multi + - git checkout $CI_COMMIT_SHA + - cd ../.. + - rm -f cmake/FindNCCL.cmake # disable NCCL workaround + - mkdir build && cd build + - /usr/local/cuda/bin/nvcc --version + - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. --install-prefix=$PREFIX -DENABLE_CUDA=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_ARCHITECTURES=61 # =75 # =80 + - cmake --build . --parallel 4 || cmake --build . --parallel 1 + - apt-get -qq --no-install-recommends -y install python3-numpy + - cmake --install . + - export OMPI_ALLOW_RUN_AS_ROOT=1 + - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + - export OMPI_MCA_btl_vader_single_copy_mechanism=none + - export OMPI_MCA_rmaps_base_oversubscribe=1 + - ctest -j 4 --output-on-failure --timeout 2400 + - INQ_EXEC_ENV="mpirun --oversubscribe -n 4" ctest --output-on-failure -j 2 --timeout 2400 + timeout: 2 hours 30 minutes + needs: ["inq", "g++"] # "cuda", "inq"] + +inq rocm: + stage: build # test + # image: rocm/dev-ubuntu-22.04 + image: rocm/dev-ubuntu-24.04 + allow_failure: false + tags: + - non-shared + - large-disk-space + - x86_64 # for image + interruptible: true + script: + - export CMAKE_GENERATOR="Unix Makefiles" # "Ninja" + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y ca-certificates cmake git gfortran gpg hipblas-dev hipfft-dev libopenmpi-dev libblas-dev libboost-filesystem-dev libboost-iostreams-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev make pkg-config python3-dev rocthrust-dev rocm-device-libs wget + - /opt/rocm/bin/hipconfig --full + - HIPCC_VERBOSE=1 /opt/rocm/bin/hipcc --version + - cmake --version + - wget https://github.com/Kitware/CMake/releases/download/v3.27.0-rc3/cmake-3.27.0-rc3-linux-x86_64.sh --no-verbose + - sh ./cmake-3.27.0-rc3-linux-x86_64.sh --skip-license --prefix=/usr + - cmake --version + - export PREFIX=`mktemp -d` + - git clone --recurse-submodules https://gitlab.com/npneq/inq.git # --branch update-multi-get + - cd inq + - cd external_libs/multi + - git checkout $CI_COMMIT_SHA + - cd ../.. + - mkdir build && cd build + - export PATH=/opt/rocm/bin:/opt/rocm/llvm/bin:${PATH} + - export CMAKE_PREFIX_PATH=/opt/rocm:${CMAKE_PREFIX_PATH} + - export ROCM_PATH=/opt/rocm + - export HIP_PATH=/opt/rocm + - cmake .. -DCMAKE_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ --install-prefix=$PREFIX -DENABLE_HIP=1 -DCMAKE_HIP_ARCHITECTURES=gfx90a + - cmake --build . --parallel 4 || cmake --build . --parallel 1 + - cmake --install . + - export OMPI_ALLOW_RUN_AS_ROOT=1 + - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + - export OMPI_MCA_btl_vader_single_copy_mechanism=none + - export OMPI_MCA_rmaps_base_oversubscribe=1 + - INQ_EXEC_ENV="mpirun --oversubscribe -n 4" ctest --output-on-failure --timeout 2400 || echo "ctest failed, probably due to lack of hardware" + timeout: 2 hours 30 minutes +# needs: ["rocm", "inq"] + +qmcpack: + stage: test + image: debian:latest + tags: + - non-shared + - docker + interruptible: true + before_script: + - apt-get -qq update + - apt-get -qq install --no-install-recommends -y ca-certificates cmake g++ git gfortran libblas-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev libopenmpi-dev make numdiff pkg-config python3 python3-h5py python3-numpy python3-mpi4py python3-scipy libxml2-dev + script: + - git clone --depth=1 https://github.com/QMCPACK/qmcpack.git + # - git clone https://github.com/correaa/qmcpack.git --branch complete-pointer-concept-for-shm_ptr + - cd qmcpack + - git config --global user.email "alfredo.correa@gmail.com" && git config --global user.name "Alfredo Correa" + - git rm -r external_codes/boost_multi/multi && git commit -m "remove multi subtree" + - git subtree add --squash -P external_codes/boost_multi/multi $CI_REPOSITORY_URL $CI_COMMIT_SHA || git subtree add --squash -P external_codes/boost_multi/multi $CI_REPOSITORY_URL # e.g. https://gitlab.com/correaa/boost-multi.git + - cd build + - cmake -DCMAKE_C_COMPILER=mpicc -DCMAKE_CXX_COMPILER=mpicxx -DBUILD_AFQMC=1 -DQMC_MIXED_PRECISION=1 -DCMAKE_BUILD_TYPE=Debug -DMPIEXEC_PREFLAGS="--allow-run-as-root;--bind-to;none" .. + - make ppconvert afqmc test_afqmc_matrix test_afqmc_numerics test_afqmc_slaterdeterminantoperations test_afqmc_walkers test_afqmc_hamiltonians test_afqmc_hamiltonian_operations test_afqmc_phmsd test_afqmc_wfn_factory test_afqmc_prop_factory test_afqmc_estimators qmc-afqmc-performance + - ctest -R ppconvert --output-on-failure + - ctest -R afqmc --output-on-failure + needs: ["g++"] + +# github: +# stage: basic +# image: debian:latest +# tags: +# - non-shared +# - docker +# interruptible: true +# before_script: +# - apt-get -qq update && apt-get -qq install --no-install-recommends -y ca-certificates # gnupg cmake g++ git gfortran libblas-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev libopenmpi-dev make numdiff pkg-config python3 python3-h5py python3-numpy python3-mpi4py python3-scipy libxml2-dev +# - | +# (type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \ +# && sudo mkdir -p -m 755 /etc/apt/keyrings \ +# && out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \ +# && cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ +# && sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ +# && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ +# - apt update +# - apt install gh -y +# script: +# - gh --version +# # needs: ["g++"] + +qmcpack cuda-12.3.1: + stage: test + image: + name: nvcr.io/nvidia/cuda:12.9.0-devel-ubuntu24.04 # nvcr.io/nvidia/cuda:12.3.1-devel-ubuntu24.04 + tags: + - non-shared + - nvidia-gpu + - high-bandwidth + interruptible: true + allow_failure: true + before_script: + - apt-get -qq update && apt-get -qq install --no-install-recommends -y ca-certificates cmake git libopenmpi-dev cmake g++ git gfortran libblas-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev libopenmpi-dev make numdiff pkg-config python3 python3-h5py python3-numpy python3-mpi4py python3-scipy rsync libxml2-dev + script: + - nvidia-smi + - rsync --version + - git clone --depth 1 https://github.com/QMCPACK/qmcpack.git + # - git clone https://github.com/correaa/qmcpack.git --branch complete-pointer-concept-for-shm_ptr + - cd qmcpack + - git config --global user.email "alfredo.correa@gmail.com" && git config --global user.name "Alfredo Correa" + - git rm -r external_codes/boost_multi/multi && git commit -m "remove multi subtree" + - git subtree add --squash -P external_codes/boost_multi/multi $CI_REPOSITORY_URL $CI_COMMIT_SHA # e.g. https://gitlab.com/correaa/boost-multi.git + - cd build + - nvcc --version + - __nvcc_device_query + - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=mpicc -DCMAKE_CXX_COMPILER=mpicxx -DBUILD_AFQMC=1 -DQMC_CXX_STANDARD=17 -DQMC_GPU=cuda -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc -DCMAKE_CUDA_HOST_COMPILER=g++ -DCMAKE_CXX_FLAGS="-Wno-deprecated -Wno-deprecated-declarations" -DCMAKE_CUDA_ARCHITECTURES=native # =80 + - VERBOSE=1 make -j 4 ppconvert afqmc test_afqmc_matrix test_afqmc_numerics test_afqmc_slaterdeterminantoperations test_afqmc_walkers test_afqmc_hamiltonians test_afqmc_hamiltonian_operations test_afqmc_phmsd test_afqmc_wfn_factory test_afqmc_prop_factory test_afqmc_estimators qmc-afqmc-performance + - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest -R ppconvert --output-on-failure + - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest -R afqmc --output-on-failure + needs: ["qmcpack","cuda"] + +# sonar cloud +# from instructions +# another example here: https://github.com/sonarsource-cfamily-examples/linux-cmake-gitlab-ci-sc/blob/main/.gitlab-ci.yml + +sonar: + stage: build + allow_failure: true + only: + refs: + - master + tags: + - non-shared + - docker + - x86_64 # for executable + interruptible: true + script: + # Run the build inside the build wrapper + - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake curl unzip g++ gcovr make lcov libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config wget + - g++ --version + - mkdir build + # Download sonar-scanner, from https://docs.sonarsource.com/sonarqube-cloud/advanced-setup/ci-based-analysis/sonarscanner-cli/ + - wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-6.2.1.4610-linux-x64.zip -O sonar-scanner.zip + - unzip -o sonar-scanner.zip + - mv sonar-scanner-6.2.1.4610-linux-x64 sonar-scanner + # - Download build-wrapper + - curl -sSLo ./build-wrapper-linux-x86.zip "${SONAR_HOST_URL}/static/cpp/build-wrapper-linux-x86.zip" + - unzip -oj build-wrapper-linux-x86.zip -d ./build-wrapper + - cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_FLAGS="--coverage -O0 -fno-inline -fno-inline-small-functions -fno-default-inline" -DCMAKE_EXE_LINKER_FLAGS="--coverage" + - build-wrapper/build-wrapper-linux-x86-64 --out-dir bw-output cmake --build build/ --verbose + - cd build + - ctest -j 1 --output-on-failure -T Test + - ctest -j 1 --output-on-failure -T Coverage + - gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml --root ${CI_PROJECT_DIR} + - cd .. + - sonar-scanner/bin/sonar-scanner -Dsonar.host.url="${SONAR_HOST_URL}" -Dsonar.token="${SONAR_TOKEN}" -Dsonar.python.version=3.12.3 -Dsonar.cfamily.compile-commands=build/compile_commands.json `#-Dsonar.cfamily.build-wrapper-output=bw-output` -Dsonar.cfamily.gcov.reportsPath="build/" + needs: ["g++"] + +pages: + stage: basic + allow_failure: true + only: + refs: + - master + tags: + - non-shared + - docker + script: + - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates asciidoctor pandoc ruby-rouge pip python3-venv + - pandoc --version + - asciidoctor --version + - mkdir .public + - cd doc + # - pandoc --highlight-style pygments --to html5 -f markdown ../README.md > multi.html + # - pandoc --to asciidoc --from markdown ../README.md > multi.adoc + - asciidoctor --failure-level=WARN -a source-highlighter=rouge -b xhtml5 multi.adoc + - cd .. + - cp -r doc/multi.html .public + # - cp -r doc/multi.adoc .public + - python3 -m venv .venv + - source .venv/bin/activate + - pip install quom + - quom include/boost/multi/array.hpp boost-multi.hpp + - cp -r include/boost/multi/array.hpp .public/boost-multi.html + - cp -r boost-multi.hpp .public/boost-multi.hpp + # - cp -r include/boost/multi/array.hpp .public/boost-multi.hpp + - mv .public public + artifacts: + paths: + - public + # check for file here: https://correaa.gitlab.io/multi/multi.html + +headers: + stage: basic + allow_failure: true + #only: + # refs: + # - master + tags: + - non-shared + - docker + script: + - apt-get -qq update && apt-get -qq install pip python3-venv + - mkdir .public + - cp include/boost/multi/array.hpp .public/boost-multi.txt + - python3 -m venv .venv + - source .venv/bin/activate + - pip install quom + - mv .public public + artifacts: + paths: + - public diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 007ef7ee7..e5d4765b9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,537 +1,63 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;-*- -# Copyright 2020-2023 Alfredo A. Correa - -# Install docker (for local CI or to set up CI machine) -# sudo apt install docker.io -# Install gitlab-runner -# curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash -# sudo apt-get install gitlab-runner - -# image: corraadock/gnudev:v2 -image: debian:stable - -variables: - GIT_SUBMODULE_STRATEGY: recursive - CODECOV_TOKEN: "999feb5b-a599-4d02-b9c5-46d977247f3a" - RT_VERSION: "0.1" - -# OLDGNUSANICHECKCOV: -# stage: build -# image: debian:stable -# script: -# - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake cppcheck curl g++ gcovr git make lcov libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config wget -# - mkdir build && cd build -# - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;--suppress=missingIncludeSystem;--inline-suppr;--std=c++17;--check-config;--error-exitcode=1" -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1 -fsanitize=address,undefined,pointer-compare,pointer-subtract,float-divide-by-zero -fno-sanitize-recover=all --coverage -fno-inline -fno-inline-small-functions -fno-default-inline" -DCMAKE_EXE_LINKER_FLAGS="--coverage" -# - cmake --build . --parallel 2 || cmake --build . --verbose -# - ASAN_OPTIONS="new_delete_type_mismatch=0:check_initialization_order=true:strict_init_order=true:detect_stack_use_after_return=true:detect_odr_violation=1" ctest -j 2 --output-on-failure -T Test -# - gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml --root ${CI_PROJECT_DIR} -# - lcov --directory . --capture --output-file coverage.info -# - lcov --remove coverage.info '/usr/*' --output-file coverage.info -# - lcov --list coverage.info && genhtml coverage.info -# - bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' -# coverage: /^\s*lines:\s*\d+.\d+\%/ -# artifacts: -# name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} -# expire_in: 2 days -# reports: -# coverage_report: -# coverage_format: cobertura -# path: build/coverage.xml - -g++: - stage: build - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake g++ make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config - - g++ --version - - mkdir build && cd build - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1 -D_LIBCPP_DEBUG=1" - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure -T Test - -coverage: - stage: build - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake curl g++ gcovr git make lcov libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config wget - - mkdir build && cd build - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-DNDEBUG --coverage -O0 -fno-inline -fno-inline-small-functions -fno-default-inline" -DCMAKE_EXE_LINKER_FLAGS="--coverage" - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure -T Test - - ctest -j 2 --output-on-failure -T Coverage - - gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml --root ${CI_PROJECT_DIR} - # - lcov --directory . --capture --output-file coverage.info - # - lcov --remove coverage.info '/usr/*' --output-file coverage.info - # - lcov --list coverage.info && genhtml coverage.info - - bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' - coverage: /^\s*Percentage\s+Coverage:\s*\d+.\d+\%/ - # coverage: /^\s*lines:\s*\d+.\d+\%/ - artifacts: - name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} - expire_in: 2 days - reports: - coverage_report: - coverage_format: cobertura - path: build/coverage.xml - needs: ["g++"] - -g++ cppcheck: - stage: build - image: debian:stable - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake cppcheck g++ make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config - - g++ --version - - mkdir build && cd build - - cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;--suppress=missingIncludeSystem;--inline-suppr;--std=c++17;--check-config;--error-exitcode=1" - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure -T Test - needs: ["g++"] - -g++ sanitizer: - stage: build - image: debian:stable - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake g++ make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config - - mkdir build && cd build - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined,pointer-compare,pointer-subtract,float-divide-by-zero -fno-sanitize-recover=all" - - cmake --build . --parallel 2 || cmake --build . --verbose - - ASAN_OPTIONS="new_delete_type_mismatch=0:check_initialization_order=true:strict_init_order=true:detect_stack_use_after_return=true:detect_odr_violation=1" ctest -j 2 --output-on-failure -T Test - needs: ["g++"] - -g++-7: - stage: build - image: debian:oldoldstable - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake g++-7 make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config wget - - wget https://github.com/Kitware/CMake/releases/download/v3.27.0-rc3/cmake-3.27.0-rc3-linux-x86_64.sh --no-verbose - - sh ./cmake-3.27.0-rc3-linux-x86_64.sh --skip-license --prefix=/usr - - cmake --version - - g++-7 --version - - mkdir build && cd build - - CXX=g++-7 CXXFLAGS="-DBOOST_TEST_MAIN -DBOOST_TEST_DYN_LINK" cmake .. -DCMAKE_BUILD_TYPE=Debug - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure -T Test - needs: ["g++"] - -g++-13 c++20: - stage: build - image: debian:testing - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet g++-13 pkg-config cmake make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev valgrind - - mkdir build && cd build - - CXX=g++-13 cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 -DCMAKE_CXX_FLAGS="-fanalyzer -Wno-analyzer-null-dereference -Wno-analyzer-possible-null-dereference -Wno-analyzer-malloc-leak -Wno-analyzer-use-of-uninitialized-value -Wno-analyzer-use-after-free" - - cmake --build . --parallel 2 || cmake --build . --verbose - - ulimit -n - - ulimit -n 1024 # neededed by valgrind in docker running in Fedora https://stackoverflow.com/questions/75292406/memchk-valgrind-reporting-inconsistent-results-in-different-docker-hosts?noredirect=1#comment132859702_75292406 - - ctest -j 1 --output-on-failure --overwrite MemoryCheckCommandOptions="-q --tool=memcheck --leak-check=yes --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all" -T memcheck - needs: ["g++"] - -clang++: - stage: build - image: debian:stable - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet clang cmake make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config - - mkdir build && cd build - - clang++ --version - - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1 -D_LIBCPP_DEBUG=1" - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure - -clang++ tidy: - stage: build - image: debian:stable - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet clang clang-tidy cmake make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config - - mkdir build && cd build - - clang-tidy --version - - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_CLANG_TIDY=clang-tidy - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure - needs: ["clang++"] - -clang++ cpplint: - stage: build - image: debian:stable - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet clang cmake cpplint libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev make pkg-config - - mkdir build && cd build - - clang++ --version - - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_CPPLINT="cpplint;--quiet" - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure - needs: ["clang++"] - -clang++ sani: - stage: build - image: debian:testing - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet clang-13 cmake make libllvm14 libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config - - mkdir build && cd build - - clang++-13 --version - - CXX=clang++-13 cmake .. -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined,pointer-compare,pointer-subtract,float-divide-by-zero -fno-sanitize-recover=all" - - cmake --build . --parallel 2 || cmake --build . --verbose - - export ASAN_OPTIONS="new_delete_type_mismatch=0:check_initialization_order=true:strict_init_order=true:detect_stack_use_after_return=true:detect_odr_violation=1" - - ctest -j 2 --output-on-failure - needs: ["clang++"] - -clang++-15 c++20: - stage: build - image: debian:testing - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet clang-15 cmake make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config - - mkdir build && cd build - - clang++-15 --version - - CXX=clang++-15 cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure - needs: ["clang++"] - -clang++-7: - stage: build - image: debian:oldoldstable - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang-7 cmake make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config wget - - wget https://github.com/Kitware/CMake/releases/download/v3.27.0-rc3/cmake-3.27.0-rc3-linux-x86_64.sh --no-verbose - - sh ./cmake-3.27.0-rc3-linux-x86_64.sh --skip-license --prefix=/usr - - mkdir build && cd build - - clang++-7 --version - - CXX=clang++-7 CXXFLAGS="-DBOOST_TEST_DYN_LINK" cmake .. -DCMAKE_BUILD_TYPE=Release - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure - needs: ["clang++"] - - -# OLDCLANGTIDYSANI: -# stage: build -# image: debian:testing -# script: -# - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet cmake clang-13 clang-tidy-14 make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config -# - mkdir build && cd build -# - clang++-13 --version -# - clang-tidy-14 --version -# - CXX=clang++-13 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CLANG_TIDY="clang-tidy-14" -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1 -D_LIBCPP_DEBUG=1 -fsanitize=address,undefined,pointer-compare,pointer-subtract,float-divide-by-zero -fno-sanitize-recover=all" -# - cmake --build . --parallel 2 || cmake --build . --verbose -# - ASAN_OPTIONS="new_delete_type_mismatch=0:check_initialization_order=true:strict_init_order=true:detect_stack_use_after_return=true:detect_odr_violation=1" ctest -j 2 --output-on-failure - -# NEWCLANGSTD20LINT: -# stage: build -# image: debian:testing -# script: -# - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet cmake clang make libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev pkg-config cpplint -# - cpplint --recursive include/multi/array.hpp -# - mkdir build && cd build -# - clang++ --version -# - CXX=clang++-14 cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 -DCMAKE_CXX_CPPLINT="cpplint;--quiet" -# - cmake --build . --parallel 2 || cmake --build . --verbose -# - ctest -j 2 --output-on-failure -# needs: ["clang++"] - -oneapi: - stage: build - image: intel/oneapi-hpckit:2023.0.0-devel-ubuntu22.04 - script: - - apt-get update && apt-get install --no-install-recommends -y --quiet ca-certificates cmake curl libboost-test-dev libboost-serialization-dev libboost-timer-dev libblas-dev liblapack-dev libfftw3-dev make pkg-config - - mkdir build && cd build - - icpx --version - - CXX=icpx cmake .. -DCMAKE_BUILD_TYPE=Release - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest --parallel 2 --output-on-failure - -oneapi-latest c++20: - stage: build - image: intel/oneapi-hpckit:latest - script: - - apt-get update && apt-get install --no-install-recommends -y --quiet ca-certificates cmake curl g++ libboost-test-dev libboost-serialization-dev libboost-timer-dev libblas-dev liblapack-dev libfftw3-dev make pkg-config - - mkdir build && cd build - - icpx --version - - CXX=icpx cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest --parallel 2 --output-on-failure - needs: ["oneapi"] - -oneapi-2022.2: - stage: build - image: intel/oneapi-hpckit:2022.2-devel-ubuntu20.04 - script: - - apt-get update && apt-get install --no-install-recommends -y --quiet ca-certificates cmake curl g++ libboost-test-dev libboost-serialization-dev libboost-timer-dev libblas-dev liblapack-dev libfftw3-dev make pkg-config - - mkdir build && cd build - - icpx --version - - CXX=icpx cmake .. -DCMAKE_BUILD_TYPE=Release - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest --parallel 2 --output-on-failure - needs: ["oneapi"] - -nvhpc: - stage: build - image: nvcr.io/nvidia/nvhpc:22.11-devel-cuda_multi-ubuntu22.04 - script: - - nvidia-smi - - apt-get update && apt-get install --no-install-recommends -y cmake make libboost-test-dev libboost-serialization-dev - - /opt/nvidia/hpc_sdk/Linux_x86_64/2022/compilers/bin/nvc++ --version - - mkdir build && cd build - - CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2022/compilers/bin/nvc++ cmake .. - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest --output-on-failure - -nvhpc-22.7: - stage: build - image: nvcr.io/nvidia/nvhpc:22.7-devel-cuda_multi-ubuntu22.04 - script: - - nvidia-smi - - apt-get update && apt-get install --no-install-recommends -y cmake make libboost-test-dev libboost-serialization-dev - - /opt/nvidia/hpc_sdk/Linux_x86_64/2022/compilers/bin/nvc++ --version - - mkdir build && cd build - - CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2022/compilers/bin/nvc++ cmake .. - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest --output-on-failure - needs: ["nvhpc"] - -nvhpc-23.5 c++20: - stage: build - image: nvcr.io/nvidia/nvhpc:23.5-devel-cuda_multi-ubuntu22.04 - script: - - nvidia-smi - - apt-get update && apt-get install --no-install-recommends -y cmake make libboost-test-dev libboost-serialization-dev - - /opt/nvidia/hpc_sdk/Linux_x86_64/2023/compilers/bin/nvc++ --version - - mkdir build && cd build - - CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2023/compilers/bin/nvc++ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest --output-on-failure - needs: ["nvhpc"] - -cuda: - stage: build - image: nvcr.io/nvidia/cuda:11.8.0-devel-ubuntu22.04 - tags: - - nvidia-docker - script: - - nvidia-smi - - apt-get -qq update && apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-test-dev libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev - - mkdir build && cd build - - ls /usr/local - - ls /usr/local/cuda-11/bin - - /usr/local/cuda-11/bin/nvcc --version - - CUDACXX=/usr/local/cuda-11/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES=61 - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure # doesn't run - - ../build/include/multi/adaptors/thrust/test/speed.cu.x - - ../build/include/multi/adaptors/thrust/test/speed_algo.cu.x - - ../build/include/multi/adaptors/fftw/test/combinations.cpp.x - needs: ["g++"] - -cuda-12.2.0: # last version: httpss://gitlab.com/nvidia/container-images/cuda/-/blob/master/doc/container_tags.pdf - stage: build - image: nvcr.io/nvidia/cuda:12.2.0-devel-ubuntu22.04 - allow_failure: true # 12.2 image not working at the moment - tags: - - nvidia-docker - script: - - nvidia-smi - - apt-get -qq update && apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-test-dev libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev - - mkdir build && cd build - - /usr/local/cuda/bin/nvcc --version - - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES=61 - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure - needs: ["cuda"] - -cuda-12.1.1: # last version: httpss://gitlab.com/nvidia/container-images/cuda/-/blob/master/doc/container_tags.pdf - stage: build - image: nvcr.io/nvidia/cuda:12.1.1-devel-ubuntu22.04 - tags: - - nvidia-docker - script: - - nvidia-smi - - apt-get -qq update && apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-test-dev libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev - - mkdir build && cd build - - /usr/local/cuda/bin/nvcc --version - - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES=61 - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure - needs: ["cuda"] - -cuda-11.4.3: - stage: build - image: nvcr.io/nvidia/cuda:11.4.3-devel-ubuntu20.04 - tags: - - nvidia-docker - script: - - nvidia-smi - - apt-get -qq update - - DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-test-dev libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev - - mkdir build && cd build - - /usr/local/cuda/bin/nvcc --version - - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES=61 - - cmake --build . --parallel 2 || cmake --build . --verbose - - ctest -j 2 --output-on-failure - needs: ["cuda"] - -rocm: - stage: build - image: rocm/dev-ubuntu-22.04 - allow_failure: true - tags: - - nvidia-docker - script: - - apt-get -qq update - - apt-get install --no-install-recommends -y cmake wget pkg-config make libboost-test-dev libboost-serialization-dev libboost-timer-dev libblas-dev libfftw3-dev wget gpg - - apt-get install --no-install-recommends -y rocthrust-dev hipblas-dev hipfft-dev rocm-device-libs - - /opt/rocm/bin/hipconfig --full - - HIPCC_VERBOSE=1 /opt/rocm/bin/hipcc --version - - cmake --version - - wget https://github.com/Kitware/CMake/releases/download/v3.27.0-rc3/cmake-3.27.0-rc3-linux-x86_64.sh --no-verbose - - sh ./cmake-3.27.0-rc3-linux-x86_64.sh --skip-license --prefix=/usr - - cmake --version - - mkdir build && cd build - - export PATH=/opt/rocm/bin:/opt/rocm/llvm/bin:${PATH} - - export CMAKE_PREFIX_PATH=/opt/rocm:${CMAKE_PREFIX_PATH} - - export ROCM_PATH=/opt/rocm - - export HIP_PATH=/opt/rocm - - cmake .. -DENABLE_HIP=1 -DCMAKE_HIP_ARCHITECTURES=gfx90a - - cmake --build . --parallel 2 || cmake --build . --verbose --parallel 1 - - ctest --output-on-failure - needs: ["clang++", "g++"] - -circle: - stage: build - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang cmake g++-11 gzip libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libelf1 libfftw3-dev make pkg-config tar wget - - mkdir -p build_latest ; cd build_latest - - wget https://www.circle-lang.org/linux/build_198.tgz --no-verbose - - tar -zxvf build_???.tgz - - cd .. - - ls - - ./build_latest/circle --version - - mkdir build && cd build - - CXX=`pwd`/../build_latest/circle cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_CIRCLE=1 - - cmake --build . --parallel 2 || make VERBOSE=1 - - ctest -j 2 --output-on-failure - -circle-187: - stage: build - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang cmake g++-11 gzip libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libelf1 libfftw3-dev make pkg-config tar wget - - mkdir -p build_latest ; cd build_latest - - wget https://www.circle-lang.org/linux/build_187.tgz --no-verbose - - tar -zxvf build_???.tgz - - cd .. - - ls - - ./build_latest/circle --version - - mkdir build && cd build - - CXX=`pwd`/../build_latest/circle cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_CIRCLE=1 - - cmake --build . --parallel 2 || make VERBOSE=1 - - ctest -j 2 --output-on-failure - needs: ["circle"] - -circle-latest: - stage: build - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang cmake g++-11 gzip libboost-test-dev libboost-timer-dev libboost-serialization-dev libblas-dev libelf1 libfftw3-dev make pkg-config tar wget - - mkdir -p build_latest ; cd build_latest - - wget https://www.circle-lang.org/linux/build_latest.tgz --no-verbose - - tar -zxvf build_*.tgz - - cd .. - - ls - - ./build_latest/circle --version - - mkdir build && cd build - - CXX=`pwd`/../build_latest/circle cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_CIRCLE=1 - - cmake --build . --parallel 2 || make VERBOSE=1 - - ctest -j 2 --output-on-failure - needs: ["circle"] - -inq: - allow_failure: false - stage: test - image: debian:stable - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y ca-certificates cmake g++ git gfortran libopenmpi-dev libblas-dev libboost-filesystem-dev libboost-iostreams-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev make pkg-config - - export PREFIX=`mktemp -d` - - git clone --recurse-submodules https://gitlab.com/npneq/inq.git - - cd inq - - git submodule update - - cd external_libs/multi - - git checkout $CI_COMMIT_SHA # check that multi repo is mirrored correctly - - cd ../.. - - mkdir build && cd build - - cmake .. --install-prefix=$PREFIX -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_ARCHITECTURES=61 - - cmake --build . --parallel 4 || cmake --build . --parallel 1 - - cmake --install . - - export OMPI_ALLOW_RUN_AS_ROOT=1 - - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 - - export OMPI_MCA_btl_vader_single_copy_mechanism=none - - export OMPI_MCA_rmaps_base_oversubscribe=1 - - ctest -j 2 --output-on-failure --timeout 600 - - INQ_EXEC_ENV="mpirun --oversubscribe -n 4" ctest --output-on-failure --timeout 600 - needs: ["g++"] - -inq cuda: - allow_failure: false - stage: test - image: nvcr.io/nvidia/cuda:11.8.0-devel-ubuntu22.04 - tags: - - nvidia-docker - variables: - GIT_STRATEGY: none - script: - - nvidia-smi - - apt-get -qq update && apt-get -qq install --no-install-recommends -y ca-certificates cmake git gfortran libopenmpi-dev libblas-dev libboost-filesystem-dev libboost-iostreams-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev pkg-config - - export PREFIX=`mktemp -d` - - git clone --recurse-submodules https://gitlab.com/npneq/inq.git - - cd inq - - cd external_libs/multi - - git checkout $CI_COMMIT_SHA - - cd ../.. - - rm -f cmake/FindNCCL.cmake # diable NCCL workaround - - mkdir build && cd build - - /usr/local/cuda/bin/nvcc --version - - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. --install-prefix=$PREFIX -DENABLE_CUDA=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_ARCHITECTURES=61 - - cmake --build . --parallel 4 || cmake --build . --parallel 1 - - cmake --install . - - export OMPI_ALLOW_RUN_AS_ROOT=1 - - export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 - - export OMPI_MCA_btl_vader_single_copy_mechanism=none - - export OMPI_MCA_rmaps_base_oversubscribe=1 - - ctest -j 2 --output-on-failure --timeout 2400 - - INQ_EXEC_ENV="mpirun --oversubscribe -n 4" ctest --output-on-failure --timeout 2400 - timeout: 2 hours 30 minutes - needs: ["cuda", "inq"] - -qmcpack: - image: debian:testing - stage: test - variables: - GIT_STRATEGY: none - script: - - apt-get -qq update && apt-get -qq install --no-install-recommends -y ca-certificates cmake g++ git gfortran libblas-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev libopenmpi-dev make numdiff pkg-config python3 python3-h5py python3-numpy python3-mpi4py python3-scipy libxml2-dev - - git clone --depth=1 https://github.com/QMCPACK/qmcpack.git - - cd qmcpack - - git config --global user.email "alfredo.correa@gmail.com" && git config --global user.name "Alfredo Correa" - - git rm -r external_codes/boost_multi/multi && git commit -m "remove multi subtree" - - git subtree add --squash -P external_codes/boost_multi/multi $CI_REPOSITORY_URL $CI_COMMIT_SHA || git subtree add --squash -P external_codes/boost_multi/multi $CI_REPOSITORY_URL # e.g. https://gitlab.com/correaa/boost-multi.git - - cd build - - cmake -DCMAKE_C_COMPILER=mpicc -DCMAKE_CXX_COMPILER=mpicxx -DBUILD_AFQMC=1 -DQMC_MIXED_PRECISION=1 -DCMAKE_BUILD_TYPE=Debug -DMPIEXEC_PREFLAGS="--allow-run-as-root;--bind-to;none" .. - - make ppconvert afqmc test_afqmc_matrix test_afqmc_numerics test_afqmc_slaterdeterminantoperations test_afqmc_walkers test_afqmc_hamiltonians test_afqmc_hamiltonian_operations test_afqmc_phmsd test_afqmc_wfn_factory test_afqmc_prop_factory test_afqmc_estimators qmc-afqmc-performance - - ctest -R ppconvert --output-on-failure - - ctest -R afqmc --output-on-failure - needs: ["g++"] - -qmcpack-cuda: - stage: test - image: nvcr.io/nvidia/cuda:11.8.0-devel-ubuntu22.04 - tags: - - nvidia-docker - variables: - GIT_STRATEGY: none - script: - - nvidia-smi - - apt-get -qq update && apt-get -qq install --no-install-recommends -y ca-certificates cmake git libopenmpi-dev cmake g++ git gfortran libblas-dev libboost-serialization-dev libfftw3-dev libhdf5-dev liblapack-dev libopenmpi-dev make numdiff pkg-config python3 python3-h5py python3-numpy python3-mpi4py python3-scipy libxml2-dev - - git clone --depth 1 https://github.com/QMCPACK/qmcpack.git - - cd qmcpack - - git config --global user.email "alfredo.correa@gmail.com" && git config --global user.name "Alfredo Correa" - - git rm -r external_codes/boost_multi/multi && git commit -m "remove multi subtree" - - git subtree add --squash -P external_codes/boost_multi/multi $CI_REPOSITORY_URL $CI_COMMIT_SHA # e.g. https://gitlab.com/correaa/boost-multi.git - - cd build - - CUDACXX=/usr/local/cuda/bin/nvcc cmake .. -DCMAKE_C_COMPILER=mpicc -DCMAKE_CXX_COMPILER=mpicxx -DBUILD_AFQMC=1 -DQMC_CXX_STANDARD=17 -DENABLE_CUDA=1 -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc -DCMAKE_CUDA_HOST_COMPILER=g++ -DCMAKE_CXX_FLAGS="-Wno-deprecated -Wno-deprecated-declarations" -DCMAKE_CUDA_ARCHITECTURES=61 - - make -j 4 ppconvert afqmc test_afqmc_matrix test_afqmc_numerics test_afqmc_slaterdeterminantoperations test_afqmc_walkers test_afqmc_hamiltonians test_afqmc_hamiltonian_operations test_afqmc_phmsd test_afqmc_wfn_factory test_afqmc_prop_factory test_afqmc_estimators qmc-afqmc-performance - - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest -R ppconvert --output-on-failure - - OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 ctest -R afqmc --output-on-failure - needs: ["qmcpack","cuda"] - +# # Copyright 2020-2025 Alfredo A. Correa + +image: debian:stable + +workflow: + auto_cancel: + on_new_commit: interruptible + rules: + - if: $CI_COMMIT_TAG + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + +variables: + GIT_SUBMODULE_STRATEGY: recursive + CODECOV_TOKEN: "999feb5b-a599-4d02-b9c5-46d977247f3a" + RT_VERSION: "0.1" + NVIDIA_DISABLE_REQUIRE: 1 # disable nvidia driver check + SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache + GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task + +g++: # debian-stable: default is gcc 12.2.0 as of April 2024 + stage: build + interruptible: false + script: + - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates cmake g++ make libboost-timer-dev libboost-serialization-dev libblas-dev liblapack-dev libfftw3-dev pkg-config + - g++ --version + - mkdir build && cd build + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1" + - cmake --build . --parallel 2 || cmake --build . --verbose + - ctest -j 2 --output-on-failure + +clang++: + stage: build + # tags: + # - non-shared + interruptible: false + script: + - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet clang cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libfftw3-dev liblapack-dev pkg-config + - mkdir build && cd build + - clang++ --version + - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1" + - cmake --build . --parallel 2 || cmake --build . --parallel 1 --verbose + - ctest -j 2 --output-on-failure + except: + - gitlab-org + +clang++-latest libc++: + stage: build + #image: debian:latest + # tags: + # - non-shared + interruptible: true + script: + - apt-get -qq update && apt-get -qq install --no-install-recommends -y --quiet ca-certificates clang cmake make libboost-timer-dev libboost-serialization-dev libblas-dev libc++-dev libc++abi-dev libfftw3-dev liblapack-dev pkg-config tar wget + - mkdir build && cd build + - clang++ --version + - CXX=clang++ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-stdlib=libc++ -DBOOST_NO_CXX98_FUNCTION_BASE -D_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" + - cmake --build . --parallel 2 || cmake --build . --parallel 1 --verbose + - ctest -j 2 --output-on-failure + needs: ["clang++"] + except: + - gitlab-org + diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md new file mode 100644 index 000000000..fb5e88d10 --- /dev/null +++ b/.gitlab/merge_request_templates/default.md @@ -0,0 +1,3 @@ +[![cmakeworkflow](https://github.com/correaa/boost-multi/actions/workflows/cmake.yml/badge.svg?branch=%{source_branch})](https://github.com/correaa/boost-multi/actions?query=workflow%3ACMake+branch%3A%{source_branch}++) +[![ciworkflow](https://github.com/correaa/boost-multi/actions/workflows/ci.yml/badge.svg?branch=%{source_branch})](https://github.com/correaa/boost-multi/actions?query=workflow%3ACI+branch%3A%{source_branch}++) +[Create/view GitHub PR](https://github.com/correaa/boost-multi/compare/%{source_branch}) diff --git a/.iwyu-test.imp b/.iwyu-test.imp new file mode 100644 index 000000000..c716dc213 --- /dev/null +++ b/.iwyu-test.imp @@ -0,0 +1,65 @@ +[ + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "private", "", "public"]}, + { "include": ["@", "public", "", "public"]}, + { "include": ["@<__assert>", "private", "", "public"] }, + { "include": ["@<__fwd/ostream.h>", "private", "", "public"] }, + { "include": ["@<__fwd/sstream.h>", "private", "", "public"] }, + { "include": ["@", "public", "", "public"] }, + { "include": ["@", "private", "", "public"] }, + { "include": ["@<__bit_reference>", "private", "", "public"] }, + { "include": ["@<__fwd/string_view.h>", "private", "", "public"] }, + { "include": ["@<__ostream/basic_ostream.h>", "private", "", "public"] }, + { "include": ["@<__config>", "private", "", "public"] } +] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..71849fbf9 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,32 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "19" + # rust: "1.64" + # golang: "1.19" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/.valgrind-suppressions b/.valgrind-suppressions index 75230f7d0..c0b8fe16c 100644 --- a/.valgrind-suppressions +++ b/.valgrind-suppressions @@ -13,3 +13,648 @@ fun:cudaMalloc* ... } +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + obj:/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.* + obj:/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.* + obj:/usr/lib/x86_64-linux-gnu/libOpenCL.so.1.0.0 + fun:clGetPlatformIDs + obj:/usr/lib/x86_64-linux-gnu/hwloc/hwloc_opencl.so + obj:/usr/lib/x86_64-linux-gnu/libhwloc.so.* + fun:hwloc_topology_load + fun:opal_hwloc_base_get_topology + obj:* + fun:orte_init + fun:orte_submit_init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + obj:/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.535.183.01 + ... + obj:/usr/lib/x86_64-linux-gnu/libOpenCL.so.1.0.0 + fun:clGetPlatformIDs + obj:/usr/lib/x86_64-linux-gnu/hwloc/hwloc_opencl.so + obj:/usr/lib/x86_64-linux-gnu/libhwloc.so.15.7.0 + fun:hwloc_topology_load +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + obj:/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.535.183.01 + ... + obj:/usr/lib/x86_64-linux-gnu/libOpenCL.so.1.0.0 + fun:clGetPlatformIDs + obj:/usr/lib/x86_64-linux-gnu/hwloc/hwloc_opencl.so + obj:/usr/lib/x86_64-linux-gnu/libhwloc.so.15.7.0 + fun:hwloc_topology_load + obj:* + obj:* +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:orte_init + fun:ompi_mpi_init + fun:PMPI_Init + ... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:* + obj:*libgomp.so.* + ... +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:orte_init + fun:ompi_mpi_init + fun:PMPI_Init + ... +} +{ + + Memcheck:Param + writev(vector[1]) + fun:__writev + fun:writev + fun:pmix_ptl_base_send_handler + obj:/usr/lib/x86_64-linux-gnu/libevent_core-2.1.so.7.0.1 + fun:event_base_loop + obj:/usr/lib/x86_64-linux-gnu/pmix2/lib/libpmix.so.2.13.1 + fun:start_thread + fun:clone +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + obj:* + obj:* + obj:* + obj:* + fun:ompi_mpi_init + fun:PMPI_Init + fun:main +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:* + ... + obj:/usr/lib/x86_64-linux-gnu/libhwloc.so.* + ... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:* + ... + obj:/usr/lib/x86_64-linux-gnu/libpciaccess.so.0.* + ... + fun:* + obj:/usr/lib/x86_64-linux-gnu/hwloc/hwloc_pci.so + ... + obj:/usr/lib/x86_64-linux-gnu/libhwloc.so.* + fun:hwloc_topology_load + ... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + obj:/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.* + ... +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + obj:/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.* + ... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + obj:/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.* + ... + obj:/usr/lib/x86_64-linux-gnu/libOpenCL.so.1.0.0 + fun:clGetPlatformIDs + obj:/usr/lib/x86_64-linux-gnu/hwloc/hwloc_opencl.so + obj:/usr/lib/x86_64-linux-gnu/libhwloc.so.* + ... + fun:hwloc_topology_load + ... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + obj:/usr/lib/x86_64-linux-gnu/libstdc++.so.* + fun:call_init* + ... + fun:_dl_init + fun:_dl_catch_exception + fun:dl_open_worker + ... + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run +} +{ + + Memcheck:Leak + match-leak-kinds: indirect + ... + obj:/usr/lib/x86_64-linux-gnu/libhwloc.so.* + ... +} +{ + + Memcheck:Leak + match-leak-kinds: definite + ... + obj:/usr/lib/x86_64-linux-gnu/libhwloc.so.* + ... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:calloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:openaux + fun:_dl_catch_exception + fun:_dl_map_object_deps + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:calloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:calloc + fun:_dl_check_map_versions + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen_implementation + fun:dlopen@@GLIBC_2.34 +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + obj:* + fun:ompi_op_base_op_select + fun:ompi_op_init + fun:ompi_mpi_init + fun:PMPI_Init + fun:main +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + obj:* + ... + obj:/usr/lib/x86_64-linux-gnu/libevent_core-2.1.so.7.0.1 + fun:event_base_loop + obj:* + fun:start_thread + fun:clone +} +{ + + Memcheck:Leak + match-leak-kinds: indirect + fun:malloc + obj:* + obj:* + obj:* + obj:* + obj:* + obj:/usr/lib/x86_64-linux-gnu/libevent_core-2.1.so.7.0.1 + fun:event_base_loop + obj:* + fun:start_thread + fun:clone +} +{ + + Memcheck:Leak + match-leak-kinds: indirect + fun:malloc + obj:* + obj:* + obj:* + obj:* + obj:/usr/lib/x86_64-linux-gnu/libevent_core-2.1.so.7.0.1 + fun:event_base_loop + obj:* + fun:start_thread + fun:clone +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ompi_op_base_op_select + fun:ompi_op_init + fun:ompi_mpi_init + fun:PMPI_Init + fun:main +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:openaux + fun:_dl_catch_exception + fun:_dl_map_object_deps + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_load_cache_lookup + fun:_dl_map_object + fun:openaux + fun:_dl_catch_exception + fun:_dl_map_object_deps + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + obj:* + obj:* + obj:* + obj:* + obj:* + obj:/usr/lib/x86_64-linux-gnu/libevent_core-2.1.so.7.0.1 + fun:event_base_loop + obj:* + fun:start_thread + fun:clone +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_load_cache_lookup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + ... + fun:ompi_mpi_init + fun:PMPI_Init + ... +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + obj:* + obj:* + fun:call_init.part.0 + fun:call_init + fun:_dl_init + fun:_dl_catch_exception + fun:dl_open_worker + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error +} +{ + + Memcheck:Leak + match-leak-kinds: definite + ... + fun:orte_finalize + fun:ompi_mpi_finalize +} +{ + + Memcheck:Leak + match-leak-kinds: indirect + ... + fun:ompi_mpi_init + fun:PMPI_Init + ... +} +{ + + Memcheck:Leak + match-leak-kinds: definite + ... + fun:ompi_mpi_init + fun:PMPI_Init + ... +} +{ + + Memcheck:Leak + ... + obj:/usr/lib/aarch64-linux-gnu/libtbb.so.12.11 + ... +} +{ + + Memcheck:Leak + ... + fun:*tbb*delegate_base* + ... +} +{ + + Memcheck:Leak +... + fun:pthread_create* +... +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen_implementation + fun:dlopen@@GLIBC_2.34 + obj:/usr/lib/aarch64-linux-gnu/libtbb.so.12.11 + obj:/usr/lib/aarch64-linux-gnu/libtbb.so.12.11 + obj:/usr/lib/aarch64-linux-gnu/libtbb.so.12.11 + fun:__pthread_once_slow + obj:/usr/lib/aarch64-linux-gnu/libtbb.so.12.11 + obj:/usr/lib/aarch64-linux-gnu/libtbb.so.12.11 + obj:/usr/lib/aarch64-linux-gnu/libtbb.so.12.11 + fun:_ZN3tbb6detail2r120isolate_within_arenaERNS0_2d113delegate_baseEl + fun:main +} +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:add_to_global_resize + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen_implementation + fun:dlopen@@GLIBC_2.34 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + ... + fun:blas_thread_init + fun:gotoblas_init + ... +} +{ + + Memcheck:Leak +... + fun:pmix_server_init +... +} +{ + + Memcheck:Leak +... + fun:orte_submit_init +... +} +{ + + Memcheck:Leak +... + fun:orte_rml_base_process_msg +... +} +{ + + Memcheck:Leak +... + fun:orte_quit +... +} +{ + + Memcheck:Leak +... + fun:orte_show_help +... +} +{ + + Memcheck:Leak +... + fun:orte_debugger_init_after_spawn +... +} +{ + + Memcheck:Leak +... + fun:__tls_get_addr +... +} +{ + + Memcheck:Leak +... + fun:opal_hwloc_base_get_topology +... +} +{ + + Memcheck:Leak +... + fun:orte_plm_base_post_launch +... +} +{ + + Memcheck:Leak +... + fun:hwloc_topology_load +} +{ + + Memcheck:Leak +... + fun:orte_init +} +{ + + Memcheck:Leak +... + fun:orte_rmaps_base_setup_proc +... +} +{ + + Memcheck:Leak +... + fun:dl_open_worker_begin +... +} +{ + + Memcheck:Cond + fun:main +} diff --git a/CMakeLists.txt b/CMakeLists.txt index d12b8bd4c..ebe3737f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,36 @@ -# -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -# Copyright 2018-2023 Alfredo A. Correa +# Copyright 2018-2025 Alfredo A. Correa +# Copyright 2024 Matt Borland +# Distributed under the Boost Software License, Version 1.0. +# https://www.boost.org/LICENSE_1_0.txt cmake_minimum_required(VERSION 3.16) +if (DEFINED BOOST_SUPERPROJECT_VERSION AND NOT DEFINED BOOST_MULTI_STANDALONE) + + project(boost_multi VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX) + + add_library(boost_multi INTERFACE) + + add_library(Boost::multi ALIAS boost_multi) + + target_include_directories(boost_multi INTERFACE include) + + target_compile_features(boost_multi INTERFACE cxx_std_14) + + message(STATUS "Boost.Multi: standalone mode OFF") + + if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt") + + add_subdirectory(test) + + endif() + + return() +endif() + # Library doesn't require installation, to still install this project: # ~~~ -# $ cmake .. --install-prefix=$HOME && cmake --build . --config Release --target test --target install -- -j $(nproc) +# $ cmake .. --install-prefix=$HOME && cmake --build . --config Release --target test --target install -- --parallel # ~~~ # to use this library in another CMake project # ~~~ @@ -15,43 +40,106 @@ cmake_minimum_required(VERSION 3.16) # target_link_libraries(${PROJECT_NAME} boost-multi::boost-multi) # ~~~ -if(EXISTS "CMakeLists.txt") - message(FATAL_ERROR "You probably don't want to run CMake in a directory with CMakeLists.txt") -endif() +message("current binary directory: ${CMAKE_CURRENT_BINARY_DIR}") project( multi HOMEPAGE_URL "https://gitlab.com/correaa/boost-multi" DESCRIPTION "A header-only modern C++ library that provides access and manipulation of data in multidimensional arrays." - VERSION 0.80.1 + VERSION 0.88.0 LANGUAGES CXX) -include(CMakePackageConfigHelpers) -include(CMakeDependentOption) -include(GNUInstallDirs) -include(CTest) +message(STATUS "Boost.Multi: standalone mode ON") -option(MULTI_BUILD_PACKAGE "Build package files as well" ON) +add_library(multi INTERFACE) -cmake_dependent_option(MULTI_BUILD_TESTS "Enable multi tests" ON "BUILD_TESTING" OFF) +add_library(boost.multi ALIAS multi) +add_library(boost::multi ALIAS multi) -cmake_dependent_option(MULTI_BUILD_PACKAGE_DEB "Create a DEB" ON "MULTI_BUILD_PACKAGE" OFF) +#target_sources( +# multi +# PUBLIC +# FILE_SET HEADERS +# BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include +# FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/boost/multi/array.hpp +#) + +set_target_properties( + multi + PROPERTIES + # VERIFY_INTERFACE_HEADER_SET ON + EXPORT_NAME multi-targets +) -add_library(multi INTERFACE) target_include_directories(multi INTERFACE $ $) -if(NOT CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) - add_library(correaa::multi ALIAS multi) +#if(NOT ENABLE_CUDA) +#target_compile_options(multi INTERFACE +# $<$: --expt-relaxed-constexpr --extended-lambda> +# $<$: +# /EHsc +# $<$:/permissive- > +# /wd5027 # move assignment operator was implicitly defined as deleted, for detail::array_allocator +# > # this is needed for MSVC in C++17 mode +#) +#else() +#target_compile_options(multi INTERFACE +# $<$: --expt-relaxed-constexpr --extended-lambda> +# $<$:-Xcompiler=/EHsc $<$:-Xcompiler=/permissive- > > # this is needed for MSVC in C++17 mode +#) + +if(NOT ENABLE_CIRCLE) + target_compile_options(multi INTERFACE + $<$,$,$>: + -Wno-c++17-extensions + -Wno-c++98-compat -Wno-c++98-compat-bind-to-temporary-copy + -Wno-padded + $<$,12>:-Wno-pre-c++17-compat> + > + $<$: --expt-relaxed-constexpr> # --extended-lambda> + $<$: + $<$>: + /EHsc /permissive- + > + $<$: + -Xcompiler=/EHsc -Xcompiler=/permissive- + > + > + ) endif() # https://youtrack.jetbrains.com/issue/CPP-25608 -target_compile_features(${PROJECT_NAME} INTERFACE $<$>:cxx_std_17>) -target_compile_options(${PROJECT_NAME} INTERFACE $<$:-std=c++17>) +target_compile_features(multi INTERFACE cxx_std_17) +if(ENABLE_CUDA) + if(WIN32) + target_compile_features(multi INTERFACE cuda_std_20) + else() + target_compile_features(multi INTERFACE cuda_std_17) + endif() +endif() + +if(NOT CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + add_library(correaa::multi ALIAS multi) +endif() # this makes CM FetchContent friendly https://www.foonathan.net/2022/06/cmake-fetchcontent/ if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) return() endif() +################################################################################ + + +include(CMakePackageConfigHelpers) +include(CMakeDependentOption) +include(GNUInstallDirs) + +include(CTest) + +option(MULTI_BUILD_PACKAGE "Build package files as well" ON) + +cmake_dependent_option(MULTI_BUILD_TESTS "Enable multi tests" ON "BUILD_TESTING" OFF) + +cmake_dependent_option(MULTI_BUILD_PACKAGE_DEB "Create a DEB" ON "MULTI_BUILD_PACKAGE" OFF) # Installation help configure_package_config_file("${PROJECT_SOURCE_DIR}/cmake/multi-config.cmake.in" "${PROJECT_BINARY_DIR}/multi-config.cmake" INSTALL_DESTINATION "share/cmake/multi") @@ -61,44 +149,86 @@ write_basic_package_version_file("${PROJECT_BINARY_DIR}/multi-config-version.cma message("current install prefix directory: ${CMAKE_INSTALL_PREFIX}") install( - TARGETS multi - EXPORT multi-targets - INCLUDES - DESTINATION "${CMAKE_INSTALL_DATADIR}") - -install( - EXPORT ${PROJECT_NAME}-targets - DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}" - NAMESPACE multi:: - FILE "${PROJECT_NAME}-targets.cmake") + TARGETS multi COMPONENT multi + EXPORT multi + # EXPORT multi-targets + DESTINATION ${CMAKE_INSTALL_LIBDIR}$<$:/debug> + # "${CMAKE_INSTALL_DATADIR}" + # RUNTIME_DESTINATION ${CMAKE_INSTALL_BINDIR}$<$:/debug> + # INCLUDES + # FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +#install( +# EXPORT ${PROJECT_NAME}-targets +# DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}" +# NAMESPACE multi:: +# FILE "${PROJECT_NAME}-targets.cmake") install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}") install(DIRECTORY "include/" TYPE INCLUDE) -add_subdirectory(test) - if(MULTI_BUILD_TESTS) - find_package(Boost 1.67 COMPONENTS unit_test_framework) # Boost 1.66 has compilation problems with g++ 8-12+ and clang++ ~15, in that case install your own Boost and use `cmake -DBOOST_ROOT=$HOME/local` + find_package(Boost COMPONENTS NO_MODULE) # you can use your own Boost and use `cmake -DBOOST_ROOT=$HOME/local` if(NOT Boost_FOUND) - message(WARNING "Cannot find Boost 1.67+, Multi library will not be fully tested.") + message(WARNING "Cannot find Boost, Multi library will not be fully tested.") else() enable_testing() - add_subdirectory(include/multi/adaptors/blas) - add_subdirectory(include/multi/adaptors/complex) - add_subdirectory(include/multi/adaptors/cuda) - add_subdirectory(include/multi/adaptors/fftw) - add_subdirectory(include/multi/adaptors/thrust) + add_subdirectory(test) + + add_subdirectory(include/boost/multi/adaptors/blas) + # # add_subdirectory(include/boost/multi/adaptors/complex) + add_subdirectory(include/boost/multi/adaptors/fftw) + + find_package(LAPACK) + if(LAPACK_FOUND) + add_subdirectory(include/boost/multi/adaptors/lapack) + endif() + + add_subdirectory(include/boost/multi/adaptors/thrust) if(ENABLE_CUDA) - add_subdirectory(include/multi/adaptors/cufft) + add_subdirectory(include/boost/multi/adaptors/cuda) + add_subdirectory(include/boost/multi/adaptors/cufft) endif() if(ENABLE_HIP) - add_subdirectory(include/multi/adaptors/hipthrust/test) + # add_subdirectory(include/boost/multi/adaptors/hipfft) + # add_subdirectory(include/boost/multi/adaptors/hipthrust/test) endif() + + add_subdirectory(include/boost/multi/adaptors) endif() endif() +# # Find the quom tool. Adjust the path if quom is not in your system's PATH. +# # Alternatively, you could download it as part of a custom build step. +# find_program(QUOM_EXECUTABLE quom REQUIRED) + +# # Define variables for the input and output files +# set(QUOM_INPUT_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/include/boost/multi/array.hpp") +# set(QUOM_OUTPUT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/array_amalgamated.hpp") + +# # Use add_custom_command to run quom and generate the single header. +# add_custom_command( +# OUTPUT ${QUOM_OUTPUT_HEADER} +# COMMAND ${QUOM_EXECUTABLE} --output ${QUOM_OUTPUT_HEADER} ${QUOM_INPUT_HEADER} +# DEPENDS ${QUOM_INPUT_HEADER} +# COMMENT "Generating single header file with quom" +# ) + +# # Create an interface library to encapsulate the single header. +# # This makes it easy for other targets to use the generated header. +# add_library(my_project_single_header INTERFACE) + +# # The generated header becomes a dependency of the interface library. +# add_dependencies(my_project_single_header ${QUOM_OUTPUT_HEADER}) + +# # Add the binary directory to the include path so targets can find the generated header. +# target_include_directories(my_project_single_header INTERFACE +# $ +# ) + if(MULTI_BUILD_PACKAGE) list(APPEND source-generators TBZ2 TGZ TXZ ZIP) @@ -114,13 +244,39 @@ if(MULTI_BUILD_PACKAGE) list(APPEND binary-generators "RPM") endif() + set(CPACK_PACKAGE_NAME ${PROJECT_NAME} + CACHE STRING "The resulting package name" + ) + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "all") + set(CPACK_PACKAGE_ARCHITECTURE "all") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Multidimensional arrays for C++" + CACHE STRING "Package description for the package metadata" + ) + + set(CPACK_PACKAGE_VENDOR "alfredo.correa@gmail.com") + # set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) + # SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/_packages") + # set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/some")#/${CMAKE_PROJECT_VERSION}") + + # set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) + # set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) + # set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) + + set(CPACK_PACKAGE_CONTACT "alfredo.correa@gmail.com") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Alfredo A. Correa") + + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + + set(CPACK_DEBIAN_FILE_NAME "multi_all.deb") + # set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)#ONE_PER_GROUP) + # set(CPACK_DEB_COMPONENT_INSTALL YES) + set(CPACK_SOURCE_GENERATOR ${source-generators}) set(CPACK_GENERATOR ${binary-generators}) - set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}") - set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}") - - set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Alfredo A. Correa") + # set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}") + # set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}") list( APPEND @@ -133,10 +289,3 @@ if(MULTI_BUILD_PACKAGE) include(CPack) endif() - -find_program(MEMORYCHECK_COMMAND valgrind) -set(VALGRIND_COMMAND_OPTIONS "-q --tool=memcheck --leak-check=yes --num-callers=51 --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all") - -# must go before `include(CTest)` -set(MEMORYCHECK_COMMAND_OPTIONS "-q --tool=memcheck --leak-check=yes --num-callers=52 --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all") -set(MEMORYCHECK_SUPPRESSIONS_FILE "${PROJECT_SOURCE_DIR}/.valgrind-suppressions") diff --git a/CPPLINT.cfg b/CPPLINT.cfg index 7bf7ea8b9..b05c72bf9 100644 --- a/CPPLINT.cfg +++ b/CPPLINT.cfg @@ -1,17 +1,14 @@ set noparent root=./include/ -linelength=240 +linelength=160 -filter=-build/include_order +filter=-build/include_order # this library uses this order of inclusion: local headers / third pary heders / boost headers / standard headers -filter=-readability/alt_tokens -filter=-readability/nolint +filter=-runtime/references # this library uses references -filter=-runtime/references +filter=-readability/nolint # accept NOLINT from other linters -filter=-whitespace/braces -filter=-whitespace/operators -filter=-whitespace/parens -filter=-whitespace/semicolon -filter=-whitespace/tab +filter=-whitespace/braces # braces unfortunately makes local scopes need an extra space {} +filter=-whitespace/parens # otherwise it will complain about whitespace in MACRO args +filter=-whitespace/tab # this library uses tabs for indentation diff --git a/README.md b/README.md index e07a1dd8a..8fff2512a 100644 --- a/README.md +++ b/README.md @@ -1,1428 +1,87 @@ - -# [Boost.]Multi - -> **Disclosure: Not an official or accepted Boost library and it is unrelated to the std::mspan proposal.** - -_© Alfredo A. Correa, 2018-2023_ - -`Multi` is a modern C++ library that provides access and manipulation of data in multidimensional arrays, for both CPU and GPU memory. - -Multidimensional array data structures are fundamental to several branches of computing, such as data analysis, image processing, and scientific simulations, and in combination with GPUs to Artificial Intelligence and Machine Learning. - -This library offers array containers and views in arbitrary dimensions with well-behaved value semantics, offering total compatibility with the Standard Algorithms (STL), special memory (including GPUs), and following modern C++ design principles. -It requires at least C++17 (works with C++20 too.) - -Some features of this library: - -* Value semantics of multi-dimensional array containers -* Well-defined referential semantics of subarray (view) types -* Interoperability with other libraries, STL, ranges, thrust (CUDA and AMD GPUs), Boost, and C-libraries -* Fast access to elements and subarrays (views) types -* Arbitrary pointer types (fancy pointers, memory spaces) -* Simplified implementation (~4000 lines) - -Do not confuse this library with [Boost.MultiArray](https://www.boost.org/doc/libs/1_69_0/libs/multi_array/doc/index.html), or with the standard MDSpan proposal `std::mdspan`. -`Multi` shares some of their goals but at a different level of generality. -The code is completely independent and with important differences in the implementation and semantics. - -## Contents -[[_TOC_]] - -## Using the library, installation and tests - -`Multi` doesn't require installation, a single header `#include` is enough to use the full core library. -`Multi` has no dependencies (except for the standard C++ library) and can be used immediately after downloading it. - -```bash -git clone https://gitlab.com/correaa/boost-multi.git -``` - -You can try the library [online](https://godbolt.org/z/dvacqK8jE) before using it locally. - -Although installation is not necessary, the library can still be installed system-wide with CMake. -The header (and cmake) files will typically end up in `/usr/local/include/multi` and `/usr/local/share/multi`. - -```bash -cd multi -mkdir -p build && cd build -cmake .. # --install-prefix=$HOME/.local -sudo cmake --install . -``` - -_Testing_ the library requires Boost.Test library, installed for example via `sudo apt install cmake libboost-test-dev make` or `sudo dnf install boost-devel cmake gcc-c++`. -A CMake build system is provided to compile and run basic tests. - -```bash -cmake --build . -ctest -``` - -Other CMake projects (targets) can depend on Multi by adding a simple `add_subdirectory(my_multi_path)` or by `find_package`: - -```cmake -find_package(multi) # see https://gitlab.com/correaa/boost-multi#using-the-library-installation-and-tests -... -target_link_library(my_target PUBLIC multi) -``` - -Alternatively to `find_package` the library can be fetched on demand by the CMake project: -```cmake -FetchContent_Declare(multi GIT_REPOSITORY https://gitlab.com/correaa/boost-multi.git) -FetchContent_MakeAvailable(multi) -... -target_link_library(my_target PUBLIC multi) -``` - -The code requires compilers with standard C++17 support, for reference any of: -LLVM's `clang` [(5.0+)](https://godbolt.org/z/51E1hjfnn) (`libc++` and `libstdc++`), -GNU's `g++` [(7.1+)](https://godbolt.org/z/1nGEbKc5a), -Nvidia's [`nvcc`](https://godbolt.org/z/Kdh5PeP5f) (11.0+) and `nvc++` (22.7+), -Intel's `icpc` (2021.2.0+) and `icpx` (2022.0.0+), -Baxter's [`circle`](https://www.circle-lang.org/) (build 187+), -and Microsoft's [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) (+19.14 in [conformant mode](https://godbolt.org/z/vrfh1fxWK)). - -Optional "adaptor" sublibraries (included in `multi/adaptors/`) have specific dependencies, Boost.Serialization, fftw, blas, lapack, thurst, CUDA -(which can be installed with `sudo apt install libboost-serializa -tion-dev libfftw3-dev libblas64-dev liblapack64-dev libthrust-dev libcudart11.0` or `sudo dnf install blas-devel fftw-devel`.) -HIP support is experimental. - -## Types - -* `multi::array>`: -Array of integer positive dimension `D`, it has value semantics if element type `T` has value semantics. -Memory is requested by an allocator of type `A`, supports stateful and polymorphic allocators. -* `multi::array_ref`: -Array interpretation of a random access range, usually a contiguous memory block. -It has reference semantics. -`P` does not need to be a language-pointer, it can be anything that behaves like a pointer (derreference and random access arithmetics). -`array` is implicitly an `array_ref`, the reverse conversion is only explicit. -* Other derived "unspecified types" fulfill a `MultiArrayView` concept, for example by taking partial indices or rotations (transpositions). -These reference types can be named life-time extensions `auto&&` or `auto const&`, -and they can decay to value types (copied elements). -* `MultiArrayView::(const_)iterator`: -Iterator to subarrays of lower dimension. -For `D == 1` this is an iterator to an element (or scalar, with zero dimension). -This types are generated by `begin` and `end` (member) functions. -* `MultiArrayView::(const_)reference`: -Reference to subarrays of lower dimension. -For `D > 1`, these are not true language-references but types that emulate them (with reference semantics). -For `D == 1` this is a true language reference to an element type (`T&`). -These types are generated by dereferencing iterators, (e.g. `*begin(MA)`), or by indexing (`MA[0]`). - -## Basic Usage - -The following code declares an array by specifying the element type and the dimension; -individual elements can be initialized from a nested rectangular list. -```cpp -multi::array A = { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0} -}; - -assert( A.size() == 2 ); -assert( A.num_elements() == 6 ); - -assert( std::get<0>(A.sizes()) == 2 ); -assert( std::get<1>(A.sizes()) == 3 ); -``` - -The value of an array can be copied, (moved,) and compared; -copies are equal but independent (value semantics). - -```cpp -std::array B = A; -assert( extensions(B) == extensions(A) ); -assert( B == A ); -assert( B[0][1] == A[0][1] ); -assert( &B[0][1] != &A[0][1] ); -``` - -Individual elements can be accessed by the multidimensional indices, either with square brackets (one index at a time) or with parenthesis (comma separated). - -```cpp -assert( &A(1, 2) == &A[1][2] ); -``` - -An arrays can be initialized from its sizes alone, in which case the element values are defaulted (or uninitialized): - -```cpp -multi::array C({3, 4, 5}); -assert( num_elements(C) == 3*4*5 ); // 60 elements with unspecified values -``` - -Arrays can be passed by value or by reference. -Most of the time, arguments should be passed through generic parameters to allow functions to also work with parts (subblocks, slices, etc.) of an array. -Usually, the most useful functions work on the _concept_ of an array rather than on a concrete type. - -```cpp -template // instead of the overspecific argument std::array -auto element_1_1(ArrayDouble2D const& m) -> double const& {return m[1][1];} -... -assert( &element_1_1(A) == &A[1][1] ); -``` - -The function expects any array or subarray of dimension 2 and return an element with type `double`. - -The generic function template arguments that are not intended to be modified are passed by `const&`; otherwise, they are passed by forward-reference `&&`. -In this way, the functions can be applied on subblocks of larger matrices. - -```cpp -assert( &element_1_1(C3D[0]) == &C3D[0][1][1] ); -``` - -## Advanced Usage - -We can create a static C-array of `double`s, and refer to it via a bidimensional array `multi::array_ref`. - -```cpp -#include "multi/array.hpp" - -#include // for sort -#include // for print - -namespace multi = boost::multi; - -int main() { - double d_data[20] = { - 150.0, 16.0, 17.0, 18.0, 19.0, - 30.0, 1.0, 2.0, 3.0, 4.0, - 100.0, 11.0, 12.0, 13.0, 14.0, - 50.0, 6.0, 7.0, 8.0, 9.0 - }; // block of 20 elements ... - multi::array_ref d2D_ref{&d_data[0], {4, 5}}; // interpreted as a 4 by 5 array - ... -``` - -Note that the syntax of creating a reference array involves passing the pointer to a memory block (20 elements here) and the logical dimensions of that memory block (4 by 5 here). - -Next we print the elements in a way that corresponds to the logical arrangement: - -```cpp - ... - auto [is, js] = d2D_ref.extensions(); - for(auto i : is) { - using std::cout; - for(auto j : js) { - cout<< d2D_ref[i][j] <<' '; - } - cout <<'\n'; - } - ... -``` - -This will output: - -> ``` -> 150 16 17 18 19 -> 30 1 2 3 4 -> 100 11 12 13 14 -> 50 6 7 8 9 -> ``` - -The arrays provide iterator-based access, which allows it to interface with algorithms and implement new ones. - -It is sometimes said (by Sean Parent) that the whole of STL algorithms can be seen as intermediate pieces to implement `std::stable_sort`. -Pressumably, if one can sort over a range, one can perform any other standard algorithm. - -```cpp - ... - std::stable_sort( begin(d2D_ref), end(d2D_ref) ); - ... -``` - -If we print the result, we will get: - -> ``` -> 30 1 2 3 4 -> 50 6 7 8 9 -> 100 11 12 13 14 -> 150 16 17 18 19 -> ``` - -The array has been changed to be in row-based lexicographical order. -Since the sorted array is a reference to the original data, the original C-array has changed. - -(Note that `std::sort` cannot be applied directly to a multidimensional C-array or to Boost.MultiArray types, among other libraries. -The arrays implemented by this library are, to the best of my knowledge, the only ones that support all STL algorithms directly.) - -If we want to order the matrix in a per-column basis we need to "view" the matrix as range of columns. -This is done in the bidimensional case, by accessing the matrix as a range of columns: - -```cpp - ... - std::stable_sort( rotated(d2D_ref).begin(), rotated(d2D_ref).end() ); - } -``` - -Which will transform the matrix into: - -> ``` -> 1 2 3 4 30 -> 6 7 8 9 50 -> 11 12 13 14 100 -> 16 17 18 19 150 -> ``` - -In other words, by combining index rotations and transpositions, an array of dimension `D` can be viewed simultaneously as `D!` (D-factorial) different ranges of different "transpositions" (rotation/permutation of indices.) - -## Initialization - -`array_ref` is initialized from a preexisting contiguous range, the index extensions should compatible with the total number of elements. - -```cpp -double* dp = new double[12]; -multi::array_ref A({3, 4}, dp); -multi::array_ref B({2, 6}, dp); -... -delete[] dp; -``` -Array references do not own memory and, just as language references, can not be rebinded (resized or "reseated") to refer to a different location. -Since `array_ref` is an array reference, it can "dangle" if the the original memory is deallocated. - -Array objects (`multi::array`), in constrast, own the elements it contains and can be resized; -`array` is initialized by specifying the index extensions (and optionally a default value). - -```cpp -multi::array A1({3} , 11.0); // {11.0, 11.0, 11.0} - -multi::array A2({2, 3} , 22.0); // { {22.0, 22.0, 22.}, {22.0, 22.0, 22.0} } - -multi::array A3({3, 2, 2}, 33.0); // { { { 33., ...}, { ... }, ... } } -``` -... or alternatively from a rectangular list. - -```cpp -multi::array A1 = {1.0, 2.0, 3.0}; -assert( num_elements(A1)==3 ); - -multi::array A2 { - { 1.0, 2.0, 3.0}, - { 4.0, 5.0, 6.0} -}; - -assert( num_elements(A2) == 2*3); - -multi::array const A3 = { - {{ 1.2, 0.0}, { 2.4, 1.0}}, - {{11.2, 3.0}, {34.4, 4.0}}, - {{15.2, 99.0}, {32.4, 2.0}} -}; - -assert( A3.num_elements() == 3 * 2 * 2 ); -``` - -In all cases, constness (`const` declaration) is honored in the expected way. - -## Copy and assigment - -The library offer value semantics for the `multi::array` family of classes. -Constructing or assigning from an existing array generates a copy of the original object, that is, and object that is independent but equal in value. - -```cpp -auto B2 = A2; // same as multi::array B2 = A2; -assert( B2 == A2 ); // copies have the same value (and also the same shape) -assert( &B2[0][0] != &A2[0][0] ); // but they are independent -``` - -A (mutable) array can be assigned at any moment, independently of the previous state or shape (extensions). -The dimensionalities must match. -```cpp -B2 = A2; -``` - -(The operation can fail if there is no enough memory to hold a copy.) - -Sometimes it is necessary to generate copies from views or subblocks. -```cpp -multi::array C2 = A2( {0, 2}, {0, 2} ); -``` -or equivalently,: -```cpp -auto C2 = + A2( {0, 2}, {0, 2} ); -``` -Note the use of the prefix `+` as an indicator that a copy must be created (it has no arithmetic implications). -Due to limitations of the language, omiting the `+` will create effectively another reference non-indepdent view of the left-hand-side, which is generally undesired. - -Subviews can also assigned but only if the shape of the left-hand side (LHS) and right-hand side (RHS) match. -Otherwise the behavior is undefined (in debug mode the program will fail an `assert`). - -```cpp -C2( {0, 2}, {0, 2} ) = A2( {0, 2}, {0, 2} ); // both are 2x2 views of arrays, *elements* are copied -``` - -Introducing the same or overlapping arrays in the RHS and LHS produces undefined behavior in general (and the library doesn't check); -Notably, this instruction does not transpose the array, but produces an undefined result: - -```cpp -A2 = A2.transposed(); -``` - -While this instead does produce a transposition, at the cost of making a copy (`+`) of the tranposed array first and assigning (or moving) it back to the original array. - -```cpp -A2 = + A2.transposed(); -``` - -In-place transposition is an active subject of research; _optimal_ in speed and memory transpositions might require specially designed libraries. - -Finally, arrays can be efficiently moved by transferring ownership of the internal data. - -```cpp -auto B2 = std::move(A2); // A2 is empty after this -``` - -Subarrays do not own the data therefore they cannot be moved in the same sense. -However, indivial elements of a view can be moved, this is particularly useful if the elements are expensive to copy. -A "moved" subview is simply another kind view of the elements. - -```cpp -multi::array, 2> A({10, 10}); -multi::array, 2> B({10, 10}); -... -B[1] = A[2].element_moved(); // 10 *elements* of the third row of A is moved into the second row of B. -``` - -## Change sizes (extents) - -Arrays can change their size while _preserving elements_ with the `reextents` method. - -```cpp -multi::array A { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0} -}; - -A.reextents({4, 4}); - -assert( A[0][0] == 1.0 ); -``` - -Arrays can be emptied (zero-size) and memory is freed with `.clear()` (equivalent to `.reextents({0, ...})`). - -The main purpose of `reextents` is element preservation. -Allocations are not amortized; -except for trivial cases, all calls to reextend allocates and deallocates memory. -If element preservation is not desired, a simple assignment (move) from a new array expresses the intention better and it is more efficient since it doesn't need to copy preexisiting elements. - -```cpp -A = multi::array({4, 4}); // like A.reextents({4, 4}) but elements are not preserved. -``` - -An alternative syntax, `.reextents({...}, value)` sets _new_ (not preexisting) elements to a specific value. - -Subarrays or views cannot change their size or be emptied (e.g. `A[1].reextents({4})` or `A[1].clear()` will not compile). -For the same reason, subarrays cannot be assigned from an array or another subarray of a different size. - -Changing the size of arrays by `reextents`, `clear`, or assignment generally invalidates existing iterators and ranges/views. -In contrast, `static_array`, which can be used in many cases as a replacement of `array`, doesn't have operations that invalidate iterators as it cannot be resized or assigned from arrays of different size. -(Preventing iterator invalidation can be a helpful technique in multithreaded programs.) - -## Iteration - -Accessing arrays by iterators (`begin`/`end`) enables the use of many iterator-based algorithms (see the sort example above). -`begin(A)/end(A)` (or equivalently `A.begin()/A.end()`) gives iterators that are linear and random access in the leading dimension. - -Other non-leading dimensions can be obtained by "rotating" indices first. -`A.rotated().begin()/.end()` gives access to a range of subarrays in second dimension number (first dimension is put at the end). - -`cbegin/cend` give constant (read-only access). -For example in a three dimensional array, - -```cpp - (cbegin(A)+1)->operator[](1).begin()[0] = 342.4; // error, read-only - ( begin(A)+1)->operator[](1).begin()[0] = 342.4; // assigns to A[1][1][0] - assert( ( begin(A)+1)->operator[](1).begin()[0] == 342.4 ); -``` - -As an example, this function allows printing arrays of arbitrary dimension into a linear comma-separated form. - -```cpp -void flat_print(double const& d) { cout< -void flat_print(MultiArray const& ma) { - cout << "{"; - if(not ma.empty()) { - flat_print(*cbegin(ma)); // first element - std::for_each(cbegin(ma)+1, cend(ma), [](auto&& e) { cout<<", "; flat_print(e);}); // rest - } - cout << "}"; -} -... -print(A); -``` -> ``` -> {{{1.2, 1.1}, {2.4, 1}}, {{11.2, 3}, {34.4, 4}}, {{15.2, 99}, {32.4, 2}}} -> ``` - -Except for those corresponding to the one-dimensional case, derreferencing iterators generally produce "proxy"-references (i.e. objects that behave in a large degree like language references). -These references can be given a name; using `auto` can be misleading since the resulting variable does not have value semantics. - -```cpp -auto row = *begin(A); // accepted by the language but misleading, row is not an independent value -``` - -In my experience, however, the following usage pattern produces a more consistent idiom for generating references (still without copying elements): - -```cpp -auto&& row0 = * begin(A); // same as decltype(A):: reference row0 = * begin(A); -auto const& crow0 = *cbegin(A); // same as decltype(A)::const_reference crow0 = *cbegin(A); - -auto&& row1 = A [1]; // same as decltype(A):: reference row1 = A [1]; -auto const& crow1 = std::as_const(A)[1]; // same as decltype(A)::const_reference crow0 = std::as_const(A)[1]; -``` - -If a new value is desired, these (equivalent) options express the intention more explicitly: - -```cpp -decltype(A)::value_type row = *begin(A); // there is a real copy of the row - auto row = + *begin(A); // there is another copy, note the use of '+' (unary plus) -``` - -### "Pointer" to subarray - -Subarrays (e.g., rows in a 2D array) are reference-like objects with a concrete address-like value that identifies them uniquely. -These addresses, which behave like pointers, can be helpful to "mark" subviews; these markers can be copied and stored in arrays. - -```cpp -auto A = multi::array({4, 4}); - -auto row2_ptr = &A[2]; // A[2] is a row of A (not an element) -assert( row2_ptr == &*(A.begin() + 2) ); -``` - -The expression `A[2]` above is technically a C++ temporary object, and therefore it doesn't have a C++ address (taking `std::addressof` gives a compilation error). -However, in the library's abstraction, `A[2]` references an existing part of the original array, i.e. it is a "library reference", whose "library address" can be obtained operator `&`. -The case is an illustration that, in the library, operator `&` is, for subarrays, different than the `std::addressof` operator; the latter may not be defined and even not compile for some expressions. - -Comparing these markers/pointers with different provenance, i.e., originating from different arrays, is generally undefined. - -## Indexing - -Arrays provide random access to elements or subviews. -Many algorithms on arrays are oriented to linear algebra, -which are ubiquitously implemented in terms of multidimensional index access. - -Iterator access and index access are two alternatives for accessing elements. -For example `*(begin(A) + n)` and `A[n]` are equivalent -and the range defined by the pair `begin(A), end(A)` is equivalent to `A(extension(A))` and, in turn, to `A()` (even for a multidimensional array, `D > 1`). -The syntax can be combined in arbitrary ways, for example `*begin(A[n])` is equivalent to `A[n][0]`. - -### Element access and partial access - -Index access mimics that of C-fixed sizes arrays. -For example, a 2-dimensional array will access to an element by specifying two indices `A[1][2]`, -which can be used for direct write and read operations; -while _partial_ index arguments `A[1]` generate a view 1-dimensional object (a reference). - -```cpp -A // is a 2D value array -A[0] // is a 1D "reference"/"view" array -A[0][0] // is a an element reference, zero-D -``` - -Transpositions are also multidimensional arrays _views_ in which the index are *logically* rearranged, for example `rotated(m)[2][3][1] == m[1][2][3]`. -(`rotated`/`unrotated` refers to the fact that the logical _indices_ are rotated to the left/right.) - -As an illustration of an algorithm based on index access (as opposed to iterators), -this example code implements Gauss Jordan Elimination without pivoting: - -```cpp -template -auto gj_solve(Matrix&& A, Vector&& y) -> decltype(y[0]/=A[0][0], y) { - std::ptrdiff_t Asize = size(A); - for(std::ptrdiff_t r = 0; r != Asize; ++r) { - auto&& Ar = A[r]; - auto&& Arr = Ar[r]; - for(std::ptrdiff_t c = r + 1; c != Asize; ++c) {Ar[c] /= Arr;} - auto const yr = (y[r] /= Arr); - for(std::ptrdiff_t r2 = r + 1; r2 != Asize; ++r2) { - auto&& Ar2 = A[r2]; - auto const& Ar2r = Ar2[r]; // auto&& Ar = A[r]; - for(std::ptrdiff_t c = r + 1; c != Asize; ++c) {Ar2[c] -= Ar2r*Ar[c];} - y[r2] -= Ar2r*yr; - } - } - for(std::ptrdiff_t r = Asize - 1; r > 0; --r) { - auto const& yr = y[r]; - for(std::ptrdiff_t r2 = r-1; r2 >=0; --r2) {y[r2] -= yr*A[r2][r];} - } - return y; -} -``` - -This function can be applied to a `multi::array` container: - -```cpp -multi::array A = {{-3.0, 2.0, -4.0},{0.0, 1.0, 2.0},{2.0, 4.0, 5.0}}; -multi::array y = {12.0, 5.0, 2.0}; // (M); assert(y.size() == M); iota(y.begin(), y.end(), 3.1); -gj_solve(A, y); -``` - -and also to a combination of `MultiArrayView`-type objects (including standard vectors): - -```cpp -multi::array A({6000, 7000}); std::iota(A.data_elements(), A.data_elements() + A.num_elements(), 0.1); -std::vector y(3000); std::iota(y.begin(), y.end(), 0.2); // could be also a multi::array y({3000}); -gj_solve(A({1000, 4000}, {0, 3000}), y); -``` - -### Slices and strides - -Given an array, a slice in the first dimension can be taken with the `sliced` function. -`sliced` takes two arguments, the first index of the slice and the last index (not included) of the slice. For example, - -```cpp -multi::array A({4, 5}); // A is a value -assert( std::get<0>(A.sizes()) == 4 ); -assert( std::get<1>(A.sizes()) == 5 ); - -auto&& A_sliced = A.sliced(1, 3); // {{d2D[1], d2D[2]}} -assert( std::get<0>(A_sliced.sizes()) == 2 ); -assert( std::get<1>(A_sliced.sizes()) == 5 ); -``` - -The number of rows in the sliced matrix is 2 because we took only two rows, row 1 and row 2 (row 3 is excluded). - -In the same way a strided view of the original array can be taken with the `strided` function. - -```cpp -auto&& d2D_strided = d2D.strided(2); // {{ d2D[0], d2D[1] }}; -assert( d2D_strided.size(0) == 2 and d2D_strided.size(1) == 5 ); -``` - -In this case the number of rows is 2 because, out of the 4 original rows we took one every two. - -Operations can be combined in a single line: - -```cpp -auto&& d2D_slicedstrided = d2D.sliced(1, 3).strided(2); // {{ d2D[1] }}; -assert( std::get<0>(d2D_slicedstrided.sizes()) == 1 and std::get<1>(d2D_slicedstrided.sizes()) == 5 ); -``` - -For convenience, `A.sliced(a, b, c)` is the same as `A.sliced(a, b).strided(c)`. - -By combining `rotated`, `sliced` and `strided` one can take sub arrays at any dimension index. -For example in a two dimensional array one can take a subset of columns by defining. - -```cpp -auto&& subA = A.rotated().sliced(1, 3).strided(2).unrotated(); -``` - -Other notations are available, for example this is equivalent to `A(multi::all, {1, 3, /*every*/2})` or `~(~A)({1, 3, 2})`. -The `rotated/strided/sliced/rotated` and combinations of them provides the most control over the subview operations. - -Blocks (slices) in multidimensions can be obtained by pure index notation using parentheses `()` (`.operator()`): - -```cpp -auto A = multi::array({6, 7}); // 2D value array - -auto&& A_block1 = A({1, 4}, {2, 4}); // 2D subarray reference (modifiable) -auto const& A_block2 = A({1, 4}, {2, 4}); // 2D subarray reference (non-modifiable) - -auto A_block3 = A({1, 4}, {2, 4}); // works but it can be confusing, use `auto&&` instead -``` - -Sometimes copies are necessary, specifically from a subarray block, this can be done by constructing a new array. -The value array can be deduced by using `auto` and the `decay` member, which in turn is equivalent to the prefix `+` operator. - -```cpp -multi::array block_value_1 = A({1, 4}, {2, 4}) ; -auto block_value_2 = A({1, 4}, {2, 4}).decay(); -auto block_value_3 = + A({1, 4}, {2, 4}) ; -``` - -Any parenthesis argument can be either a range (with or without stride) or an index. -Range argument can be substituted by `multi::all` to obtain the whole range. - -## Conversions - -Conversion between two distinct array types is possible if the underlying elements allow it. -The result is as if elements are converted one by one. -Allowed conversions can be implicit or explicit and reflect the behavior of the element types. Array shapes (extensions) are preserved in the conversion. - -```cpp -// conversions from real to complex is implicit ... -double d = 5.0; -std::complex z = d; -// ... therefore from array of reals to arrays of complex is also -multi::array D({10, 10}); -multi::array, 2> Z = D; -// in the other direction, conversions are (implict or explicit) forbidden -// multi::array DD{Z}; // compilation error -``` - -Another case is illustrated by `std::complex` and `std::complex`; in one direction, the conversion can be implicit, while in the other, it can only be explicit. -The arrays reflect this behavior: - -```cpp -multi::array> C; -multi::array> Z = C; // implicit conversion ok -multi::array> C2{Z}; // conversion needs to be explicit -``` - -Implicit conversions are generally considered harmful, but inconsistent conversions are worst; therefore, the library allows them when appropriate. -The main drawback of implicit conversions in this context is that they might incur unexpected (costly) data conversions when passing arguments to functions. - -```cpp -void fun(multi::array> Z) { ... }; -... -multi::array D({10, 10}); -fun(D); // real elements are converted to complex silently here -``` -In many instances, specially in generic code, it might still be a desirable behavoir. - -To prevent implicit conversions, use element types with no implicit conversions when possible. - -Finally, arrays of unrelated element types are prevented from producing direct conversions, resulting in compilation errors. -This type of conversions can be defined as element-wise transformations if necessary. -For example, to convert an array of integers to an array of text strings: - -```cpp - multi::array const A = {{1, 2}, {3, 4}}; - - auto to_string = [](int e) {return std::to_string(e);}; - multi::array B = A.element_transformed(to_string); -``` - -## Const-correctness - -Const-correctness refers to the property of a program that disallows object mutation when it is not desired or logically incorrect. -Honoring the constness declaration is fundamental not only to avoid bugs and typos but also for thread safety and generic programming. -The library goes to great lengths to ensure const-correctness for the whole or parts of any object. - -An array can be declared constant using the keyword `const`. -A reference array (`array_ref`) is never resizable (or reassignable), but its elements are, in principle, mutable. - -The design ensures that the constness of references and values is propagated to subarrays (views) and, ultimately, their elements. - -```cpp -template -void print(Array1D const& coll) { -// *coll.begin() = 99; // doesn't compile, "assignment of read-only location" - - for(auto const& e : coll) {std::cout<< e <<", ";} - std::cout << std::endl; -} - -int main() { - multi::array const coll1 = {0, 8, 15, 47, 11, 42}; - - print( coll1 ); // prints "0, 8, 15, 47, 11, 42" - print( coll1({0, 3}) ); // prints "0, 8, 15" -} -``` - -As a general rule for passing generic arrays as arguments, pass them as `Array const&` (in the context of `template`); -unless mutation is expected, in which case take arguments as `Array&&` (note the double ampersand, i.e., universal/forwarding reference). -Analogously, views can be *named* into "constant language references" using `auto const&` and, if mutation is desired, `auto&&` should be used. -Regular references `Array&` or `auto&` in general do not have the expected behavior for views. - -```cpp -template -void fill_99(Array1D&& coll) { - for(auto& e : coll) { e = 99; } -} - -int main() { - multi::array coll1 = {0, 8, 15, 47, 11, 42}; - - fill_99( coll1 ); - fill_99( coll1({0, 3}) ); - - auto&& coll1_take3 = coll1({0, 3}); - fill_99( coll1_take3 ); - - auto const& coll2 = coll1; -// fill_99( coll2 ); // doesn't compile because coll2 is const -// fill_99( coll2({0, 3}) ); // similar to coll2 | take(3) doesn't compile - - auto const& coll1_take3_const = coll1({0, 3}); -// fill_99( coll1_take3_const ); // doesn't compile because coll1_take3_const is const -} -``` - -## Partially formed elements - -The library can take advantage of types with [partially formed](https://marcmutz.wordpress.com/tag/partially-formed-state/) state when -elements are trivial to construct (e.g., built-in types). -In such cases, `multi::array` does not initialize individual elements unless specified. -If trivial construction is unavailable, the library uses the default constructor. - -For example, after construction, the values of the six elements of this array are unspecified (partially formed). -```cpp -multi::array A2({2, 3}); -``` - -No behavior of the program should depend on these values. -(Address sanitizers and memory checkers can detect this.) -This design is a slight departure from the STL's design, which [immediatelly initializes elements in containers](https://lemire.me/blog/2012/06/20/do-not-waste-time-with-stl-vectors/). - -For types that afford partially formed states, elements can be later specified via assignment or assigning algorithms (e.g., copy or transform destination). -Initialization can be enforced by passing a value argument after the extensions. -```cpp -multi::array A2({2, 3}, 0); // generically multi::array({2, 3}, T{}); or multi::array({2, 3}, {}) -``` - -This design is particularly advantageous for *numeric* types for which external low-level libraries can fill values. -(or when data sits in GPUs, where the initialization step would require an expensive kernel launch and subsequent synchronization). - -Unfortunately, regarding the numeric types, STL's `std::complex` was standardized as not-trivially constructible. -A workaround is possible by forcing a particular flag on the client code in global scope, for example, immediately after including the library: -```cpp -#include -... -template<> inline constexpr -bool multi::force_element_trivial_default_construction> = true; -``` - -With this line, `std::complex` elements inside arrays will be left uninitialized unless a value is specified. -The rule will only apply to this library’s contains, not to other containers, such as `std::vector` or indivial `std::complex` elements. - -## Type Requirements - -Thelibrary design tries to impose the minimum possible requirements over the types that parameterize the arrays. -Array operations assume that the contained type (element type) are regular (i.e. different element represent disjoint entities that behave like values). -Pointer-like random access types can be used as substitutes of built-in pointers. -(Therefore pointers to special memory and fancy-pointers are supported.) - -### Linear Sequences: Pointers - -An `array_ref` can reference an arbitrary random access linear sequence (e.g. memory block defined by pointer and size). -This way, any linear sequence (e.g. `raw memory`, `std::vector`, `std::queue`) can be efficiently arranged as a multidimensional array. - -```cpp -std::vector buffer(100); -multi::array_ref A({10, 10}, buffer.data()); -A[1][1] = 9.0; - -assert( buffer[11] == 9.0 ); // the target memory is affected -``` -Since `array_ref` does not manage the memory associated with it, the reference can be simply dangle if the `buffer` memory is reallocated (e.g. by vector-`resize` in this case). - -### Special Memory: Pointers and Views - -`array`s managate their memory behind the scenes through allocators, which can be specified at construction. -It can handle special memory, as long as the underlying types behave coherently, these include [fancy pointers](https://en.cppreference.com/w/cpp/named_req/Allocator#Fancy_pointers) (and fancy references). -Associated fancy pointers and fancy reference (if any) are deduced from the allocator types. -Another use of fancy pointer is to create by-element "projections". - -#### Allocators and Fancy Pointers - -Specific uses of fancy memory are file-mapped memory or interprocess shared memory. -This example illustrates memory persistency by combining with Boost.Interprocess library. -The arrays support their allocators and fancy pointers (`boost::interprocess::offset_ptr`). - -```cpp -#include -using namespace boost::interprocess; -using manager = managed_mapped_file; -template using mallocator = allocator; -decltype(auto) get_allocator(manager& m) {return m.get_segment_manager();} - -template using marray = multi::array>; - -int main() { -{ - manager m{create_only, "mapped_file.bin", 1 << 25}; - auto&& arr2d = *m.construct>("arr2d")(marray::extensions_type{1000, 1000}, 0.0, get_allocator(m)); - arr2d[4][5] = 45.001; -} -// imagine execution restarts here, the file "mapped_file.bin" persists -{ - manager m{open_only, "mapped_file.bin"}; - auto&& arr2d = *m.find>("arr2d").first; - assert( arr2d[7][8] == 0. ); - assert( arr2d[4][5] == 45.001 ); - m.destroy>("arr2d"); -} -} -``` - -(See also, examples of interactions with the CUDA Thrust library to see more uses of special pointer types to handle special memory.) - -#### Transformed views - -Another kind of fancy-pointer is one that transforms the underlying values. -These are useful to create "projections" or "views" of data elements. -In the following example a "transforming pointer" is used to create a conjugated view of the elements. -In combination with transposed view, it can create a hermitic (transposed-conjugate) view of the matrix (without copying elements). -We can adapt the library type `boost::transform_iterator` to save coding, but other libraries can be used also. -The hermitized view is read-only, but with additional work a read-write view can be created (see `multi::blas::hermitized` in multi-adaptors). - -```cpp -constexpr auto conj = [](auto const& c) -> auto const {return std::conj(c);}; - -template struct conjr : boost::transform_iterator { - template conjr(As const&... as) : boost::transform_iterator{as...} {} -}; - -template -auto hermitized(Array2D const& arr) { - return arr - .transposed() // lazily tranposes the array - .template static_array_cast>(conj) // lazy conjugate elements - ; -} - -int main() { - using namespace std::complex_literals; - multi::array A = { - { 1. + 2.i, 3. + 4.i}, - { 8. + 9.i, 10. + 11.i} - }; - - auto const& Ah = hermitized(A); - - assert( Ah[1][0] == std::conj(A[0][1]) ); -} -``` - -To simplify this bolier plate, the library provides the `.element_transformed(F)` method that will apply a transformation `F` to each element of the array. -In this example the original arrays is transformed into a transposed array with duplicated elements. - -```cpp - multi::array A = { - {1.0, 2.0}, - {3.0, 4.0}, - }; - - auto const scale = [](auto x) { return x * 2.0; }; - - auto B = + A.rotated().element_transformed(scale); - assert( B[1][0] == A[0][1] * 2 ); -``` -([live](https://godbolt.org/z/b7E56Mjc8)) - -# Interoperability with other software - -## STL (Standard Template Library) - -The fundamental goal of the library is that the arrays and iterators can be used with STL algorithms out-of-the-box with a reasonable efficiency. -The most dramatic example of this is that `std::sort` works with array as it is shown in a previous example. - -Along with STL itself, the library tries to interact with other existing quality C++ libraries listed below. - -### Ranges (C++20) - -Although no exhaustive test has been performed, the library is expected to work with STL ranges. -The library works well with Ranges-v3 which is approximately a superset of STL ranges (see example below). - -### Polymorphic Memory Resources - -The library is compatible with C++17's polymorphic memory resources (PMR) which allows using preallocated buffers as described in this example. -This enables the use of stack memory, with many performance advantaneges. -For example, this code uses a buffer to allocate memory for two arrays, we will see how this buffer ends up containing the data of the arrays `"aaaabbbbbbXX"`. - -```cpp -#include // for polymorphic memory resource, monotonic buffer - -int main() { - char buffer[13] = "XXXXXXXXXXXX"; // a small buffer on the stack - std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; - - multi::pmr::array A({2, 2}, 'a', &pool); - multi::pmr::array B({3, 2}, 'b', &pool); - - assert( buffer == std::string{"aaaabbbbbbXX"} ); -} -``` -(`multi::pmr::array` is a synonym for `multi::array>`.) - -The library supports classic allocators (`std::allocator` by default) and also allocators from other libraries (see [CUDA Thrust](#cuda-thrust) Thurst section). - -### Substitutability with standard vector and span - -The one-dimensional case `multi::array` is special and overlaps functionality with other dynamic array implementations, such as `std::vector`. -Indeed, both types of containers are similar and usually substitutable, with no or minor modifications. -For example, both can be constructed from a list of elements (`C c = {x0, x2, ...};`) or from a size `C c(size);`. - -Both are assignable, have the same element access patterns and iterator interface, and implement all (lexical) comparisons. - -They differ conceptually in their resizing operations: `multi::array` doesn't insert or push elements and resize works differently. -The discrepancy is that the library doesn't implement *amortized* allocations; therefore, these operations would be of a higher complexity cost than the `std::vector`. -For this reason, `resize(new_size)` is replaced with `reextent({new_size})` in `multi::array`, which primary function is for element preservation when necessary. - -With the appropriate specification of the memory allocator, `multi::array` can refer to special memory not supported by `std::vector`. - -Finally, an array can be copied by `std::vector v(A1D.begin(), A1D.end());` or `v.assign(A1D.begin(), A1D.end());` or viseversa. -Without copying, a reference to the underlying memory can be created `auto&& R1D = multi::array_ref(v.data(), v.size());` or conversely `std::span(A1D.data_elements(), A1D.num_elements());`. -(See examples [here](https://godbolt.org/z/n4TY998o4).) - -The `std::span` semantics (C++20) is not well defined as a reference- or pointer-like type; it doesn't respect `const` correctness in generic code. -This behavior is contrary to the goals of this library; -and for this reason, there is no single substitute for `std::span` for all cases. -Depending on how it is used, either `multi::array_ref [const& | &&]` or `multi::array_ptr` may replace the features of `std::span`. -The former typically works when using it as function argument. - -## Comparison to other dynamic array libraries - -The C++23 standard is projected to provide `mdspan`, a non-owning _multidimensional_ array. -So here is an appropriate point to compare the two libraries. -Although the goals are similar, the two libraries differ in their generality and approach; in a few words: - -The Multi library concentrates on _well-defined value- and reference-semantics of arbitrary memory types with regularly arranged elements_ (distributions described by strides and offsets) and _extreme compatibility with STL algorithms_ (via iterators) and other fundamental libraries. - -`mdspan` concentrates on _arbitrary layouts_ for non-owning memory of a single type (CPU raw pointers). -Due to the priority of arbitrary layouts, the `mdspan` research team didn't find efficient ways to introduce iterators into the library. -Therefore, its compatibility with the rest of the STL is lacking. -The ultimate reason is that arbitrary layouts do not compose well across subdimensions, and, in turn, this imposes certain limitations in `mdspan`, such as ad-hoc slicing and subarray. - -[Eigen](https://eigen.tuxfamily.org/index.php?title=Main_Page) is a very popular matrix linear algebra library, and as such, it only handles the special 2D (and 1D) array case. -Instead, the Multi library is dimension-generic and doesn't make any algebraic assumptions for arrays or contained elements (but still can be used to _implement_ dense linear algebra algorithms.) - -Here is a table comparing with `mdspan`, R. Garcia's [Boost.MultiArray](https://www.boost.org/doc/libs/1_82_0/libs/multi_array/doc/user.html) and Eigen. -[(online)](https://godbolt.org/z/555893MqW). - - -| | Multi | mdspan | Boost.MultiArray (R. Garcia) | Inria's Eigen | -|--- | --- | --- | --- | --- | -| No external Deps | **yes** (only Standard Library C++17) | **yes** (only Standard Library) | **yes** (only Boost) | **yes** | -| Arbritary number of dims | **yes**, via positive dimension (compile-time) parameter `D` | **yes** | **yes** | no (only 1D and 2D) | -| Non-owning view of data | **yes**, via `multi::array_ref(ptr, {n1, n2, ..., nD})` | **yes**, via `mdspan m{T*, extents{n1, n2, ..., nD}};` | **yes**, via `boost::multi_array_ref(T*, boost::extents[n1][n2]...[nD])` | **yes**, via `Eigen::Map>(ptr, n1, n2)` | -| Compile-time dim size | no | **yes**, via template paramaters `mdspan{T*, extent<16, dynamic_extents>{32} }` | no | **yes**, via `Eigen::Array` | -| Array values (owning data) | **yes**, via `multi::array({n1, n2, ..., nD})` | no, (planned `mdarray`) | **yes**, via `boost::multi_array(boost::extents[n1][n2]...[nD])` | **yes**, via `Eigen::Array(n1, n2)` | -| Value semantic (Regular) | **yes**, via cctor, mctor, assign, massign, auto decay of views | no, and not planned | partial, assigment on equal extensions | **yes** (?) | -| Move semantic | **yes**, via mctor and massign | no | no (C++98 library) | **yes** (?) | -| const-propagation semantics | **yes**, via `const` or `const&` | no, const mdspan elements are assignable! | no, inconsistent | (?) | -| Element initialization | **yes**, via nested init-list | no | no | no, only delayed init via `A << v1, v2, ...;` | -| References w/no-rebinding | **yes**, assignment is deep | no, assignment of mdspan rebinds! | **yes** | **yes** (?) | -| Element access | **yes**, via `A(i, j, ...)` or `A[i][j]...` | **yes**, via `A(i, j, ...)` | **yes**, via `A[i][j]...` | **yes**, via `A(i, j)` (2D only) | -| Partial element access | **yes**, via `A[i]` or `A(i, multi::all)` | **yes**, via `submdspan(A, i, full_extent)` | **yes**, via `A[i]` | **yes**, via `A.row(i)` | -| Subarray views | **yes**, via `A({0, 2}, {1, 3})` or `A(1, {1, 3})` | **yes**, via `submdspan(A, std::tuple{0, 2}, std::tuple{1, 3})` | **yes**, via `A[indices[range(0, 2)][range(1, 3)]]` | **yes**, via `A.block(i, j, di, dj)` | -| Subarray with lower dim | **yes**, via `A(1, {1, 3})` | **yes**, via `submdspan(A, 1, std::tuple{1, 3})` | **yes**, via `A[1][indices[range(1, 3)]]` | **yes**, via `A(1, Eigen::placeholders::all)` | -| Subarray w/well def layout | **yes** (strided layout) | no | **yes** (strided layout) | **yes** (strided) | -| Recursive subarray | **yes** (layout is stack-based and owned by the view) | **yes** (?) | no (subarray may dangle layout, design bug?) | **yes** (?) (1D only) | -| Custom Alloctors | **yes**, via `multi::array` | no (no allocation or ownership) | **yes** (stateless?) | no | -| PMR Alloctors | **yes**, via `multi::pmr::array` | no (no allocation or ownership) | no | no | -| Fancy pointers / references | **yes**, via `multi::array` or views | no | no | no | -| Strided Layout | **yes** | **yes** | **yes** | **yes** | -| Fortran-ordering | **yes**, only for views, e.g. resulted from transposed views | **yes** (only views are supported) | **yes** | **yes** | -| Zig-zag / Hilbert ordering | no | **yes**, via arbitrary layouts (no inverse or flattening) | no | no | -| Arbitrary layout | no | **yes**, possibly inneficient, no efficient slicing | no | no | -| Flattening of elements | **yes**, via `A.elements()` range (efficient representation) | **yes**, but via indices roundtrip (inefficient) | no, only for allocated arrays | no, not for subblocks (?) | -| Iterators | **yes**, standard compliant, random-access-iterator | no, or very limited | **yes**, limited | no | -| Multidimensional iterators (cursors) | **yes** (experimental) | no | no | no | -| STL algorithms or Ranges | **yes** | no, limited via `std::cartesian_product` | **yes**, some do not work | no | -| Compatibility with Boost | **yes**, serialization, interprocess (see below) | no | no | no | -| Compatibility with Thrust or GPUs | **yes**, via flatten views (loop fusion), thrust-pointers/-refs | no | no | no | -| Used in production | [QMCPACK](https://qmcpack.org/), [INQ](https://gitlab.com/npneq/inq) | (?) , experience from Kokkos incarnation | **yes** (?) | [**yes**](https://eigen.tuxfamily.org/index.php?title=Main_Page#Projects_using_Eigen) | - -## Serialization - -The capability of serializing arrays is important to save/load data to/from disk and also to communicate values via streams or networks (including MPI). -The C++ language does not give any facilities for serialization and unfortunately the standard library doesn't either. - -However there are a few libraries that offer a certain common protocol for serialization, -such as [Boost.Serialization](https://www.boost.org/doc/libs/1_76_0/libs/serialization/doc/index.html) and [Cereal](https://uscilab.github.io/cereal/). -The Multi library is compatible with both of them, and yet it doesn't depend on any of them. -The user can choose one or the other, or none if serialization is not needed. -The generic protocol is such that variables are (de)serialized using the (`>>`)`<<` operator with the archive; operator `&` can be used to have single code for both. -Serialization can be binary (efficient) or text-based (human readable). - -Here it is a small implementation of save and load functions for array to JSON format with Cereal. -The example can be easily adapted to other formats or libries (XML with Boost.Serialization are commented on the right). - -```cpp -#include // this library - -#include // #include - // #include -// for serialization of array elements (in this case strings) -#include // #include - -#include // saving to files in example - -using input_archive = cereal::JSONInputArchive ; // boost::archive::xml_iarchive; -using output_archive = cereal::JSONOutputArchive; // boost::archive::xml_oarchive; -using cereal::make_nvp; // boost::serialization::make_nvp; - -namespace multi = boost::multi; - -template -auto array_load(IStream&& is) { - multi::array value; - input_archive{is} >> make_nvp("value", value); - return value; -} - -template -void array_save(OStream&& os, multi::array const& value) { - output_archive{os} << make_nvp("value", value); -} - -int main() { - multi::array const A = {{"w", "x"}, {"y", "z"}}; - array_save(std::ofstream{"file.string2D.json"}, A); // use std::cout to print serialization to the screen - - auto const B = array_load(std::ifstream{"file.string2D.json"}); - assert(A == B); -} -``` -[(online)](https://godbolt.org/z/9j9avjh8M) - -These templated functions work for any dimension and element type (as long as the element type is serializable in itself; all basic types are serializable by default). -However, note that the user must ensure that data is serialized and deserialized into the same type; -the underlying serialization libraries only do minimal consistency checks for efficiency reasons and don't try to second-guess file formats or contained types. -Serialization is a relatively low-level feature for which efficiency and economy of bytes is a priority. -Cryptic errors and crashes can occur if serialization libraries, file formats, or C++ types are mixed between writes and reads. - -References to subarrays (views) can also be serialized; however, size information is not saved in such cases. -The reasoning is that references to subarrays cannot be resized in their number of elements if there is a size mismatch during deserialization. -Therefore, array views should be deserialized as other array views, with matching sizes. - -The output JSON file of the previous example looks like this. -(The XML would have a similar structure.) - -```json -{ - "value": { - "cereal_class_version": 0, - "extensions": { - "cereal_class_version": 0, - "extension": { - "cereal_class_version": 0, - "first": 0, - "last": 2 - }, - "extension": { - "first": 0, - "last": 2 - } - }, - "elements": { - "cereal_class_version": 0, - "item": "w", - "item": "x", - "item": "y", - "item": "z" - } - } -} -``` - -Large datasets tend to be serialized slowly for archives with heavy formatting. -Here it is a comparison of speeds when (de)serializing a 134 MB 4-dimensional array of with random `double`s. - -| Archive format (Library) | file size | speed (read - write) | time (read - write) | -| ---------------------------- | ------------- | ------------------------------ |-----------------------| -| JSON (Cereal) | 684 MB | 3.9 MB/sec - 8.4 MB/sec | 32.1 sec - 15.1 sec | -| XML (Cereal) | 612 MB | 2. MB/sec - 4. MB/sec | 56 sec - 28 sec | -| XML (Boost) | 662 MB | 11. MB/sec - 13. MB/sec | 11 sec - 9 sec | -| YAML ([custom archive)](https://gitlab.com/correaa/boost-archive-yml) | 702 MB | 10. MB/sec - 4.4 MB/sec | 12 sec - 28 sec | -| Portable Binary (Cereal) | 134 MB | 130. MB/sec - 121. MB/sec | 9.7 sec - 10.6 sec | -| Text (Boost) | 411 MB | 15. MB/sec - 16. MB/sec | 8.2 sec - 7.6 sec | -| Binary (Cereal) | 134 MB | 134.4 MB/sec - 126. MB/sec | 0.9 sec - 0.9 sec | -| Binary (Boost) | 134 MB | 5200. MB/sec - 1600. MB/sec | 0.02 sec - 0.1 sec | -| gzip-XML (Cereal) | 191 MB | 2. MB/sec - 4. MB/sec | 61 sec - 32 sec | -| gzip-XML (Boost) | 207 MB | 8. MB/sec - 8. MB/sec | 16.1 sec - 15.9 sec | - -## Range-v3 - -The library works out of the box with Eric Niebler's Range-v3 library. -The library helps removing explicit iterators (e.g. `begin`, `end`) from the code when possible. - -Every Multi array object can be regarded as range. -Every subarray references (and array values) are interpreted as range views. - -For example for a 2D array `d2D`, `d2D` itself is interpreted as a range of rows. -Each row, in turn, is interpreted as a range of elements. -In this way, `d2D.transposed()` is interpreted as a range of columns (of the original array), and each column a range of elements (arranged vertically in the original array). - -```cpp -#include -int main(){ - - multi::array const d2D = { - { 0, 1, 2, 3}, - { 5, 6, 7, 8}, - {10, 11, 12, 13}, - {15, 16, 17, 18} - }; - assert( ranges::inner_product(d2D[0], d2D[1], 0.) == 6+2*7+3*8 ); - assert( ranges::inner_product(d2D[0], rotated(d2D)[0], 0.) == 1*5+2*10+15*3 ); - - static_assert(ranges::RandomAccessIterator::iterator>{}); - static_assert(ranges::RandomAccessIterator::iterator>{}); -} -``` - -In this other [example](https://godbolt.org/z/MTodPEnsr), a 2D Multi array (or subarray) is modified such that each element of a column is subtracted the mean value of such column. - -```cpp -#include -#include - -template -void subtract_mean_columnwise(MultiArray2D&& arr) { - auto&& tarr = arr.transposed(); - auto const column_mean = - tarr - | ranges::views::transform([](auto const& row) {return ranges::accumulate(row, 0.0)/row.size();}) - | ranges::to> - ; - - ranges::transform( - arr.elements(), - column_mean | ranges::views::cycle, - arr.elements().begin(), - [](auto const elem, auto const mean) {return elem - mean;} - ); -} -``` - -## Boost.Interprocess - -Using Interprocess allows for shared memory and for persistent mapped memory. - -```cpp -#include -#include "multi/array.hpp" -#include - -namespace bip = boost::interprocess; -using manager = bip::managed_mapped_file; -template using mallocator = bip::allocator; -auto get_allocator(manager& m){return m.get_segment_manager();} - -namespace multi = boost::multi; -template using marray = multi::array>; - -int main(){ -{ - manager m{bip::create_only, "bip_mapped_file.bin", 1 << 25}; - auto&& arr2d = *m.construct>("arr2d")(std::tuple{1000, 1000}, 0., get_allocator(m)); - arr2d[4][5] = 45.001; - m.flush(); -} -{ - manager m{bip::open_only, "bip_mapped_file.bin"}; - auto&& arr2d = *m.find>("arr2d").first; - assert( arr2d[4][5] == 45.001 ); - m.destroy>("arr2d");// eliminate>(m, "arr2d");} -} -} -``` - -(Similarly works with [LLNL's Meta Allocator](https://github.com/llnl/metall)) - -## CUDA Thrust (and HIP Thrust) - -The library works out-of-the-box in combination with the Thrust library. - -```cpp -#include // this library - -#include // from CUDA or ROCm distributions - -namespace multi = boost::multi; - -int main() { - multi::array> A({10,10}); - multi::array> B({10,10}); - A[5][0] = 50.0; - - thrust::copy(A.rotated()[0].begin(), A.rotated()[0].end(), B.rotated()[0].begin()); // copy row 0 - assert( B[5][0] == 50.0 ); -} -``` -[(live)](https://godbolt.org/z/e7bjKqh69) - -which uses the default Thrust device backend (i.e. CUDA when compiling with `nvcc`, HIP/ROCm when compiling with a HIP/ROCm compiler, or OpenMP or TBB in other cases). -Universal memory (accessible from normal CPU code) can be used with `thrust::universal_allocator` (from ``) instead. - -More specific allocators can be used ensure CUDA backends, for example CUDA managed memory: - -```cpp -#include -... - multi::array> A({10,10}); -``` - -In the same way, to *ensure* HIP backends please replace the `cuda` namespace by the `hip` namespace, and in the directory name ``. -`` is provided by the ROCm distribution (in `/opt/rocm/include/thrust/system/hip/`, and not by the NVIDIA distribution.) - -Multi doesn't have a dependency on Thrust (or viseversa); -they just work well together, both in terms of semantics and efficiency. -Certain "patches" (to improve Thrust behavior) can be applied to Thrust to gain extra efficiency and achieve near native speed by adding the `#include`. - -Multi can be used on existing memory in a non-invasive way via (non-owning) reference arrays: - -```cpp - // assumes raw_pointer was allocated with cudaMalloc or hipMalloc - using gpu_ptr = thrust::cuda::pointer; // or thrust::hip::pointer - multi::array_ref Aref({n, n}, gpu_ptr{raw_pointer}); -``` - -Finally, the element type of the device array has to be device-friendly to work correctly; -this includes all build in types, and classes with basic device operations, such as construction, destruction, and assigment. -They notably do not include `std::complex`, in which can be replaced by the device-friendly `thrust::complex` can be used as replacement. - -### Thrust memory resources - -GPU memory is relative expensive to allocate, therefore any application that allocates and deallocates arrays often will suffer performance issue. -This is where special memory management is important, for example for avoiding real allocations when possible by caching and reusing memory blocks. - -Thrust implements both polymorphic and non-polymorphic memory resources via `thrust::mr::allocator`; -Multi supports both. - -```cpp -auto pool = thrust::mr::disjoint_unsynchronized_pool_resource( - thrust::mr::get_global_resource(), - thrust::mr::get_global_resource() -); - -// memory is handled by pool, not by the system allocator -multi::array> arr({1000 - i%10, 1000 + i%10}, &pool); // or multi::mr::array for short -``` - -The associated pointer type for the array data is deduced from the _upstream_ resource; in this case, `thrust::universal_ptr`. - -As as quick recipe to improve performance in many cases, here it is a recipe for a `caching_allocator` which uses a global (one per thread) memory pool. -The requested memory resides in GPU (managed) memory (`thrust::cuda::universal_memory_resource`) while the cache _bookkeeping_ is held in CPU memory (`new_delete_resource`). - -```cpp -template>>> -struct caching_allocator : Base_ { - caching_allocator() : - Base_{&thrust::mr::tls_disjoint_pool( - thrust::mr::get_global_resource(), - thrust::mr::get_global_resource() - )} {} - caching_allocator(caching_allocator const&) : caching_allocator{} {} // all caching allocator are equal - template struct rebind {using other = caching_allocator;}; -}; -... -int main() { - ... - using array2D = multi::array>; - - for(int i = 0; i != 10; ++i) { array2D A({100, 100}); ... use A ...} -} -``` - -In the example, most of the memory requests are handled by reutilizing the memory pool avoiding expensive system allocations. -More targeted usage patterns may require locally (non-globally) defined memory resources. - -## CUDA C++ - -CUDA is a dialect of C++ that allows writing pieces of code directly for GPU execution, known as "CUDA kernels". -CUDA code is generally "low level" (less abstracted) but it can be used in combination with CUDA Thrust or the CUDA runtime library, specially to implement algorithm that are hard to implement otherwise. -Although code inside kernels has certain restrictions, most Multi expressions can be used. -(Most functions in Multi, except those involving memory allocations, are marked `__device__` to allow this.) - -Calling kernels involves a special syntax (`<<< ... >>>`), and they cannot take arguments by reference (or by values that are not trivial, e.g. not entirely contained in the stack). -Since arrays are usually passed by reference (e.g. `multi::array&` or `Array&&`), a different idiom needs to be used. -(Large arrays are not passed by value to avoid copies, but even if a copy would be fine, kernel arguments cannot allocate memory themselves.) -Iterators (e.g. `.begin()/.end()`) and "cursors" (e.g. `.home()`) are "trivial to copy" and can be passed by value and represent a "proxy" to an array, including allowing the normal index syntax and other transformations. - -Cursors are a generalization of iterators for multiple dimensions. -They are cheaply copied (like iterators) and they allow indexing. -Also, they have no associated `.size()` or `.extensions()`, but this is generally fine for kernels. - -Here it is an example implementation for matrix multiplication, in combination with Thrust and Multi, - -```cpp -#include // from https://gitlab.com/correaa/boost-multi -#include // for thrust::cuda::allocator - -template -__global__ void Kernel(ACursor A, BCursor B, CCursor C, int N) { - int x = threadIdx.x + blockIdx.x * blockDim.x; - int y = threadIdx.y + blockIdx.y * blockDim.y; - - typename CCursor::element_type value{0.0}; - for (int k = 0; k != N; ++k) { value += A[y][k] * B[k][x]; } - C[y][x] = value; -} - -namespace multi = boost::multi; - -int main() { - int N = 1024; - - // declare 3 square arrays - multi::array> A({N, N}); A[0][0] = ...; - multi::array> B({N, N}); B[0][0] = ...; - multi::array> C({N, N}); - - // kernel invocation code - assert(N % 32 == 0); - dim3 dimBlock(32, 32); - dim3 dimGrid(N/32, N/32); - Kernel<<>>(A.home(), B.home(), C.home(), N); - cudaDeviceSynchronize(); - - // now C = A x B -} -``` -[(live)](https://godbolt.org/z/eKbeosrWa) - -## TotalView - -TotalView visual debugger (commercial), popular in HPC environments, can display arrays in human-readable form (for simple types, like `double` or `std::complex`). -To use it, simply `#include "multi/adaptors/totalview.hpp"` and link to the TotalView libraries, compile and run the code with the TotalView debugger. - -# Technical points - -### What's up with the multiple bracket notation? - -The chained bracket notation (`A[i][j][k]`) allows to refer to elements and subarrays lower dimensional subarrays in a consistent and _generic_ manner and it is the recommended way to access the array objects. -It is a frequently raised question whether the chained bracket notation is good for performance, since it appears that each utilization of the bracket leads to the creation of a temporary object which in turn generates a partial copy of the layout. -Moreover, this goes against [historical recommendations](https://isocpp.org/wiki/faq/operator-overloading#matrix-subscript-op). - -It turns out that modern compilers with a fair level of optimization (`-O2`) can elide these temporary objects, so that `A[i][j][k]` generates identical assembly code as `A.base() + i*stride1 + j*stride2 + k*stride3` (+offsets not shown). -In a subsequent optimization, constant indices can have their "partial stride" computation removed from loops. -As a result, these two loops lead to the [same machine code](https://godbolt.org/z/ncqrjnMvo): - -```cpp - // given the values of i and k and accumulating variable acc ... - for(long j = 0; j != M; ++j) {acc += A[i][j][k];} -``` -```cpp - auto* base = A.base() + i*std::get<0>(A.strides()) + k*std::get<2>(A.strides()); - for(long j = 0; j != M; ++j) {acc += *(base + j*std::get<1>(A.strides()));} -``` - -Incidentally, the library also supports parenthesis notation with multiple indices `A(i, j, k)` for element or partial access, but it does so as part of a more general syntax to generate sub-blocks. -In any case `A(i, j, k)` is expanded to `A[i][j][k]` internally in the library when `i, j, k` are normal integer indices. -Additionally, array coordinates can be directly stored in tuple-like data structures, allowing this functional syntax: - -```cpp -std::array p = {2, 3, 4}; -std::apply(A, p) = 234; // same as A(2, 3, 4) = 234; and same as A[2][3][4] = 234; -``` - -### Customizing recursive operations: SCARY iterators - -A level of customization can be achieved by intercepting internal recursive algorithms. -Multi iterators are [SCARY](http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2009/n2980.pdf). -SCARY means that they are independent of any container and can be accessed generically through their dimension and underlying pointer types: - -For example, `boost::multi::array_iterator it` is a row (or column) iterator of an array of dimension 2 or higher, whose underlying pointer type is `double*`. -This row (or column) and subsequent ones can be accessed by the normal iterator(pointer) notation `*it` and `it[n]` respectively. -Indirection `it->...` is supported (even for iterators if high dimension). -The base pointer, the strides and the size of the arrow can be accessed by `base(it)`, `stride(it)`, `it->size()`. - -The template arguments of the iterator can be used to customize operations that are recursive (and possibly inefficient in certain context) in the library: - -```cpp -namespace boost{namespace multi{ -template // custom copy 1D (aka strided copy) -void copy(It first, It last, multi::array_iterator > dest){ - assert( stride(first) == stride(last) ); - std::cerr<<"1D copy(it1D, it1D, it1D) with strides "<< stride(first) <<" "<< stride(dest) < // custom copy 2D (aka double strided copy) -void copy(It first, It last, multi::array_iterator > dest){ - assert( stride(first) == stride(last) ); - std::cerr<<"2D copy(It, It, it2D) with strides "<< stride(first) <<" "<< stride(dest) < $0.html) && firefox --new-window $0.html; sleep 5; rm $0.html; exit +--> + +**[Boost.] Multi** + +> **Disclosure: This is not an official or accepted Boost library and is unrelated to the std::mdspan proposal. It is in the process of being proposed for inclusion in [Boost](https://www.boost.org/) and it doesn't depend on Boost libraries.** + +_© Alfredo A. Correa, 2018-2025_ + +_Multi_ is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory. + +```cpp +#include // for assert +#include // from https://gitlab.com/correaa/boost-multi or https://gitlab.com/correaa/boost-multi + +namespace multi = boost::multi; + +int main() { + multi::array A = {{1, 2, 3}, {4, 5, 6}}; // 2D array of integers + + assert(A.size() == 2); // the array has 2 rows + assert(A.size() == A.end() - A.begin()); // interators to rows + + assert(A[1][1] == 5); // element access through indexing + + assert(A.elements().size() == 2 * 3); // array has 6 elements + assert(A.elements()[4] == 5); // elements gives "flat" sequences +} +``` +[(online)](https://godbolt.org/z/6cnqzK4ah) + +## Learn about Multi + +* [Online documentation](https://correaa.gitlab.io/boost-multi/multi.html) + +## Try Multi + +Before installing the library, you can try it [online](https://godbolt.org/z/dvacqK8jE) through the Godbolt's Compiler Explorer. + +## Install Multi + +_Multi_ has no external dependencies and can be used immediately after downloading. +```bash +git clone https://gitlab.com/correaa/boost-multi.git +``` + +_Multi_ doesn't require installation since a single header is enough to use the entire core library; +```c++ +#include + +int main() { ... } +``` + +The library can be also installed with CMake. +The header (and CMake) files will be installed in the chosen prefix location (by default, `/usr/local/include/multi` and `/usr/local/share/multi`). +```bash +cd boost-multi +mkdir -p build && cd build +cmake . -B ./build # --install-prefix=$HOME/.local +cmake --install ./build # or sudo ... +``` + +_Testing_ the library requires Boost.Core (headers), installed for example, via `sudo apt install cmake git g++ libboost-test-dev make` or `sudo dnf install boost-devel cmake gcc-c++ git`. +A CMake build system is provided to compile and run basic tests. +```bash +ctest -C ./build +``` + +Once installed, other CMake projects (targets) can depend on Multi by adding a simple `add_subdirectory(my_multi_path)` or by `find_package`: +```cmake +find_package(multi) # see https://gitlab.com/correaa/boost-multi +``` + +Alternatively, the library can be fetched on demand: +```cmake +include(FetchContent) +FetchContent_Declare(multi GIT_REPOSITORY https://gitlab.com/correaa/boost-multi.git) +FetchContent_MakeAvailable(multi) +... +target_link_libraries(my_target PUBLIC multi) +``` + +## Support + +* File a Gitlab [issue](https://gitlab.com/correaa/boost-multi/-/issues/new?type=ISSUE) or Github [issue](https://github.com/correaa/boost-multi/issues/new/choose). +* Join the [**#boost-multi**](https://cpplang.slack.com/archives/C071VGKUA5P) discussion group at [cpplang.slack.com](https://cpplang.slack.com/) diff --git a/benchmark/algorithms_gemm.cpp b/benchmark/algorithms_gemm.cpp index df0e85f23..3c44d7534 100644 --- a/benchmark/algorithms_gemm.cpp +++ b/benchmark/algorithms_gemm.cpp @@ -1,13 +1,12 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// © Alfredo Correa 2021 +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi gemm (not blas)" -#define BOOST_TEST_DYN_LINK -#include +// #include -#include "../array.hpp" +#include -#include "../algorithms/gemm.hpp" +#include #include #include diff --git a/benchmark/array_ref_timing.cpp b/benchmark/array_ref_timing.cpp index e91d4dac2..f8f0b5934 100644 --- a/benchmark/array_ref_timing.cpp +++ b/benchmark/array_ref_timing.cpp @@ -6,7 +6,7 @@ clang++ -O3 -Ofast -std=c++14 -DNDEBUG -Wall -Wextra -Wpedantic -Werror $0 -o $0 #include -#include +#include #include #include #include @@ -16,7 +16,7 @@ namespace multi = boost::multi; int main(){ - assert(0); // check that NDEBUG is on + assert(0); // check that NDEBUG is off { std::ptrdiff_t NX = 20000; std::ptrdiff_t NY = 20000; @@ -25,12 +25,12 @@ int main(){ std::iota(begin(data), end(data), 0.); multi::array_cref data2D_cref({NX, NY}, data.data()); - cout << "size " << data.size()*sizeof(double)/1e6 << "MB\n"; + cout << "size " << static_cast(data.size()*sizeof(double))/1.0e6 << "MB\n"; iota(begin(data), end(data), 1.2); iota(begin(data), end(data), 10.1); data[1234] = 929.1; double sum_raw; { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{std::cerr, 3, "sum: raw %t seconds\n"}; for(auto const& e : data) sum += e; sum_raw = sum; @@ -38,12 +38,10 @@ int main(){ iota(begin(data), end(data), 1.2); iota(begin(data), end(data), 202.2); data[1234] = 399.1; double sum_2D; { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{std::cerr, 3, "sum: 2D %t seconds\n"}; auto ext = extensions(data2D_cref); for(auto i : std::get<0>(ext)){ - // auto const& data2D_crefi = data2D_cref[i]; // not necessary in any clang or gcc - // for(auto j : std::get<1>(ext)) sum += data2D_crefi[j]; for(auto j : std::get<1>(ext)) sum += data2D_cref(i, j); } sum_2D = sum; @@ -51,11 +49,11 @@ int main(){ iota(begin(data), end(data), 1.2); iota(begin(data), end(data), 2.21); data[1234] = 3299.1; double sum_2D_acc; { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{cerr, 3, "sum: 2D acc %t seconds\n"}; sum = std::accumulate( - begin(data2D_cref), end(data2D_cref), 0., - [](auto&& a, auto&& b){return a + std::accumulate(begin(b), end(b), 0.);} + begin(data2D_cref), end(data2D_cref), 0.0, + [](auto const& a, auto const& b){return a + std::accumulate(begin(b), end(b), 0.0);} ); sum_2D_acc = sum; } @@ -64,20 +62,18 @@ int main(){ { boost::timer::auto_cpu_timer t{cerr, 3, "sum: 2Dwrong acc %t seconds\n"}; sum_2Dwrong_acc = std::accumulate( - begin(rotated(data2D_cref)), end(rotated(data2D_cref)), 0., - [](auto&& a, auto&& b){return a + std::accumulate(begin(b), end(b), 0.);} + begin(rotated(data2D_cref)), end(rotated(data2D_cref)), 0.0, + [](auto const& a, auto const& b){return a + std::accumulate(begin(b), end(b), 0.0);} ); } iota(begin(data), end(data), 1.2); iota(begin(data), end(data), 11.2); data[1234] = 199.1; double sum_2Dwrong; { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{std::cerr, 3, "sum: 2Dwrong sum %t seconds\n"}; - auto ext = extensions(data2D_cref); - for(auto j : std::get<1>(ext)){ - // auto const& data2D_crefXj = data2D_cref.rotated(1)[j]; // not necessary in clang or gcc - for(auto i : std::get<0>(ext)){ - // sum += data2D_crefXj[i]; + auto const [is, js] = extensions(data2D_cref); + for(auto j : js){ + for(auto i : is){ sum += data2D_cref[i][j]; } } @@ -86,7 +82,7 @@ int main(){ iota(begin(data), end(data), 1.2); iota(begin(data), end(data), 10.112); data[1234] = 99.1; double sum_raw2; { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{cerr, 3, "sum: raw %t seconds\n"}; for(auto const& e : data) sum += e; sum_raw2 = sum; @@ -99,78 +95,86 @@ int main(){ } cout<< sum_2D + sum_2D_acc + sum_2Dwrong_acc + sum_2Dwrong + sum_raw + sum_raw2 + sum_raw_acc < v(NX*NY*NZ); - cout<<"3D data "<< v.size()*sizeof(double)/1e6 <<"MB\n"; + cout<<"3D data "<< static_cast(v.size()*sizeof(double))/1.0e6 <<"MB\n"; iota(begin(v), end(v), 0.1); multi::array_cref v3D_cref({NX, NY, NZ}, v.data()); assert( num_elements(v3D_cref) == std::ptrdiff_t(v.size()) ); { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{std::cerr, 3, "sum: 3D raw %t seconds\n"}; for(auto const& e : v) sum += e; cout << sum << '\n'; } iota(begin(v), end(v), 1.2); { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{std::cerr, 3, "sum: 3D indexed %t seconds\n"}; - auto ext = extensions(v3D_cref); - for(auto i : std::get<0>(ext)){ + auto const [is, js, ks] = extensions(v3D_cref); + for(auto i : is) { auto const& v3D_crefi = v3D_cref[i]; - for(auto j : std::get<1>(ext)){ + for(auto j : js) { auto const& v3D_crefij = v3D_crefi[j]; - for(auto k : std::get<2>(ext)) + for(auto k : ks) { sum += v3D_crefij[k]; + } } } cout << sum << '\n'; } iota(begin(v), end(v), 4444.5); { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{std::cerr, 3, "sum: 3Dwrong indexed %t seconds\n"}; - for(auto k : v3D_cref.extension(2)) + for(auto k : v3D_cref.extension(2)) // TODO(correaa) this doesn't work anymore for(auto j : v3D_cref.extension(1)) for(auto i : v3D_cref.extension(0)) sum += v3D_cref[i][j][k]; cout << sum << '\n'; } } -{ - std::ptrdiff_t NX = 150, NY = 150, NZ = 150, NA = 150; +{ + std::ptrdiff_t NX = 150; + std::ptrdiff_t NY = 150; + std::ptrdiff_t NZ = 150; + std::ptrdiff_t NA = 150; + std::vector v(NX*NY*NZ*NA); multi::array_cref v4D_cref({NX, NY, NZ, NA}, v.data()); assert( v4D_cref.num_elements() == std::ptrdiff_t(v.size()) ); cout<<"4D data "<< num_elements(v4D_cref)*sizeof(double)/1e6 <<"MB\n"; iota(begin(v), end(v), 0.1); { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{std::cerr, 3, "sum: 4D raw %t seconds\n"}; for(auto const& e : v) sum += e; cout<< sum <<'\n'; } iota(begin(v), end(v), 1222.1); { - double sum = 0.; + double sum = 0.0; boost::timer::auto_cpu_timer t{std::cerr, 3, "sum: 4D indexed %t seconds\n"}; - auto ext = extensions(v4D_cref); - for(auto i : std::get<0>(ext)){ + auto const [is, js, ks, ls] = extensions(v4D_cref); + for(auto i : is) { // auto const& v4D_crefi = v4D_cref[i]; // not necessary in clang or gcc - for(auto j : std::get<1>(ext)){ + for(auto j : js) { // auto const& v4D_crefij = v4D_crefi[j]; // not necessary in clang or gcc - for(auto k : std::get<2>(ext)){ + for(auto k : ks) { // auto const& v4D_crefijk = v4D_crefij[k]; // not necessary in clang or gcc - for(auto l : std::get<3>(ext)) + for(auto l : ls) { sum += v4D_cref[i][j][k][l]; + } } } } cout<< std::to_string(sum)[0] <<'\n'; } } - return 0; } diff --git a/benchmark/assignment.cpp b/benchmark/assignment_1d.cpp similarity index 59% rename from benchmark/assignment.cpp rename to benchmark/assignment_1d.cpp index 7ebeea0e5..095193a20 100644 --- a/benchmark/assignment.cpp +++ b/benchmark/assignment_1d.cpp @@ -1,10 +1,10 @@ #ifdef COMPILATION// sudo cpupower frequency-set --governor performance -$CXX -DNDEBUG `#-DNOEXCEPT_ASSIGNMENT` $0 -o $0x `pkg-config --libs benchmark`&&$0x&&rm $0x;exit +set -x; clang++ -std=c++17 -DNDEBUG -O3 `#-DNOEXCEPT_ASSIGNMENT` -I../include `pkg-config --cflags --libs benchmark` $0 -o $0x &&$0x&&rm $0x;exit #endif #include -#include "../array.hpp" +#include #include #include @@ -12,7 +12,7 @@ namespace multi = boost::multi; using complex = std::complex; -MAYBE_UNUSED constexpr complex I{0, 1}; +[[maybe_unused]] constexpr complex I{0.0, 1.0}; using T = complex; constexpr std::size_t N = 1 << 24; @@ -68,3 +68,23 @@ BENCHMARK_MAIN(); //MultiAssignment 53.7 ms 28.5 ms 26 //RawAssignment 52.0 ms 27.8 ms 23 +// 2024/11/16 running on Mac M3 Pro +// + clang++ -std=c++17 -DNDEBUG -O3 -I../include -I/opt/homebrew/Cellar/google-benchmark/1.9.0/include -L/opt/homebrew/Cellar/google-benchmark/1.9.0/lib -lbenchmark ./assignment.cpp -o ./assignment.cppx +// + ./assignment.cppx +// Unable to determine clock rate from sysctl: hw.cpufrequency: No such file or directory +// This does not affect benchmark measurements, only the metadata output. +// ***WARNING*** Failed to set thread affinity. Estimated CPU frequency may be incorrect. +// 2024-11-16T02:08:52-08:00 +// Running ./assignment.cppx +// Run on (16 X 24 MHz CPU s) +// CPU Caches: +// L1 Data 64 KiB +// L1 Instruction 128 KiB +// L2 Unified 4096 KiB (x16) +// Load Average: 3.26, 2.88, 2.98 +// ----------------------------------------------------------- +// Benchmark Time CPU Iterations +// ----------------------------------------------------------- +// VectorAssignment 4.35 ms 4.35 ms 161 +// MultiAssignment 4.35 ms 4.35 ms 164 +// RawAssignment 4.27 ms 4.27 ms 162 \ No newline at end of file diff --git a/benchmark/assignment_2d.cpp b/benchmark/assignment_2d.cpp new file mode 100644 index 000000000..6c8a7d4f5 --- /dev/null +++ b/benchmark/assignment_2d.cpp @@ -0,0 +1,126 @@ +#ifdef COMPILATION// sudo cpupower frequency-set --governor performance +set -x; clang++ -std=c++17 -DNDEBUG -O3 `#-DNOEXCEPT_ASSIGNMENT` -I../include `pkg-config --cflags --libs benchmark` $0 -o $0x &&$0x&&rm $0x;exit +#endif + +#include + +#include + +#include +#include + +namespace multi = boost::multi; + +using complex = std::complex; +[[maybe_unused]] constexpr complex I{0.0, 1.0}; +using T = complex; + +constexpr std::size_t N = 1 << 12; + +static void VectorAssignment(benchmark::State& state){ + std::vector a(N*N); std::iota(begin(a), end(a), 1.11); + std::vector b(N*N); std::iota(begin(b), end(b), 0.); + for(auto _ : state){ + b = a; + benchmark::DoNotOptimize(b.data()); + benchmark::ClobberMemory(); + } +} +BENCHMARK(VectorAssignment)->Unit(benchmark::kMillisecond); + +static void MultiAssignment(benchmark::State& state){ + multi::array a({N, N}); std::iota(a.elements().begin(), a.elements().end(), 1.11); + multi::array b({N, N}); std::iota(b.elements().begin(), b.elements().end(), 0.0); + for(auto _ : state){ + b = a; + benchmark::DoNotOptimize(b.data_elements()); + benchmark::ClobberMemory(); + } +} +BENCHMARK(MultiAssignment)->Unit(benchmark::kMillisecond); + +static void MultiForLoopAssignment(benchmark::State& state){ + multi::array a({N, N}); std::iota(a.elements().begin(), a.elements().end(), 1.11); + multi::array b({N, N}); std::iota(b.elements().begin(), b.elements().end(), 0.0); + for(auto _ : state){ + for(auto i = N*0; i != N; ++i) + for(auto j = N*0; j != N; ++j) + b[i][j] = a[i][j]; + + benchmark::DoNotOptimize(b.data_elements()); + benchmark::ClobberMemory(); + } +} +BENCHMARK(MultiForLoopAssignment)->Unit(benchmark::kMillisecond); + +static void MultiRangeAssignment(benchmark::State& state){ + multi::array a({N, N}); std::iota(a.elements().begin(), a.elements().end(), 1.11); + multi::array b({N, N}); std::iota(b.elements().begin(), b.elements().end(), 0.0); + for(auto _ : state){ + b() = a(); + benchmark::DoNotOptimize(b.data_elements()); + benchmark::ClobberMemory(); + } +} +BENCHMARK(MultiRangeAssignment)->Unit(benchmark::kMillisecond); + +static void MultiElementsAssignment(benchmark::State& state){ + multi::array a({N, N}); std::iota(a.elements().begin(), a.elements().end(), 1.11); + multi::array b({N, N}); std::iota(b.elements().begin(), b.elements().end(), 0.0); + for(auto _ : state){ + b.elements() = a.elements(); + benchmark::DoNotOptimize(b.data_elements()); + benchmark::ClobberMemory(); + } +} +BENCHMARK(MultiElementsAssignment)->Unit(benchmark::kMillisecond); + +static void RawAssignment(benchmark::State& state){ + multi::array a({N, N}); std::iota(a.elements().begin(), a.elements().end(), 1.11); + multi::array b({N, N}); std::iota(b.elements().begin(), b.elements().end(), 0.0); + for(auto _ : state){ + std::copy_n(a.data_elements(), a.num_elements(), b.data_elements()); + benchmark::DoNotOptimize(b.data_elements()); + benchmark::ClobberMemory(); + } +} +BENCHMARK(RawAssignment)->Unit(benchmark::kMillisecond); + +BENCHMARK_MAIN(); + +//2020-06-12 12:01:19 +//Running ./assignment.cppx +//Run on (12 X 4600 MHz CPU s) +//CPU Caches: +// L1 Data 32K (x6) +// L1 Instruction 32K (x6) +// L2 Unified 256K (x6) +// L3 Unified 12288K (x1) +//Load Average: 12.13, 9.36, 7.01 +//----------------------------------------------------------- +//Benchmark Time CPU Iterations +//----------------------------------------------------------- +//VectorAssignment 50.0 ms 26.7 ms 25 +//MultiAssignment 53.7 ms 28.5 ms 26 +//RawAssignment 52.0 ms 27.8 ms 23 + +// 2024/11/16 running on Mac M3 Pro +// + clang++ -std=c++17 -DNDEBUG -O3 -I../include -I/opt/homebrew/Cellar/google-benchmark/1.9.0/include -L/opt/homebrew/Cellar/google-benchmark/1.9.0/lib -lbenchmark ./assignment.cpp -o ./assignment.cppx +// + ./assignment.cppx +// Unable to determine clock rate from sysctl: hw.cpufrequency: No such file or directory +// This does not affect benchmark measurements, only the metadata output. +// ***WARNING*** Failed to set thread affinity. Estimated CPU frequency may be incorrect. +// 2024-11-16T02:08:52-08:00 +// Running ./assignment.cppx +// Run on (16 X 24 MHz CPU s) +// CPU Caches: +// L1 Data 64 KiB +// L1 Instruction 128 KiB +// L2 Unified 4096 KiB (x16) +// Load Average: 3.26, 2.88, 2.98 +// ----------------------------------------------------------- +// Benchmark Time CPU Iterations +// ----------------------------------------------------------- +// VectorAssignment 4.35 ms 4.35 ms 161 +// MultiAssignment 4.35 ms 4.35 ms 164 +// RawAssignment 4.27 ms 4.27 ms 162 \ No newline at end of file diff --git a/benchmark/element_access.cpp b/benchmark/element_access.cpp index d49d3c091..4ddd8cc3b 100644 --- a/benchmark/element_access.cpp +++ b/benchmark/element_access.cpp @@ -11,7 +11,7 @@ clang++-9 -std=c++17 -O3 -DNDEBUG -DBOOST_DISABLE_ASSERTS $0 -o $0x -lbenchmark const long X_SIZE = 4000; const long Y_SIZE = 4000; -typedef boost::multi_array ImageArrayType; +using ImageArrayType = boost::multi_array; static void MeasureNative2(benchmark::State& state) { diff --git a/benchmark/serialization.cpp b/benchmark/serialization.cpp index 1ab232ff4..8491581be 100644 --- a/benchmark/serialization.cpp +++ b/benchmark/serialization.cpp @@ -1,5 +1,11 @@ +#ifdef COMPILATION_INSTRUCTIONS +g++-14 -O3 -Ofast -std=c++17 -DNDEBUG -I../include -I/opt/homebrew/Cellar/boost/1.87.0/include -L/opt/homebrew/Cellar/boost/1.87.0/lib -lboost_serialization `pkg-config --cflags --libs benchmark` -Wno-deprecated-declarations $0 -o $0.x && time $0.x $@ && rm -f $0.x; exit +#endif + #include -#include "../array.hpp" +#include + +#include //#include //#include @@ -23,6 +29,8 @@ #include #include +#include + #include #include @@ -67,7 +75,9 @@ void BM_iserialization(benchmark::State& st) { for(auto _ : st) { std::ifstream fs{"file"}; Ar xa{fs}; - xa>> multi::archive_traits::make_nvp("A", A); + // auto&& Ap = A(); + // xa >> boost::serialization::make_nvp("A", Ap); + xa>> multi::archive_traits::make_nvp("A", A()); benchmark::DoNotOptimize(A); benchmark::ClobberMemory(); } @@ -149,7 +159,7 @@ void BM_gzip_iserialization(benchmark::State& st) { //BENCHMARK_TEMPLATE(BM_gzip_oserialization, cereal::XMLOutputArchive); //BENCHMARK_TEMPLATE(BM_gzip_iserialization, cereal::XMLInputArchive); -BENCHMARK_TEMPLATE(BM_gzip_oserialization, boost::archive::xml_oarchive); -BENCHMARK_TEMPLATE(BM_gzip_iserialization, boost::archive::xml_iarchive); +// BENCHMARK_TEMPLATE(BM_gzip_oserialization, boost::archive::xml_oarchive); +// BENCHMARK_TEMPLATE(BM_gzip_iserialization, boost::archive::xml_iarchive); BENCHMARK_MAIN(); diff --git a/benchmark/tr_prod.cpp b/benchmark/tr_prod.cpp index 74770c363..178adcebf 100644 --- a/benchmark/tr_prod.cpp +++ b/benchmark/tr_prod.cpp @@ -239,7 +239,7 @@ int main() { cout << tr_square_direct(A) << '\n'; cout << tr_square_block2(A) << '\n'; { - int const N = 32768/2; + multi::array::size_type const N = 32768/2; multi::array A({N, N}); std::iota(A.data_elements(), A.data_elements() + N*N, 1.11); double warm = tr_square_direct(A); cout << warm << '\n'; diff --git a/benchmark/traceAA.cpp b/benchmark/traceAA.cpp index ff1c4961c..60239cde1 100644 --- a/benchmark/traceAA.cpp +++ b/benchmark/traceAA.cpp @@ -170,10 +170,10 @@ int main(int, char*[]){ // cerr<<" test N ni nj\n"; // exit(1); // } - int const N = 32768;// atoi(argv[1]); + std::ptrdiff_t const N = 32768; // atoi(argv[1]); cout << "N = " << N << '\n'; - int const ni = 64; // atoi(argv[2]); - int const nj = 64; //atoi(argv[3]); + int const ni = 64; // atoi(argv[2]); + int const nj = 64; //atoi(argv[3]); { cout << "Miguel traceAA\n"; double* A = new double[N*N]; diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 000000000..dce32ecda --- /dev/null +++ b/conanfile.py @@ -0,0 +1,29 @@ +from conan import ConanFile +from conan.tools.files import copy + + +class MultiConan(ConanFile): + name = "b-multi" + version = "0.80.1" + homepage = "https://gitlab.com/correaa/boost-multi" + description = "Multidimensional array access to contiguous or regularly contiguous memory. (Not an official Boost library)" + topics = ( + "array", + "multidimensional", + "library", + ) + license = "Boost" + url = "https://gitlab.com/correaa/boost-multi" + # No settings/options are necessary, this is header only + exports_sources = "include/*" + no_copy_source = True + + def package(self): + # This will also copy the "include" folder + copy(self, "*.hpp", self.source_folder, self.package_folder) + + def package_info(self): + # For header-only packages, libdirs and bindirs are not used + # so it's recommended to set those as empty. + self.cpp_info.bindirs = [] + self.cpp_info.libdirs = [] diff --git a/doc/Jamfile.v2 b/doc/Jamfile.v2 new file mode 100644 index 000000000..d66cd8ada --- /dev/null +++ b/doc/Jamfile.v2 @@ -0,0 +1,23 @@ +# Copyright 2025 Joaqu�n M L�pez Mu�oz. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +# See http://www.boost.org/libs/multi for library home page. + +import asciidoctor ; + +html multi.html : multi.adoc ; + +install html_ : multi.html : html ; + +pdf multi.pdf : multi.adoc ; +explicit multi.pdf ; + +install pdf_ : multi.pdf : multi ; +explicit pdf_ ; + +alias boostdoc ; +explicit boostdoc ; +alias boostrelease : html_ ; +explicit boostrelease ; diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 000000000..87df240f3 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,20 @@ + + + + + + +Boost.Multi Documentation + + + +Automatic redirection failed, please go to +html/index.html + + diff --git a/doc/multi.adoc b/doc/multi.adoc new file mode 100644 index 000000000..42477ec12 --- /dev/null +++ b/doc/multi.adoc @@ -0,0 +1,41 @@ += https://gitlab.com/correaa/boost-multi[Boost.Multi] +Alfredo A. Correa (last modified: {docdatetime}) +:toc: left +:toclevels: 2 +:idprefix: +:docinfo: private-footer +:source-highlighter: rouge +:source-language: c++ +:nofooter: +:sectlinks: +:leveloffset: +1 +:imagesdir: ../img +:stem: latexmath +:small: pass:[] +:small-end: pass:[] + +++++ + +++++ + +include::multi/intro.adoc[] +include::multi/install.adoc[] +include::multi/primer.adoc[] +include::multi/tutorial.adoc[] +include::multi/interop.adoc[] +include::multi/technical.adoc[] +include::multi/reference.adoc[] +include::multi/appendix.adoc[] + diff --git a/doc/multi/appendix.adoc b/doc/multi/appendix.adoc new file mode 100644 index 000000000..283285102 --- /dev/null +++ b/doc/multi/appendix.adoc @@ -0,0 +1,146 @@ +[#appendix] + += Appendix + +:idprefix: appendix_ + +# Appendix: Comparison to other array libraries (mdspan, Boost.MultiArray, etc) + +The C++23 standard provides `std::mdspan`, a non-owning _multidimensional_ array. +So here is an appropriate point to compare the two libraries. +Although the goals are similar, the two libraries differ in their generality and approach. + +The Multi library concentrates on _well-defined value- and reference-semantics of arbitrary memory types with regularly arranged elements_ (distributions described by strides and offsets) and _extreme compatibility with STL algorithms_ (via iterators) and other fundamental libraries. +While `mdspan` concentrates on _arbitrary layouts_ for non-owning memory of a single type (CPU raw pointers). +Due to the priority of arbitrary layouts, the `mdspan` research team didn't find efficient ways to introduce iterators into the library. +Therefore, its compatibility with the rest of the STL is lacking. +link:https://godbolt.org/z/aWW3vzfPj[Preliminarily], Multi array can be converted (viewed as) `mdspan`. + +link:https://www.boost.org/doc/libs/1_82_0/libs/multi_array/doc/user.html[Boost.MultiArray] is the original multidimensional array library shipped with Boost. +This library can replace Boost.MultiArray in most contexts, it even fulfillis the concepts of `boost::multi_array_concepts::ConstMultiArrayConcept` and `...::MutableMultiArrayConcept`. +Boost.MultiArray has technical and semantic limitations that are overcome in this library, regarding layouts and references; +it doesn't support value-semantics, iterator support is limited and it has other technical problems. + +link:https://eigen.tuxfamily.org/index.php?title=Main_Page[Eigen] is a very popular matrix linear algebra framework library, and as such, it only handles the special 2D (and 1D) array case. +Instead, the Multi library is dimension-generic and doesn't make any algebraic assumptions for arrays or contained elements (but still can be used to _implement_, or in combination, with dense linear algebra algorithms.) + +Other frameworks includes the OpenCV (Open Computing Vision) framework, which is too specialized to make a comparison here. + +Here is a table comparing with `mdspan`, R. Garcia's link:https://www.boost.org/doc/libs/1_82_0/libs/multi_array/doc/user.html[Boost.MultiArray] and Eigen. +link:https://godbolt.org/z/555893MqW[(online)]. + +|=== +| | Multi | mdspan/mdarray | Boost.MultiArray (R. Garcia) | Inria's Eigen + +| No external Deps | **yes** (only Standard Library C++17) | **yes** (only Standard Library C++17/C++26) | **yes** (only Boost) | **yes** +| Arbritary number of dims | **yes**, via positive dimension (compile-time) parameter `D` | **yes** | **yes** | no (only 1D and 2D) +| Non-owning view of data | **yes**, via `multi::array_ref(ptr, {n1, n2, ..., nD})` | **yes**, via `mdspan m{T*, extents{n1, n2, ..., nD}};` | **yes**, via `boost::multi_array_ref(T*, boost::extents[n1][n2]...[nD])` | **yes**, via `Eigen::Map>(ptr, n1, n2)` +| Compile-time dim size | no | **yes**, via template paramaters `mdspan{T*, extent<16, dynamic_extents>{32} }` | no | **yes**, via `Eigen::Array` +| Array values (owning data) | **yes**, via `multi::array({n1, n2, ..., nD})` | yes? (planned for `mdarray`) | **yes**, via `boost::multi_array(boost::extents[n1][n2]...[nD])` | **yes**, via `Eigen::Array(n1, n2)` +| Value semantic (Regular) | **yes**, via cctor, mctor, assign, massign, auto decay of views | yes? (planned for `mdarray`) | partial, assigment on equal extensions | **yes** (?) +| Move semantic | **yes**, via mctor and massign | yes? for `mdarray` (depends on adapted container) | no (C++98 library) | **yes** (?) +| const-propagation semantics | **yes**, via `const` or `const&` | no, const mdspan elements are assignable! | no, inconsistent | (?) +| Element initialization | **yes**, via nested init-list | no (?) | no | no, only delayed init via `A << v1, v2, ...;` +| References w/no-rebinding | **yes**, assignment is deep | no, assignment of mdspan rebinds! | **yes** | **yes** (?) +| Element access | **yes**, via `A(i, j, ...)` or `A[i][j]...` | **yes**, via `A[i, j, ...]` | **yes**, via `A[i][j]...` | **yes**, via `A(i, j)` (2D only) +| Partial element access | **yes**, via `A[i]` or `A(i, multi::all)` | no, only via `submdspan(A, i, full_extent)` | **yes**, via `A[i]` | **yes**, via `A.row(i)` +| Subarray views | **yes**, via `A({0, 2}, {1, 3})` or `A(1, {1, 3})` | **yes**, via `submdspan(A, std::tuple{0, 2}, std::tuple{1, 3})` | **yes**, via `A[indices[range(0, 2)][range(1, 3)]]` | **yes**, via `A.block(i, j, di, dj)` +| Subarray with lower dim | **yes**, via `A(1, {1, 3})` | **yes**, via `submdspan(A, 1, std::tuple{1, 3})` | **yes**, via `A[1][indices[range(1, 3)]]` | **yes**, via `A(1, Eigen::placeholders::all)` +| Subarray w/well def layout | **yes** (strided layout) | no | **yes** (strided layout) | **yes** (strided) +| Recursive subarray | **yes** (layout is stack-based and owned by the view) | **yes** (?) | no (subarray may dangle layout, design bug?) | **yes** (?) (1D only) +| Restrictions (lazy arrays) | **yes**, `[](auto is...) {...} ^ extensions_t(...)` | no | no | no? +| Expression templates | no (implementable through Restrictions, see section) | no | no | **yes** (via operator overloading `A + B * C`) +| Custom Alloctors | **yes**, via `multi::array` | yes(?) through `mdarray`'s adapted container | **yes** (stateless?) | no +| PMR Alloctors | **yes**, via `multi::pmr::array` | yes(?) through `mdarray`'s adapted container | no | no +| Fancy pointers / references | **yes**, via `multi::array` or views | no | no | no +| Stride-based Layout | **yes** | **yes** | **yes** | **yes** +| Fortran-ordering | **yes**, only for views, e.g. resulted from transposed views | **yes** | **yes**. | **yes** +| Zig-zag / Hilbert ordering | no | **yes**, via arbitrary layouts (no inverse or flattening) | no | no +| Arbitrary layout | no | **yes**, possibly inneficient, no efficient slicing | no | no +| Flattening of elements | **yes**, via `A.elements()` range (efficient representation) | **yes**, but via indices roundtrip (inefficient) | no, only for allocated arrays | no, not for subblocks (?) +| Iterators | **yes**, standard compliant, random-access-iterator | no | **yes**, limited | no +| Multidimensional iterators (cursors) | **yes** (experimental) | no | no | no +| STL algorithms or Ranges | **yes** | no, limited via `std::cartesian_product` | **yes**, some do not work | no +| Compatibility with Boost | **yes**, serialization, interprocess (see below) | no | no | no +| Compatibility with Thrust or GPUs | **yes**, via flatten views (loop fusion), thrust-pointers/-refs | no | no | no +| Used in production | link:https://qmcpack.org/[QMCPACK], link:https://gitlab.com/npneq/inq[INQ] | (?) , experience from Kokkos incarnation | **yes** (?) | link:https://eigen.tuxfamily.org/index.php?title=Main_Page#Projects_using_Eigen[**yes**] +|=== + +# Appendix: Multi for FORTRAN programmers + +This section summarizes simple cases translated from FORTRAN syntax to C++ using the library. +The library strives to give a familiar feeling to those who use multidimensional arrays in FORTRAN. +Arrays can be indexed using square brackets or parenthesis, which would be more familiar to FORTRAN syntax. +The most significant differences are that array indices in FORTRAN start at `1`, and that index ranges are specified as closed intervals, while in Multi, they start by default at `0`, and ranges are half-open, following C++ conventions. +Like in FORTRAN, arrays are not initialized automatically for simple types (e.g., numeric); such initialization needs to be explicit. + +|=== +| | FORTRAN | C++ Multi + +| Declaration/Construction 1D | `real, dimension(2) :: numbers` (at top) | `multi::array numbers(2);` (at scope) +| Initialization (2 elements) | `real, dimension(2) :: numbers = [ 1.0, 2.0 ]` | `multi::array numbers = { 1.0, 2.0 };` +| Element assignment | `numbers(2) = 99.0` | `numbers(1) = 99.0;` (or `numbers[1]`) +| Element access (print 2nd) | `Print *, numbers(2)` | `std::cout << numbers(1) << '\n';` +| Initialization | `DATA numbers / 10.0 20.0 /` | `numbers = {10.0, 20.0};` +|=== + +In the more general case for the dimensionality, we have the following correspondance: + +|=== +| | FORTRAN | C++ Multi + +| Construction 2D (3 by 3) | `real*8 :: A2D(3,3)` (at top) | `multi::array A2D({3, 3});` (at scope) +| Construction 2D (2 by 2) | `real*8 :: B2D(2,2)` (at top) | `multi::array B2D({2, 2});` (at scope) +| Construction 1D (3 elements) | `real*8 :: v1D(3)` (at top) | `multi::array v1D({3});` (at scope) +| Assign the 1st column of A2D | `v1D(:) = A2D(:,1)` | `v1( _ ) = A2D( _ , 0 );` +| Assign the 1st row of A2D | `v1D(:) = A2D(1,:)` | `v1( _ ) = A2D( 0 , _ );` +| Assign upper part of A2D | `B2D(:,:) = A2D(1:2,1:2)` | `B2D( _::_ , _ ) = A2D({0, 2}, {0, 2});` +|=== + +Note that these correspondences are notationally logical; +internal representation (memory ordering) can still be different, affecting operations that interpret 2D arrays as contiguous elements in memory. + +Range notation such as `1:2` is replaced by `{0, 2}`, which considers both the difference in the start index and the half-open interval notation in the C++ conventions. +Stride notation such as `1:10:2` (i.e., from first to tenth included, every two elements) is replaced by `{0, 10, 2}`. +Complete range interval (single `:` notation) is replaced by `multi::_`, which can be used simply as `_` after the declaration `using multi::_;`. +These rules extend to higher dimensionality. + +Unlike FORTRAN, Multi doesn't provide algebraic operators, using algorithms is encouraged instead. +For example, a FORTRAN statement like `A = A + B` is translated as this in the one-dimensional case: + +```cpp +std::transform(A.begin(), A.end(), B.begin(), A.begin(), std::plus{}); // valid for 1D arrays only +``` + +In the general dimensionality case we can write: + +```cpp +auto&& Aelems = A.elements(); +auto const& Belems = B.elements(); +std::transform(Aelems.begin(), A.elems.end(), Belems.begin(), Aelems.begin(), std::plus<>{}); // valid for arbitrary dimension +``` + +or +```cpp +std::ranges::transform(A.elements(), B.elements(), A.elements().begin(), std::plus<>{}); // alternative using C++20 ranges +``` + +A FORTRAN statement like `C = 2.0*C` is rewritten as `std::ranges::transform(C.elements(), C.elements().begin(), [](auto const& e) { return 2.0*e; });`. + +It is possible to use C++ operator overloading for functions such as `operartor+=` (`A += B;`) or `operator*=` (`C *= 2.0;`); +however, this possibility can become unwindenly complicated beyond simple cases (also it can become inefficient if implemented naively). + +Simple loops can be mapped as well, taking into account indexing differences: +```fortran +do i = 1, 5 ! for(int i = 0; i != 5; ++i) { + do j = 1, 5 ! for(int j = 0; j != 5; ++j) { + D2D(i, j) = 0 ! D2D(i, j) = 0; + end do ! } +end do ! } +``` +link:https://godbolt.org/z/77onne46W[(live)] + +However, algorithms like `transform`, `reduce`, `transform_reduce`and `for_each`, and offer a higher degree of control over operations, including memory allocations if needed, and even enable parallelization, providing a higher level of flexibility. +In this case, `std::fill(D2D.elements().begin(), D2D.elements().end(), 0);` will do. + +> **Thanks** to Joaquín López Muñoz and Andrzej Krzemienski for the critical reading of the documentation and to Matt Borland for his help integrating Boost practices in the testing code. diff --git a/doc/multi/install.adoc b/doc/multi/install.adoc new file mode 100644 index 000000000..c8f1fc974 --- /dev/null +++ b/doc/multi/install.adoc @@ -0,0 +1,73 @@ +[#install] + += Installation + +:idprefix: install_ + +Before using the library, you can try it https://godbolt.org/z/dvacqK8jE[online]. + +_Multi_ has no external dependencies and can be used immediately after downloading. +```bash +git clone https://gitlab.com/correaa/boost-multi.git +``` + +_Multi_ doesn't require installation since a single header is enough to use the entire core library; +```c++ +#include + +int main() { ... } +``` + +The library can still be installed with CMake. +The header (and CMake) files will be installed in the chosen prefix location (by default, `/usr/local/include/multi` and `/usr/local/share/multi`). +```bash +cd boost-multi +mkdir -p build && cd build +cmake . -B ./build # --install-prefix=$HOME/.local +cmake --install ./build # or sudo ... +``` + +_Testing_ the library requires Boost.Core (headers), installed for example, via +`sudo apt install cmake git g{plus}{plus} libboost-test-dev make` +or `sudo dnf install boost-devel cmake gcc-c{plus}{plus} git`. +A CMake build system is provided to compile and run basic tests. +```bash +ctest -C ./build +``` + +Once installed, other CMake projects (targets) can depend on Multi by adding a simple `add_subdirectory(my_multi_path)` or by `find_package`: +```cmake +find_package(multi) # see https://gitlab.com/correaa/boost-multi +``` + +Alternatively to the library can be fetched on demand: +```cmake +include(FetchContent) +FetchContent_Declare(multi GIT_REPOSITORY https://gitlab.com/correaa/boost-multi.git) +FetchContent_MakeAvailable(multi) +... +target_link_libraries(my_target PUBLIC multi) +``` + +The code requires any modern https://godbolt.org/z/hxEYGYEWc[C\++ compiler] (or https://godbolt.org/z/zvYoMjeTh[CUDA compiler]) with standard C\++17 support; +for reference, (at least) any of: +LLVM's https://godbolt.org/z/51E1hjfnn[clang (5.0+)] (libc\++ and libstdc\++), +GNU's https://godbolt.org/z/1nGEbKc5a[g\++ (7.1+)], +Nvidia's https://godbolt.org/z/v9W889njx[nvcc (11.5+)] +and + https://godbolt.org/z/6z39PjT47[nvc\++ (22.7+)], +Intel's icpx (2022.0.0+) and icc (2021.1.2+, deprecated), +Baxter's https://www.circle-lang.org[circle] (https://godbolt.org/z/KeG417fMz[build 202+]), +https://zig.news/kristoff/compile-a-c-c-project-with-zig-368j[Zig] in https://godbolt.org/z/cKGebsWMG[c++ mode (v0.9.0+)], +Edison Design's https://edg.com/c[EDG] https://godbolt.org/z/693fxPedx[(6.5+)] +and +Microsoft's https://visualstudio.microsoft.com/vs/features/cplusplus[MSVC] (https://godbolt.org/z/Kqrva137M[+14.1]). + +(Multi code inside CUDA kernel can be compiled with `nvcc` and with https://godbolt.org/z/7dTKdPTxc[clang (in CUDA mode)]. +Inside HIP code, it can be compiled with AMD's clang rocm (5.0+).) + +Optional _adaptor_ sublibraries (included in `multi/adaptors/`) have specific dependencies: fftw, , lapack, thurst, or CUDA +(all of them can be installed with +`sudo apt install libfftw3-dev lib64-dev liblapack64-dev libthrust-dev nvidia-cuda-dev` +or `sudo dnf install fftw-devel ...`. +) diff --git a/doc/multi/interop.adoc b/doc/multi/interop.adoc new file mode 100644 index 000000000..c2fe99a82 --- /dev/null +++ b/doc/multi/interop.adoc @@ -0,0 +1,773 @@ +[#interop] + += Interoperability + +:idprefix: interop_ + +## Standard Library (STL algorithms) + +The fundamental goal of the library is that the arrays and iterators can be used out-of-the-box with C++ Standard Library, in particular the Standard Template Library (STL) and the algorithms it provides. +The most dramatic example of this is that `std::sort` works with array as it is shown in a previous section. + +Along with STL itself, the library tries to interact with other existing quality C++ libraries listed below. + +### Ranges (C++20) + +link:https://en.cppreference.com/w/cpp/ranges[Standard ranges] extend standard algorithms, reducing the need for iterators, in favor of more composability and a less error-prone syntax. + +In this example, we replace the values of the first row for which the sum of the elements is odd: + +```cpp + static constexpr auto accumulate = [](auto const& R) { return std::ranges::fold_left(R, 0, std::plus<>{}); }; + + auto arr = multi::array{ + {2, 0, 2, 2}, + {2, 7, 0, 2}, // this row adds to an odd number + {2, 2, 0, 4}, + }; + + auto const row = std::ranges::find_if(arr, [](auto const& r) { return accumulate(r) % 2 == 1; }); + if(row != arr.end()) std::ranges::fill(*row, 9); + + assert(arr[1][0] == 9 ); +``` +link:https://godbolt.org/z/cT9WGffM3[(live)] + +Together with the array constructors, the ranges library enables a more functional programming style; +this allows us to work with immutable variables in many cases. + +```cpp + multi::array const A = {{...}}; + multi::array const V = {...}; + + multi::array const R = std::views::zip_transform(std::plus<>{}, A[0], V); + + // Alternative imperative mutating code: + // multi::array R(V.size()); // R is created here... + // for(auto i : R.extension()) {R[i] = A[0][i] + V[i];} // ...and then mutated here +``` +link:https://godbolt.org/z/M84arKMnT[(live)] + + +The "pipe" (`|`) notation of standard ranges allows one-line expressions. +In this example, the expression will yield the maximum value of the rows sums: +link:https://godbolt.org/z/hvqnsf4xb[`std::ranges::max(arr | std::views::transform(accumulate))`] + +Like in classic STL, standard range algorithms acting on sequences operate in the first dimension by default, +for example, lexicographical sorting on rows can be performed with the `std::ranges::sort` algorithm. + +```cpp + auto A = multi::array{ + {'S', 'e', 'a', 'n', ' ', ' '}, + {'A', 'l', 'e', 'x', ' ', ' '}, + {'B', 'j', 'a', 'r', 'n', 'e'}, + }; + assert(!std::ranges::is_sorted(A)); + + std::ranges::sort(A); // will sort on rows + + assert( std::ranges::is_sorted(A)); + + assert( + A == multi::array{ + {'A', 'l', 'e', 'x', ' ', ' '}, + {'B', 'j', 'a', 'r', 'n', 'e'}, + {'S', 'e', 'a', 'n', ' ', ' '}, + } + ); +``` + +To operate on the second dimension (sort by columns), use `std::ranges::sort(~A)` (or `std::ranges::sort(A.transposed())`). + +There are many range's views to enumerate here, including link:https://godbolt.org/z/Pjc9bjoe6[`std::reverse`] (reverse the elements --of the leading dimension--). +It is important to understand that most views generated by ranges are multidimensional arrays in the sense of this library. + +### Polymorphic Memory Resources + +In addition to supporting classic allocators (`std::allocator` by default), the library is compatible with C++17's link:https://en.cppreference.com/w/cpp/header/memory_resource[polymorphic memory resources (PMR)], which allows using advanced allocation strategies, including preallocated buffers. +This example code uses a buffer as memory for two arrays; +in it, a predefined buffer will contain the arrays' data (something like `"aaaabbbbbbXX"`). + +```cpp +#include // for polymorphic memory resource, monotonic buffer + +int main() { + char buffer[13] = "XXXXXXXXXXXX"; // a small buffer on the stack + std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; + + multi::pmr::array A({2, 2}, 'a', &pool); + multi::pmr::array B({3, 2}, 'b', &pool); + + assert( buffer != std::string{"XXXXXXXXXXXX"} ); // overwritten w/elements, implementation-dependent (libstd consumes from left, and libc++, from the right) +} +``` + +`multi::pmr::array` is a synonym for `multi::array>`. +In this particular example, the technique can be used to avoid dynamic memory allocations of small local arrays. link:https://godbolt.org/z/fP9P5Ksvb[(live)] + +The library also supports memory resources from other libraries, including those returning special pointer types (see the [CUDA Thrust](#cuda-thrust) section and the Boost.Interprocess section). + +### Substitutability with standard vector and span + +The one-dimensional case `multi::array` is special and overlaps functionality with other dynamic array implementations, such as `std::vector`. +Indeed, both types of containers are similar and usually substitutable, with no or minor modifications. +For example, both can be constructed from a list of elements (`C c = {x0, x2, ...};`) or from a size `C c(size);`, where `C` is either type. + +Both values are assignable, have the same element access patterns and iterator interface, and implement all (lexical) comparisons. + +They differ conceptually in their resizing operations: `multi::array` doesn't insert or push elements and resizing works differently. +The difference is that the library doesn't implement *amortized* allocations; therefore, these operations would be of a higher complexity cost than the `std::vector`. +For this reason, `resize(new_size)` is replaced with `reextent({new_size})` in `multi::array`, whose primary utility is for element preservation when necessary. + +In a departure from standard containers, elements are left initialized if they have trivial constructor. +So, while `multi::array A({N}, T{})` is equivalent to `std::vector V(N, T{})`, `multi::array A(N)` will leave elements `T` uninitialized if the type allows this (e.g. built-ins), unlike `std::vector V(N)` which will initialize the values. +RAII types (e.g. `std::string`) do not have trivial default constructor, therefore they are not affected by this rule. + +With the appropriate specification of the memory allocator, `multi::array` can refer to special memory not supported by `std::vector`. + +Finally, an array `A1D` can be copied by `std::vector v(A1D.begin(), A1D.end());` or `v.assign(A1D.begin(), A1D.end());` or vice versa. +Without copying, a reference to the underlying memory can be created `auto&& R1D = multi::array_ref(v.data(), v.size());` or conversely `std::span(A1D.data_elements(), A1D.num_elements());`. +(See examples link:https://godbolt.org/z/n4TY998o4[here].) + +The `std::span` (C++20) has not a well defined reference- or pointer-semantics; it doesn't respect `const` correctness in generic code. +This behavior is contrary to the goals of this library; +and for this reason, there is no single substitute for `std::span` for all cases. +Depending on how it is used, either `multi::array_ref [const& | &&]` or `multi::array_ptr` may replace the features of `std::span`. +The former typically works when using it as function argument. + +Multi-dimensinal arrays can interoperate with C++23's non-owning `mdspan`. +link:https://godbolt.org/z/aocn73bzf[Preliminarily], Multi's subarrays (arrays) can be converted (viewed as) `mdspan` automatically (replicating the behavior of `std::vector`). + +A detailed comparison with other array libraries (mspan, Boost.MultiArray, Eigen) is explained in an Appendix. + +### Execution policies (parallel algorithms) + +Multi's iterators can exploit parallel algorithms by specifying execution policies. +This code takes every row of a two-dimensional array and sums its elements, putting the results in a one-dimensional array of compatible size. +The execution policy (`par`) selected is passed as the first argument. + +```cpp + multi::array const A = ...; + multi::array v(size(A)); + + std::transform(std::execution::par, arr.begin(), arr.end(), vec.begin(), [](auto const& row) {return std::reduce(row.begin(), row.end());} ); +``` +link:https://godbolt.org/z/63jEdY7zP[(live)] + +For an array of 10000x10000 elements, the execution time decreases to 0.0288 sec, compared to 0.0526 sec for the non-parallel version (i.e. without the `par` argument). + +Note that parallelization is, in this context, inherently one-dimensional. +For example, parallelization happens for the transformation operation, but not to the summation. + +The optimal way to parallelize specific operations strongly depends on the array's size and shape. +Generally, straightforward parallelization without exploiting the n-dimensional structure of the data has a limited pay-off; +and nesting parallelization policies usually don't help either. + +Flattening the n-dimensional structure for certain algorithms might help, but such techniques are beyond the scope of this documentation. + +Some member functions internally perform algorithms and that can benefit from execution policies; +in turn, some of these functions have the option to pass a policy. +For example, this copy construction can initialize elements in parallel from the source: + +```cpp + multi::array const A = ...; + multi::array const B(std::execution::par, A); // copies A into B, in parallel, same effect as multi::array const B(A); or ... B = A; +``` + +Execution policies are not limited to STL; +Thrust and oneAPI also offer execution policies that can be used with the corresponding algorithms. + +Execution policies and ranges can be mixed (`x` and `y` can be 1D dimensional arrays, of any arithmetic element type) +```cpp +template +auto dot_product(X1D const& x, Y1D const& y) { + assert(x.size() == y.size()); + auto const& z = std::ranges::views::zip(x, y) + | std::ranges::views::transform([](auto const& ab) { auto const [a, b] = ab; + return a * b; + }) + ; + return std::reduce(std::execution::par_unseq, z.begin(), z.end()); +} +``` +link:https://godbolt.org/z/cMq87xPvb[(live)] + +## Range-v3 + +The library works out of the box with Eric Niebler's Range-v3 library, a precursor to the standard Ranges library (see above). +The library helps removing explicit iterators (e.g. `begin`, `end`) from the code when possible. + +Every Multi array object can be regarded as range. +Every subarray references (and array values) are interpreted as range views. + +For example for a 2D array `d2D`, `d2D` itself is interpreted as a range of rows. +Each row, in turn, is interpreted as a range of elements. +In this way, `d2D.transposed()` is interpreted as a range of columns (of the original array), and each column a range of elements (arranged vertically in the original array). + +```cpp +#include + +int main(){ + + multi::array const d2D = { + { 0, 1, 2, 3}, + { 5, 6, 7, 8}, + {10, 11, 12, 13}, + {15, 16, 17, 18} + }; + assert( ranges::inner_product(d2D[0], d2D[1], 0.) == 6+2*7+3*8 ); + assert( ranges::inner_product(d2D[0], rotated(d2D)[0], 0.) == 1*5+2*10+15*3 ); + + static_assert(ranges::RandomAccessIterator::iterator>{}); + static_assert(ranges::RandomAccessIterator::iterator>{}); +} +``` + +In this other link:https://godbolt.org/z/MTodPEnsr[example], a 2D Multi array (or subarray) is modified such that each element of a column is subtracted the mean value of such column. + +```cpp +#include +#include + +template +void subtract_mean_columnwise(MultiArray2D&& arr) { + auto&& tarr = arr.transposed(); + auto const column_mean = + tarr + | ranges::views::transform([](auto const& row) {return ranges::accumulate(row, 0.0)/row.size();}) + | ranges::to> + ; + + ranges::transform( + arr.elements(), + column_mean | ranges::views::cycle, + arr.elements().begin(), + [](auto const elem, auto const mean) {return elem - mean;} + ); +} +``` + +## Serialization + +The ability to serialize arrays is essential for storing data in a persistent medium (files on disk) and communicating values via streams or networks (e.g., MPI). +Unfortunately, the C++ language does not provide facilities for serialization, and the standard library doesn't either. + +However, there are a few libraries that offer a certain common protocol for serialization, +such as link:https://www.boost.org/doc/libs/1_76_0/libs/serialization/doc/index.html[Boost.Serialization] and link:https://uscilab.github.io/cereal/[Cereal]. +The Multi library is compatible with both (and doesn't depend on any of them). +The user can choose one or the other, or none, if serialization is not needed. +The generic protocol is such that variables are (de)serialized using the (`>>`)`<<` operator with the archive; operator `&` can be used to have a single code for both. +Serialization can be binary (efficient) or text-based (human-readable). + +Here, it is a small implementation of save and load functions for an array to JSON format with the Cereal library. +The example can be easily adapted to other formats or libraries. +(An alternative for XML with Boost.Serialization is commented on the right.) + +```cpp +#include // this library + +#include // or #include // #include + // #include +// for serialization of array elements (in this case strings) +#include // #include + +#include // saving to files in example + +using input_archive = cereal::JSONInputArchive ; // or ::XMLInputArchive ; // or boost::archive::xml_iarchive; +using output_archive = cereal::JSONOutputArchive; // or ::XMLOutputArchive; // or boost::archive::xml_oarchive; + +using cereal::make_nvp; // or boost::serialization::make_nvp; + +namespace multi = boost::multi; + +template +auto array_load(IStream&& is) { + multi::array value; + input_archive{is} >> make_nvp("value", value); + return value; +} + +template +void array_save(OStream&& os, multi::array const& value) { + output_archive{os} << make_nvp("value", value); +} + +int main() { + multi::array const A = {{"w", "x"}, {"y", "z"}}; + array_save(std::ofstream("file.string2D.json"), A); // use std::cout to print serialization to the screen + + auto const B = array_load(std::ifstream("file.string2D.json")); + assert(A == B); +} +``` +link:https://godbolt.org/z/Grr7Mqef5[(online)] + +These templated functions work for any dimension and element type (as long as the element type is serializable in itself; all basic types are serializable by default). +However, note that the user must ensure that data is serialized and deserialized into the same type; +the underlying serialization libraries only do minimal consistency checks for efficiency reasons and don't try to second-guess file formats or contained types. +Serialization is a relatively low-level feature for which efficiency and economy of bytes are a priority. +Cryptic errors and crashes can occur if serialization libraries, file formats, or C++ types are mixed between writes and reads. +Some formats are human-readable but still not particularly pretty for showing as output (see the section on Formatting on how to print to the screen). + +References to subarrays (views) can also be serialized; however, size information is not saved in such cases. +The reasoning is that references to subarrays cannot be resized in their number of elements if there is a size mismatch during deserialization. +Therefore, array views should be deserialized as other array views with matching sizes. + +The output JSON file created by Cereal in the previous example looks like this. + +```json +{ + "value": { + "cereal_class_version": 0, + "extensions": { + "cereal_class_version": 0, + "extension": { + "cereal_class_version": 0, + "first": 0, + "last": 2 + }, + "extension": { + "first": 0, + "last": 2 + } + }, + "elements": { + "cereal_class_version": 0, + "item": "w", + "item": "x", + "item": "y", + "item": "z" + } + } +} +``` +(The link:https://godbolt.org/z/de814Ycar[Cereal XML] and Boost XML output would have a similar structure.) + +Large datasets tend to be serialized slowly for archives with heavy formatting. +Here it is a comparison of speeds when (de)serializing a 134 MB 4-dimensional array of with random `double`s. + +|=== +| Archive format (Library) | file size | speed (read - write) | time (read - write) | + +| JSON (Cereal) | 684 MB | 3.9 MB/sec - 8.4 MB/sec | 32.1 sec - 15.1 sec | +| XML (Cereal) | 612 MB | 2.0 MB/sec - 4.0 MB/sec | 56.0 sec - 28.0 sec | +| XML (Boost) | 662 MB | 11.0 MB/sec - 13.0 MB/sec | 11.0 sec - 9.0 sec | +| YAML (link:https://gitlab.com/correaa/boost-archive-yml[custom archive)]) | 702 MB | 10.0 MB/sec - 4.4 MB/sec | 12.0 sec - 28.0 sec | +| Portable Binary (Cereal) | 134 MB | 130 MB/sec - 121 MB/sec | 9.7 sec - 10.6 sec | +| Text (Boost) | 411 MB | 15.0 MB/sec - 16.0 MB/sec | 8.2 sec - 7.6 sec | +| Binary (Cereal) | 134 MB | 134.4 MB/sec - 126. MB/sec | 0.9 sec - 0.9 sec | +| Binary (Boost) | 134 MB | 5200 MB/sec - 1600 MB/sec | 0.02 sec - 0.1 sec | +| gzip-XML (Cereal) | 191 MB | 2.0 MB/sec - 4.0 MB/sec | 61 sec - 32 sec | +| gzip-XML (Boost) | 207 MB | 8.0 MB/sec - 8.0 MB/sec | 16.1 sec - 15.9 sec | +|=== + +## Boost.Interprocess + +Using Interprocess allows for shared memory and for persistent mapped memory. + +```cpp +#include +#include "multi/array.hpp" +#include + +namespace bip = boost::interprocess; +using manager = bip::managed_mapped_file; +template using mallocator = bip::allocator; +auto get_allocator(manager& m){return m.get_segment_manager();} + +namespace multi = boost::multi; +template using marray = multi::array>; + +int main(){ +{ + manager m{bip::create_only, "bip_mapped_file.bin", 1 << 25}; + auto&& arr2d = *m.construct>("arr2d")(std::tuple{1000, 1000}, 0., get_allocator(m)); + arr2d[4][5] = 45.001; + m.flush(); +} +{ + manager m{bip::open_only, "bip_mapped_file.bin"}; + auto&& arr2d = *m.find>("arr2d").first; + assert( arr2d[4][5] == 45.001 ); + m.destroy>("arr2d");// eliminate>(m, "arr2d");} +} +} +``` + +## CUDA (and HIP, and OMP, and TBB) via Thrust + +The library works out-of-the-box in combination with the Thrust library. + +```cpp +#include // this library + +#include // from CUDA or ROCm distributions + +namespace multi = boost::multi; + +int main() { + multi::array> A({10,10}); + multi::array> B({10,10}); + A[5][0] = 50.0; + + thrust::copy(A.rotated()[0].begin(), A.rotated()[0].end(), B.rotated()[0].begin()); // copy row 0 + assert( B[5][0] == 50.0 ); +} +``` +link:https://godbolt.org/z/oM4YbPYz8[(live)] + +which uses the default Thrust device backend (i.e. CUDA when compiling with `nvcc`, HIP/ROCm when compiling with a HIP/ROCm compiler, or OpenMP or TBB in other cases). +Universal memory (accessible from normal CPU code) can be used with `thrust::universal_allocator` (from ``) instead. + +More specific allocators can be used ensure CUDA backends, for example CUDA managed memory: + +```cpp +#include +... + multi::array> A({10,10}); +``` + +In the same way, to *ensure* HIP backends please replace the `cuda` namespace by the `hip` namespace, and in the directory name ``. +`` is provided by rocThrust in the ROCm distribution (in `/opt/rocm/include/thrust/system/hip/`, and not by the NVIDIA distribution.) + +Multi doesn't have a dependency on Thrust (or vice versa); +they just work well together, both in terms of semantics and efficiency. +Certain "patches" (to improve Thrust behavior) can be applied to Thrust to gain extra efficiency and achieve near native speed by adding the `#include`. + +Multi can be used on existing memory in a non-invasive way via (non-owning) reference arrays: + +```cpp + // assumes raw_pointer was allocated with cudaMalloc or hipMalloc + using gpu_ptr = thrust::cuda::pointer; // or thrust::hip::pointer + multi::array_ref Aref({n, n}, gpu_ptr{raw_pointer}); +``` + +Finally, the element type of the device array has to be device-friendly to work correctly; +this includes all build in types, and classes with basic device operations, such as construction, destruction, and assigment. +They notably do not include `std::complex`, in which can be replaced by the device-friendly `thrust::complex` can be used as replacement. + +### OpenMP via Thrust + +In an analogous way, Thrust can also handle OpenMP (omp) allocations and multi-threaded algorithms of arrays. +The OMP backend can be enabled by the compiler flags `-DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_BACKEND_OMP` or by using the explicit `omp` system types: + +```cpp +#include +#include + +#include + +namespace multi = boost::multi; + +int main() { + auto A = multi::thrust::omp::array({10,10}, 0.0); // or multi::array>; + auto B = multi::thrust::omp::array({10,10}); // or multi::array>; + + A[5][0] = 50.0; + + // copy row 0 + thrust::copy( + A.rotated()[0].begin(), A.rotated()[0].end(), + B.rotated()[0].begin() + ); + assert( B[5][0] == 50.0 ); + auto C = B; // uses omp automatically for copying behind the scenes +} +``` +link:https://godbolt.org/z/KW19zMYnE[(live)] + +Compilation might need to link to an omp library, `-fopenmp -lgomp`. + +Without Thrust, OpenMP pragmas would also work with this library, however OpenMP memory allocation, would need to be manually managed. + +### Thrust memory resources + +GPU memory is relative expensive to allocate, therefore any application that allocates and deallocates arrays often will suffer performance issues. +This is where special memory management is important, for example for avoiding real allocations when possible by caching and reusing memory blocks. + +Thrust implements both polymorphic and non-polymorphic memory resources via `thrust::mr::allocator`; +Multi supports both. + +```cpp +auto pool = thrust::mr::disjoint_unsynchronized_pool_resource( + thrust::mr::get_global_resource(), + thrust::mr::get_global_resource() +); + +// memory is handled by pool, not by the system allocator +multi::array> arr({1000, 1000}, &pool); +``` + +The associated pointer type for the array data is deduced from the _upstream_ resource; in this case, `thrust::universal_ptr`. + +As as quick way to improve performance in many cases, here it is a recipe for a `caching_allocator` which uses a global (one per thread) memory pool that can replace the default Thrust allocator. +The requested memory resides in GPU (managed) memory (`thrust::cuda::universal_memory_resource`) while the cache _bookkeeping_ is held in CPU memory (`new_delete_resource`). + +```cpp +template>>> +struct caching_allocator : Base_ { + caching_allocator() : + Base_{&thrust::mr::tls_disjoint_pool( + thrust::mr::get_global_resource(), + thrust::mr::get_global_resource() + )} {} + caching_allocator(caching_allocator const&) : caching_allocator{} {} // all caching allocators are equal + template struct rebind { using other = caching_allocator; }; +}; +... +int main() { + ... + using array2D = multi::array>; + + for(int i = 0; i != 10; ++i) { array2D A({100, 100}); /*... use A ...*/ } +} +``` +link:https://godbolt.org/z/rKG8PhsEh[(live)] + +In the example, most of the frequent memory requests are handled by reutilizing the memory pool avoiding expensive system allocations. +More targeted usage patterns may require locally (non-globally) defined memory resources. + +## CUDA C++ + +CUDA is a dialect of C++ that allows writing pieces of code for GPU execution, known as "CUDA kernels". +CUDA code is generally "low level" (less abstracted) but it can be used in combination with CUDA Thrust or the CUDA runtime library, specially to implement custom algorithms. +Although code inside kernels has certain restrictions, most Multi features can be used. +(Most functions in Multi, except those involving memory allocations, are marked `__device__` to allow this.) + +Calling kernels involves a special syntax (`<<< ... >>>`), and they cannot take arguments by reference (or by values that are not trivial). +Since arrays are usually passed by reference (e.g. `multi::array&` or `Array&&`), a different idiom needs to be used. +(Large arrays are not passed by value to avoid copies, but even if a copy would be fine, kernel arguments cannot allocate memory themselves.) +Iterators (e.g. `.begin()/.end()`) and "cursors" (e.g. `.home()`) are "trivial to copy" and can be passed by value and represent a "proxy" to an array, including allowing the normal index syntax and other transformations. + +Cursors are a generalization of iterators for multiple dimensions. +They are cheaply copied (like iterators) and they allow indexing. +Also, they have no associated `.size()` or `.extensions()`, but this is generally fine for kernels. +(Since `cursors` have minimal information for indexing, they can save stack/register space in individual kernels.) + +Here it is an example implementation for matrix multiplication, in combination with Thrust and Multi, + +```cpp +#include // from https://gitlab.com/correaa/boost-multi +#include // for thrust::cuda::allocator + +template +__global__ void Kernel(ACursor A, BCursor B, CCursor C, int N) { + int x = threadIdx.x + blockIdx.x * blockDim.x; + int y = threadIdx.y + blockIdx.y * blockDim.y; + + typename CCursor::element_type value{0.0}; + for (int k = 0; k != N; ++k) { value += A[y][k] * B[k][x]; } + C[y][x] = value; +} + +namespace multi = boost::multi; + +int main() { + int N = 1024; + + // declare 3 square arrays + multi::array> A({N, N}); A[0][0] = ...; + multi::array> B({N, N}); B[0][0] = ...; + multi::array> C({N, N}); + + // kernel invocation code + assert(N % 32 == 0); + dim3 dimBlock(32, 32); + dim3 dimGrid(N/32, N/32); + Kernel<<>>(A.home(), B.home(), C.home(), N); + cudaDeviceSynchronize(); + + // now C = A x B +} +``` +link:https://godbolt.org/z/eKbeosrWa[(live)] + +Expressions such as `A.begin()` (iterators) can also be passed to kernels, but they could unnecessarely occupy more kernel "stack space" when size information is not needed (e.g. `A.begin()->size()`). + +## SYCL + +The SYCL library promises the unify CPU, GPU and FPGA code. +At the moment, the array containers can use the Unified Shared Memory (USM) allocator, but no other tests have been investigated. + +```cpp + sycl::queue q; + + sycl::usm_allocator q_alloc(q); + multi::array data(N, 1.0, q_alloc); + + //# Offload parallel computation to device + q.parallel_for(sycl::range<1>(N), [=,ptr = data.base()] (sycl::id<1> i) { + ptr[i] *= 2; + }).wait(); +``` +link:https://godbolt.org/z/8WG8qaf4s[(live)] + +Algorithms are expected to work with oneAPI execution policies as well (not tested) + +```cpp + auto policy = oneapi::dpl::execution::dpcpp_default; + sycl::usm_allocator alloc(policy.queue()); + multi::array vec(n, alloc); + + std::fill(policy, vec.begin(), vec.end(), 42); +``` + +## Formatting ({fmt} pretty printing) + +The library doesn't have a "pretty" printing facility to display arrays. +Although it is not ideal, arrays can be printed and formated by looping over elements and dimension, as shown in other examples (using standard streams). + +Fortunatelly, the library automatically works with the external library link:https://fmt.dev/latest/index.html[{fmt}], both for arrays and subarrays. +The fmt library is not a dependency of the Multi library; +they simply work well together using the "ranges" part of the formatting library. +fmt allows a high degree of confurability. + +This example prints a 2-dimensional subblock of a larger array. + +```cpp +#include "fmt/ranges.h" +... + multi::array A2 = { + {1.0, 2.0, 3.0}, + /*-subblock-**/ + {3.0, 4.0, /**/ 5.0}, + {6.0, 7.0, /**/ 8.0}, + }; + + fmt::print("A2 subblock = {}", A2({1, 3}, {0, 2})); // second and third row, first and second column +``` +obtaining the "flat" output `A2 subblock = [[3, 4], [6, 7]]`. +(A similar effect can be achieved with link:https://godbolt.org/z/4ehd4s5vf[experimental C++23 `std::print` in libc++].) + +For 2 or more dimensions the output can be conveniently structured in different lines using the `fmt::join` facility: + +```cpp + fmt::print("\n[{}]\n", fmt::join(A2({1, 3}, {0, 2}), ",\n ")); // first dimension rows are printer are in different lines +``` +with the output: + +> ``` +> [[3, 4], +> [6, 7]] +> ``` + +In a similar way, the size of the array can be printed simply by passing the sizes output; `fmt::print("{}", A2(...).sizes() )`, which will print `(2, 2)`. + +(link:https://godbolt.org/z/7sGExn35n[(live)]). + +When saving arrays to files, please consider using serialization (see section) instead of formatting facilities. + +## Python (cppyy) + +There is no special code to interoperated with Python; +however the memory layout is compatible with Numpy and pybind11 binding with zero-copy could be written. +Furthermore the library works out-of-the-box via de automatic `cppyy` bindings. + +Here it is a complete Python session: + +```python +# We import the library +import cppyy +cppyy.add_include_path('path-to/boost-multi/include') +cppyy.include("boost/multi/array.hpp") + +multi = cppyy.gbl.boost.multi + +# We can created a one-dimensional array with 4 initialized to 0.0, or from a list of numbers. +# We can print the array and change the element values: +a1d = multi.array['double', 1](4, 0.0) +a1d = multi.array['double', 1]([1.0, 2.0, 3.0, 4.0]) +print(a1d) +``` +> ```python +> { 1.0000000, 2.0000000, 3.0000000, 4.0000000 } +> ``` +```python +# elements and assignable +a1d[2] = 99.9 +print(a1d) +``` +> ```python +> { 1.0000000, 2.0000000, 99.900000, 4.0000000 } +> ``` +```python +# We can also create a 2x2 array, or directly from a bidimensional list: +a2d = multi.array['double', 2](multi.extensions_t[2](2, 2), 0.0) +a2d = multi.array['double', 2]([[1.0, 2.0], [3.0, 4.0]]) + +# We can retrive information from an individual element, or from a row +print(a2d.transposed()[0]) +``` +> ```python +> { 1.0000000, 3.0000000 } +> ``` +```python +arow = a2d[0] +print(arow) + +# rows (or slices in general) are references to the original arrays +arow[0] = 66.6 +print(a2d[0]) +``` +> ```python +> { 66.600000, 2.0000000 } +> ``` +```python +arow_copy = + a2d[0] +arow_copy = 11111.1 +print(a2d[0]) +``` +> ```python +> { 66.600000, 2.0000000 } +> ``` + +## Legacy libraries (C-APIs) + +Multi-dimensional array data structures exist in all languages, whether implicitly defined by its strides structure or explicitly at the language level. +Functions written in C tend to receive arrays by pointer arguments (e.g., to the "first" element) and memory layout (sizes and strides). + +A C-function taking a 2D array with a concrete type might look like this in the general case: +```c +void fun(double* data, int size1, int size2, int stride1, int stride2); +``` +such a function can be called from C++ on Multi array (`arr`), by extracting the size and layout information, +```cpp +fun(arr.base(), std::get<0>(arr.sizes()), std::get<1>(arr.sizes()), std::get<0>(arr.strides()), std::get<1>(arr.strides()); +``` +or +```cpp +auto const [size1, size2] = arr.sizes(); +auto const [stride1, stride2] = arr.strides(); + +fun(arr.base(), size1, size2, stride1, stride2); +``` + +Although the recipe can be applied straightforwardly, different libraries make various assumptions about memory layouts (e.g., 2D arrays assume that the second stride is 1), and some might take stride information in a different way (e.g., FFTW doesn't use strides but stride products). +Furthermore, some arguments may need to be permuted if the function expects arrays in column-major (Fortran) ordering. + +For these reasons, the library is accompanied by a series of adaptor libraries to popular C-based libraries, which can be found in the `include/multi/adaptors/` subdirectory: + +- link:https://gitlab.com/correaa/boost-multi/-/blob/master/include/boost/multi/adaptors/blas/README.md[BLAS/cuBLAS Adator 🔗] + +Interface for BLAS-like linear algebra libraries, such as openblas, Apple's Accelerate, MKL and hipBLAS/cuBLAS (GPUs). +Simply `#include "multi/adaptors/blas.hpp"` (and link your program with `-lblas` for example). + +- Lapack + +Interface for Lapack linear solver libraries. +Simply `#include "multi/adaptors/lapack.hpp"` (and link your program with `-llapack` for example). + +- FFTW/cuFFT + +Interface for FFTW libraries, including FFTW 3, MKL, cuFFT/hipFFT (for GPU). +Simply `#include "multi/adaptors/fftw.hpp"` (and link your program with `-lfftw3` for example). + +- link:https://gitlab.com/correaa/boost-multi/-/blob/master/include/boost/multi/adaptors/mpi/README.md[MPI Adaptor 🔗] + +Use arrays (and subarrays) as messages for distributed interprocess communication (GPU and CPU) that can be passed to MPI functions through datatypes. +Simply `#include "multi/adaptors/mpi.hpp"`. + +- TotalView: visual debugger (commercial) + +Popular in HPC environments, can display arrays in human-readable form (for simple types, like `double` or `std::complex`). +Simply `#include "multi/adaptors/totalview.hpp"` and link to the TotalView libraries, compile and run the code with the TotalView debugger. diff --git a/doc/multi/intro.adoc b/doc/multi/intro.adoc new file mode 100644 index 000000000..850d82f8c --- /dev/null +++ b/doc/multi/intro.adoc @@ -0,0 +1,38 @@ +[#intro] + += Introduction + +:idprefix: intro_ + +_Multi_ is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory. + +Multidimensional array data structures are fundamental to several branches of computing, such as data analysis, image processing, and scientific simulations, and, in combination with GPUs, to Artificial Intelligence and Machine Learning. +This library offers array containers and subarrays in arbitrary dimensions with well-behaved value semantics, +featuring logical access recursively across dimensions and to elements through indices and iterators. + +The internal data structure layout is stride-based, which makes it compatible with low-level C libraries. + +The library interface is designed to be compatible with standard algorithms and ranges (STL) and special memory (including GPUs) and follows modern C++ design principles. + +Features of this library that aim to facilitate the manipulation of multidimensional arrays include: + +* Value semantics of multidimensional array containers and well-defined referential semantics to avoid unnecessary copies if possible. +* Availability of different access patterns to the elements in the multidimensional structure, as nested sequences or as a single sequence of elements. +A _D_-dimensional array can be interpreted either as an (STL-compatible) sequence of (_D_-1)-dimensional subarrays or as a flattened one-dimensional (also STL-compatible) sequence of elements. +* Interoperability with both legacy C and modern C++ libraries (e.g., STL, ranges, Thrust --CUDA and AMD GPUs--, Boost). +* Memory management and allocation to exploit modern memory spaces, including GPU memory, mapped memory, and fancy pointers. + +Do not confuse this library with +https://www.boost.org/doc/libs/1_69_0/libs/multi_array/doc/index.html[Boost.MultiArray] +or with the standard MDSpan proposal `std::mdspan`. +This library shares some of their goals and is compatible with them, but it is designed at a different level of generality and with other priorities (such as the features listed above). +The code is entirely independent and has fundamental implementation and semantics differences. + +The library's primary concern is with the storage and logic structure of data; +it doesn't make algebraic or geometric assumptions about the arrays and their elements. +(It is still a good building block for implementing mathematical algorithms, such as representing algebraic dense matrices in the 2D case.) + +The library does not throw exceptions and provides basic guarantees (such as no memory leaks) in their presence (e.g., thrown from allocations). +Indexing and other logical errors result in undefined behavior, which this library attempts to reflect via assertions. + +_Multi_ is a header-only library and C++17 or later is required. diff --git a/doc/multi/primer.adoc b/doc/multi/primer.adoc new file mode 100644 index 000000000..4e11e6b19 --- /dev/null +++ b/doc/multi/primer.adoc @@ -0,0 +1,71 @@ +[#primer] + += Primer (basic usage) + +:idprefix: primer_ + +In this example, we initialize a 2-dimensional array of numbers, and then we inspect the properties of this array, copy the array, and manipulate the element values. +The example can be followed link:https://godbolt.org/z/b8ao5Wz1T[online]. + +The individual elements are initialized from a nested rectangular list. +```cpp +multi::array A = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, +}; + +auto const [n, m] = A.sizes(); + +assert( n == 2 ); // or std::get<0>(A.sizes()) == 2 +assert( m == 3 ); // or std::get<1>(A.sizes()) == 3 + +assert( A.size() == 2 ); // size in first dimension, same as std::get<0>(A.sizes()) +assert( A.num_elements() == 6 ); // total number of elements +``` + +Individual elements can be accessed by the multidimensional indices, using brackets. + +```cpp +assert( A[1][2] == 6.0 ); +``` + +The value of an array can be copied, (moved,) and compared; +copies are equal but independent (disjoint). + +```cpp +std::array B = A; // we make a copy + +assert( B == A ); // copies are equal +assert( B.extensions() == A.extensions() ); // extensions (sizes) are equal +assert( B[0][1] == A[0][1] ); // all elements are equal +assert( &B[0][1] != &A[0][1] ); // elements are independent (dfferent addresses) +``` + +An array can be initialized from its sizes alone, in which case the element values are defaulted: + +```cpp +multi::array C({3, 4, 5}); +assert( C.num_elements() == 3*4*5 ); // 60 elements with unspecified values +``` + +Arrays can be passed by value or by reference. +Most of the time, arguments should be passed through generic parameters to also allow functions to work with parts (subblocks, slices, etc.) of an array. +The most useful functions work on the _concept_ of an array rather than on a concrete type, for example: + +```cpp +template // instead of the overspecific argument std::array +auto element_1_1(ArrayDouble2D const& m) -> double const& { return m[1][1]; } +... +assert( &element_1_1(A) == &A[1][1] ); +``` + +The function expects any array or subarray of dimension 2 (or greater) and returns an element with type `double`. + +The generic function template arguments that are not intended to be modified are passed by `const&`; otherwise, they are passed by forward-reference `&&`. +In this way, the functions can be applied to subblocks of larger matrices. + +```cpp +assert( &element_1_1(C[0]) == &C[0][1][1] ); +``` + +(Although most of the examples use numeric elements for conciseness, the library is designed to hold general types (e.g. non-numeric, non-trivial types, like `std::string`, other containers or, in general, user-defined value-types.) diff --git a/doc/multi/reference.adoc b/doc/multi/reference.adoc new file mode 100644 index 000000000..c6e72fa45 --- /dev/null +++ b/doc/multi/reference.adoc @@ -0,0 +1,465 @@ +[#reference] + += Reference + +:idprefix: reference_ + +## Fundamental types and concepts + +The library interface presents several closely related C++ types (classes) representing arrays. +The fundamental types represent multidimensional containers (called `array`), references that can refer to subsets of these containers (called `subarray`), and iterators. +In addition, there are other classes for advanced uses, such as multidimensional views of existing buffers (called `array_ref`) and non-resizable owning containers (called `static_array`). + +When using the library, it is simpler to start from `array`, and other types are rarely explicitly used, especially if using `auto`; +however, it is convenient for documentation to present the classes in a different order since the classes `subarray`, `array_ref`, `static_array`, and `array` have an *is-a* relationship (from left to right). +For example, `array_ref` has all the methods available to `subarray`, and `array` has all the operations of `array_ref` (and more). + +### Subarrays + +A subarray-reference is part (or a whole) of another larger array and they are represented by `multi::subarray` in the library. +It is important to understand that `subarray` s have referential semantics, their elements are not independent of the values of the larger arrays they are part of. +An instance of this class represents a subarray with elements of type `T` and dimensionality `D`, stored in memory described by the pointer type `P`. +(`T`, `D`, and `P` initials are used in this sense across the documentation.) + +Instances of this class have reference semantics and behave like "language references" as much as possible. +As references, they cannot be rebinded or resized; assignments are always "deep". +They are characterized by a size that does not change in the lifetime of the reference. +They are usually the result of indexing over other `multi::subarray`s and `multi::array`s objects, typically of higher dimensions; +therefore, the library doesn't expose constructors for this class. +The whole object can be invalidated if the original array is destroyed. + +(Additionally, `multi::const_subarray` provides a similar interface to `multi::subarray` but it protects referenced elements from being modified.) + +#### `multi::subarray` member types + +|=== +|`subarray::...` | Description + +|`value_type` | `multi::array` or, for `D == 1`, `T` (the element type) +|`reference` | `multi::subarray` or, for `D == 1`, `pointer_traits

::reference` (usually `T&`) +|`const_reference` | `multi::const_subarray` or, for `D == 1`, `pointer_traits

::rebind::reference` (usually `T const&`) +|`index` | indexing type in the leading dimension (usually `std::diffptr_t`) +|`size_type` | describe size (number of subarrays) in the leading dimension (signed version of pointer size type, usually std::diffptr_t) +|`index_rang` | describe ranges of indices, constructible from braced indices types or from an extension_type. Can be continuous (e.g. {2, 14}) or strided (e.g. {2, 14, /*every*/ 3}) +|`extesion_type` | describe a contiguous range of indices, constructible from braced index (e.g. `{0, 10}`) or from a single integer size (e.g. 10, equivalent to {0, 10}). +|`difference_type` | describe index differences in leading dimension (signed version of pointer size type, usually `std::diffptr_t`) +|`pointer` | `multi::subarray_ptr` or, for `D == 1`, `P` (the element pointer type, usually `T*`) +|`const_pointer` | `multi::const_subarray_ptr` or, for `D == 1`, `pointer_traits

::rebind` (usually `T const*`) +|`iterator` | describe a random-access iterator in the leading dimension +|`const_iterator` | describe a random-access iterator in the leading dimension to constant data +|=== + +#### `multi::subarray` special member functions + +|=== +| `subarray::...` | + +| (constructors) | not exposed; copy constructor is not available since the instances are not copyable; destructors are trivial since it doesn't own the elements +| `operator=` | assigns the elements from the source; the sizes must match +|=== + +It is important to note that assignments in this library are always "deep," and reference-like types cannot be rebound after construction. +(Reference-like types have corresponding pointer-like types that provide an extra level of indirection and can be rebound (just like language pointers); +these types are `multi::array_ptr` and `multi::subarray_ptr` corresponding to `multi::array_ref` and `multi::subarray` respectively.) + +#### `multi::subarray` relational functions + +|=== +| `operator==`/`operator!=` | Tells if elements of two `subarray` s are equal and if extensions of the subarrays are the same +| `operator<`/`operator<=` | Less-than/less-or-equal lexicographical comparison (requires elements to be comparable) +| `operator>`/`operator>=` | Greater-than/grater-or-equal lexicographical comparison (requires elements to be comparable) +|=== + +It is important to note that, in this library, comparisons are also always "deep". +Lexicographical order is defined recursively, starting from the first dimension index and from left to right. +For example, `A < B` if `A[0] < B[0]`, or `A[0] == B[0]` and `A[1] < B[1]`, or ..., etc. +Lexicographical order applies naturally if the extensions of `A` and `B` are different; however, their dimensionalities must match. +(See sort examples). + +#### `multi::subarray` shape access + +|=== +| `sizes` | returns a tuple with the sizes in each dimension +| `extensions` | returns a tuple with the extensions in each dimension +| `size` | returns the number of subarrays contained in the first dimension +| `extension` | returns a contiguous index range describing the set of valid indices +| `num_elements` | returns the total number of elements +|=== + +#### `multi::subarray` element access + +|=== +|`operator[]` | access specified element by index (single argument), returns a `reference` (see above), for `D > 1` it can be used recursively +|`front` | access first element (undefined result if array is empty). Takes no argument. +|`back` | access last element (undefined result ifarray is empty). Takes no argument. +|`operator()` | When used with zero arguments, it returns a `subarray` reference representing the whole array. +|`operator()(i) | When used with one argument, access a specified element by index (return a `reference`) or by range (return a `subarray` of equal dimension). +|=== + +- `subarray::operator()(i, j, k, ...)`, as in `S(i, j, k)` for indices `i`, `j`, `k` is a synonym for `A[i][j][k]`, the number of indices can be lower than the total dimension (e.g., `S` can be 4D). +Each index argument lowers the dimension by one. +- `subarray::operator()(ii, jj, kk)`, the arguments can be indices or ranges of indices (`index_range` member type). +This function allows positional-aware ranges. +Each index argument lowers the rank by one. +A special range is given by `multi::_`, which means "the whole range" (also spelled `multi::all`). +For example, if `S` is a 3D of sizes 10-by-10-by-10, `S(3, {2, 8}, {3, 5})` gives a reference to a 2D array where the first index is fixed at 3, with sizes 6-by-2 referring the subblock in the second and third dimension. +Note that `S(3, {2, 8}, {3, 5})` (6-by-2) is not equivalent to `S[3]({2, 8})({3, 5})` (2-by-10). +- `operator()()` (no arguments) gives the same array but always as a subarray type (for consistency), `S()` is equivalent to `S(S.extension())` and, in turn to `S(multi::_)` or `S(multi::all)`. + +#### `multi::subarray` structure access + +These member functions are generally used for accessing details of the internal data structure (layout) interfacing with C-libraries. + +|=== +| `subarray::...` | + +| `layout` | returns a single layout object with stride and size information +| `base` | direct access to underlying memory pointer (`S[i][j]... == S.base() + std::get<0>(S.strides())*i + std::get<1>(S.strides())*j + ...`) +| `stride` | return the stride value of the leading dimension, e.g `(&A[1][0][0]... - &A[0][0]...)` +| `strides` | returns a tuple with the strides defining the internal layout +|=== + +#### `multi::subarray` iterators + +|=== +| `subarray::...` | + +| `begin/cbegin` | returns (const) iterator to the beginning +| `end/cend` | returns (const) iterator to the end +|=== + +#### `multi::subarray` subarray/array generators + +These operations generate different ways to view the elements of a (sub)array, but without copying elements or allocate) + +|=== +| `subarray::...` | (these operations do not copy elements or allocate) + +| `broadcasted` | returns a view of higher dimensionality (`D {plus} 1`) obtained by infinite repetition of the original array. (This returns a special kind of subarray with a degenerate layout and no size operation. Takes no argument.) +| `chunked` | a view of higher dimensionality resulting from partitioning the original into subarrays of a certain length +| `dropped` | (takes one integer argument `n`) returns a subarray with the first n-elements (in the first dimension) dropped from the original subarray. This doesn't remove or destroy elements or resize the original array +| `element_transformed` | creates a view of the array, where each element is transformed according to a function (first and only argument) +| `elements` | a flatted view of all the elements rearranged canonically. `A.elements()[0] -> A[0][0]`, `A.elements()[1] -> A[0][1]`, etc. The type of the result is not a subarray but a special kind of range. Takes no argument. +| `rotated/unrotated` | a view (`subarray`) of the original array with indices (un)rotated from right to left (left to right), for `D = 1` returns the same `subarray`. For given `i`, `j`, `k`, `A[i][j][k]` gives the same element as `A.rotated()[j][k][i]` and, in turn the same as `A.unrotated()[k][i][j])`. Preserves dimension. The function is cyclic; `D` applications will give the original view. Takes no argument. +| `taked` | a view of the original array with the first elements +| `transposed` (same as prefix `operator~`) | a view (`subarray`) of the original array with the first two indices exchanged, only available for `D > 1`; for `D = 2`, `rotated`, `unrotated` and `transposed` give same view. Takes no argument. +| `partitioned` | a view of higher dimensionality resulting by splitting the original range in a certain number of parts (complementary to `.chunked`) +| `sliced` | (takes two index arguments `a` and `b`) returns a subarray with elements from index `a` to index `b` (non-inclusive) `{S[a], ... S[b-1]}`. Preserves the dimension. +| `strided` | (takes one integer argument `s`) returns a subarray skipping `s` elements. Preserves the dimension. +| `static_array_cast(args...)` | produces a view where the underlying pointer constructed by `P2{A.base(), args...}`. Usually, `args...` is empty. Non-empty arguments are useful for stateful fancy pointers, such as transformer iterators. +| `reinterpret_cast_array` | underlying elements are reinterpreted as type T2, element sizes (`sizeof`) have to be equal; `reinterpret_cast_array(n)` produces a view where the underlying elements are interpreted as an array of `n` elements of type `T2`. +|=== + +This function creates an indipendent copy of any (sub)array view: + +|=== +| `subarray::...` | (these operations do not copy elements or allocate) + +| `decay` (same as prefix unary `operator+`) | creates a concrete independent `array` with the same dimension and elements as the view. Usually used to force a value type (and forcing a copy of the elements) and avoid the propagation of a reference type in combination with `auto` (e.g., `auto A2_copy = + A[2];`). +|=== + +A reference `subarray` can be invalidated when its origin array is invalidated or destroyed. +For example, if the `array` from which it originates is destroyed or resized. + +### Array references + +An array reference _D_-dimensional view of the contiguous pre-existing memory buffer, and it is represented by the type `multi::array_ref`. +This class doesn't manage the elements it contains, and it has reference semantics (it can't be rebound, assignments are deep, and have the same size restrictions as `subarray`) + +Since `array_ref` is-a `subarray`, it inherits all the class methods and types described before and, in addition, it defines these members below. + +|=== +| Member types | same as for `subarray` +|=== + +|=== +| Member functions | same as for `subarray` plus ... + +| (constructors) | `array_ref::array_ref({e1, e2, ...}, p)` constructs a D-dimensional view of the contiguous range starting at p and ending at least after the size size of the multidimensional array (product of sizes). The default constructor and copy constructor are not exposed. Destructor is trivial since elements are not owned or managed. +|=== + +|=== +| Element access | same as for `subarray` +|=== + +|=== +| Structure access | same as for `subarray` +|=== + +|=== +| Iterators | same as for `subarray` +|=== + +|=== +| Capacity | same as for `subarray` +|=== + +|=== +| Creating views | same as for `subarray` +|=== + +|=== +| Creating arrays | same as for `subarray` +|=== + +|=== +| Relational functions | same as for `subarray` +|=== + +An `array_ref` can be invalidated if the original buffer is deallocated. + +### Static arrays + +A static array is a _D_-dimensional array that manages an internal memory buffer, and it is represented by `multi::static_array>`. +This class owns the elements it contains; it has _restricted_ value semantics because assignments are restricted to sources with equal sizes. +Memory is requested by an allocator of type Alloc (standard allocator by default). +It supports stateful and polymorphic allocators, which are the default for the special type `multi::pmr::static_array`. + +For most uses, a `multi::array` should be preferred instead. + +The main feature of this class is that its iterators, subarrays, and pointers do not get invalidated unless the whole object is destroyed. +In this sense, it is semantically similar to a C-array, except that elements are allocated from the heap. +It can be useful for scoped uses of arrays and multi-threaded programming and to ensure that assignments do not incur allocations. +The C++ coreguiles proposed a similar (albeith one-dimensional) class, called linkL:http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#gslowner-ownership-pointers[`gsl::dyn_array`]. + +|=== +| Member types | same as for `array_ref` +|=== + +|=== +| Member fuctions | same as for `array_ref` plus ... + +| (constructors) | `static_array::static_array({e1, e2, ...}, T val = {}, Alloc = {})` constructs a D-dimensional array by allocating elements. `static_array::static_array(std::initializer_list<...>` constructs the array with elements initialized from a nested list. +| (destructor) | Destructor deallocates memory and destroy the elements +| `operator=` | assigns the elements from the source, sizes must match. +|=== + +|=== +| Element access | same as for `array_ref` +|=== + +|=== +| Structure access | same as for `array_ref` +|=== + +|=== +| Iterators | same as for `array_ref` +|=== + +|=== +| Capacity | same as for `array_ref` +|=== + +|=== +| Creating views | same as for `array_ref` +|=== + +|=== +| Creating arrays | same as for `array_ref` +|=== + +|=== +| Relational fuctions | same as for `array_ref` +|=== + +### Arrays + +An array of integer positive dimension D has value semantics if element type T has value semantics and it is represented by `multi::array>`. +It supports stateful and polymorphic allocators, which is implied for the special type `multi::pmr::array`. + +|=== +| Member types | same as for `static_array` (see above) +|=== + +|=== +| Member fuctions | + +| (constructors) | `array::array({e1, e2, ...}, T val = {}, Alloc = {})` constructs a D-dimensional array by allocating elements;`array::array(It first, It last)` and `array::array(Range const& rng)`, same for a range of subarrays. `static_array::static_array(std::initializer_list<...>, Alloc = {})` constructs the array with elements initialized from a nested list. +| (destructor) | Destructor deallocates memory and destroy the elements +| `operator=` | assigns for a source `subarray`, or from another `array`. `array`s can be moved +|=== + +|=== +| Element access | same as for `static_array` +|=== + +|=== +| Structure access | same as for `static_array` +|=== + +|=== +| Iterators | same as for `static_array` +|=== + +|=== +| Capacity | same as for `static_array` +|=== + +|=== +| Creating views | same as for `static_array` +|=== + +|=== +| Creating arrays | same as for `static_array` +|=== + +|=== +| Relational fuctions | same as for `static_array` +|=== + +|=== +| Manipulation | + +| `clear` | Erases all elements from the container. The array is resized to zero size. +| `reextent` | Changes the size of the array to new extensions. `reextent({e1, e2, ...})` elements are preserved when possible. New elements are initialized with a default value `v` with a second argument `reextent({e1, e2, ...}, v)`. The first argument is of `extensions_type`, and the second is optional for element types with a default constructor. +|=== + + +### Iterators + +The library offers random-access iterator to subarrays of dimension `D - 1`. and they are represented by types of the form `multi::[sub]array::(const_)iterator`. +These is generally used to interact with or implement algorithms. +They can be default constructed but do not expose other constructors since they are generally created from `begin` or `end`, manipulated arithmetically, `operator--`, `operator++` (pre and postfix), or random jumps `operator+`/`operator-` and `operator+=`/`operator-=`. +They can be dereferenced by `operator*` and index access `operator[]`, returning objects of lower dimension `subarray::reference` (see above). +Note that this is the same type for all related arrays, for example, `multi::array::(const_)iterator`. + +`iterator` can be invalidated when its original array is invalidated, destroyed or resized. +An `iterator` that stems from `static_array` becomes invalid only if the original array was destroyed or out-of-scope. + +## Type Requirements + +The library design tries to impose the minimum possible requirements over the types that parameterize the arrays. +Array operations assume that the contained type (element type) are regular (i.e. different element represent disjoint entities that behave like values). +Pointer-like random access types can be used as substitutes of built-in pointers. +(Therefore pointers to special memory and fancy-pointers are supported.) + +### Linear Sequences: Pointers + +An `array_ref` can reference an arbitrary random access linear sequence (e.g. memory block defined by pointer and size). +This way, any linear sequence (e.g. `raw memory`, `std::vector`, `std::queue`) can be efficiently arranged as a multidimensional array. + +```cpp +std::vector buffer(100); +multi::array_ref A({10, 10}, buffer.data()); +A[1][1] = 9.0; + +assert( buffer[11] == 9.0 ); // the target memory is affected +``` +Since `array_ref` does not manage the memory associated with it, the reference can be simply dangle if the `buffer` memory is reallocated (e.g. by vector-`resize` in this case). + +### Special Memory: Pointers and Views + +`array`s manage their memory behind the scenes through allocators, which can be specified at construction. +It can handle special memory, as long as the underlying types behave coherently, these include link:https://en.cppreference.com/w/cpp/named_req/Allocator#Fancy_pointers[fancy pointers] (and fancy references). +Associated fancy pointers and fancy reference (if any) are deduced from the allocator types. + +#### Allocators and Fancy Pointers + +Specific uses of fancy memory are file-mapped memory or interprocess shared memory. +This example illustrates memory persistency by combining with Boost.Interprocess library. +The arrays support their allocators and fancy pointers (`boost::interprocess::offset_ptr`). + +```cpp +#include +using namespace boost::interprocess; +using manager = managed_mapped_file; +template using mallocator = allocator; +decltype(auto) get_allocator(manager& m) {return m.get_segment_manager();} + +template using marray = multi::array>; + +int main() { +{ + manager m{create_only, "mapped_file.bin", 1 << 25}; + auto&& arr2d = *m.construct>("arr2d")(marray::extensions_type{1000, 1000}, 0.0, get_allocator(m)); + arr2d[4][5] = 45.001; +} +// imagine execution restarts here, the file "mapped_file.bin" persists +{ + manager m{open_only, "mapped_file.bin"}; + auto&& arr2d = *m.find>("arr2d").first; + assert( arr2d[7][8] == 0. ); + assert( arr2d[4][5] == 45.001 ); + m.destroy>("arr2d"); +} +} +``` +link:https://godbolt.org/z/oeTss3s35[(live)] + +(See also, examples of interactions with the CUDA Thrust library to see more uses of special pointer types to handle special memory.) + +### Transformed views + +Another kind of use of the internal pointer-like type is to transform underlying values. +These are useful to create "projections" or "views" of data elements. +In the following example a "transforming pointer" is used to create a conjugated view of the elements. +In combination with a transposed view, it can create a hermitic (transposed-conjugate) view of the matrix (without copying elements). +We can adapt the library type `boost::transform_iterator` to save coding, but other libraries can be used also. +The hermitized view is read-only, but with additional work, a read-write view can be created (see `multi::::hermitized` in multi-adaptors). + +```cpp +constexpr auto conj = [](auto const& c) {return std::conj(c);}; + +template struct conjr : boost::transform_iterator { + template conjr(As const&... as) : boost::transform_iterator{as...} {} +}; + +template +auto hermitized(Array2D const& arr) { + return arr + .transposed() // lazily tranposes the array + .template static_array_cast>(conj) // lazy conjugate elements + ; +} + +int main() { + using namespace std::complex_literals; + multi::array A = { + { 1.0 + 2.0i, 3.0 + 4.0i}, + { 8.0 + 9.0i, 10.0 + 11.0i} + }; + + auto const& Ah = hermitized(A); + + assert( Ah[1][0] == std::conj(A[0][1]) ); +} +``` + +To simplify this boilerplate, the library provides the `.element_transformed(F)` method that will apply a transformation `F` to each element of the array. +In this example, the original array is transformed into a transposed array with duplicated elements. + +```cpp + multi::array A = { + {1.0, 2.0}, + {3.0, 4.0}, + }; + + auto const scale = [](auto x) { return x * 2.0; }; + + auto B = + A.transposed().element_transformed(scale); + assert( B[1][0] == A[0][1] * 2 ); +``` + +link:https://godbolt.org/z/TYavYEG1T[(live)] + +Since `element_transformed` is a reference-like object (transformed view) to the original data, it is important to understand the semantics of evaluation and possible allocations incurred. +As mentioned in other sections using `auto` and/or `+` appropriately can lead to simple and efficient expressions. + +|=== +| Construction | Allocation of `T`s | Initialization (of `T`s) | Evaluation (of `fun`) | Notes + +| `multi::array const B = A.element_transformed(fun);` | Yes | No | Yes | Implicit conversion to `T` if result is different, dimensions must match. B can be mutable. +| `multi::array const B = + A.element_transformed(fun);` | Yes (and move, or might allocate twice if types don't match) | No | Yes | Not recommended +| `multi::array const B{A.element_transformed(fun)};` | Yes | No | Yes | Explicit conversion to `T` if result is different, dimensions must match +| `auto const B = + A.elements_transformed(fun);` | Yes | No | Yes | Types and dimension are deduced, result is contiguous, preferred +| `auto const B = A.element_transformed(fun);` | No | No | No (delayed) | Result is effective a reference, may dangle with `A`, usually `const`, not recommended +| `auto const& B = A.elements_transformed(fun);` | No | No | No (delayed) | Result is effective a reference, may dangle with `A`. Preferred way. +| `multi::array B(A.extensions()); B = A.element_transformed(fun);` | Yes | Yes (during construction) | Yes | "Two-step" construction. `B` is mutable. Not recommended +|=== + +|=== +| Assigment | Allocation of `T`s | Initialization (of `T`s) | Evaluation (of `fun`) | Notes + +| `B = A.elements_transformed(fun);` | No, if sizes match | Possibly (when `B` was initialized) | Yes | `B` can't be declared `const`, it can be a writable subarray, preferred +| `B = + A.elements_transformed(fun);` | Yes | Possibly (when `B` was initialized) | Yes | Not recommended. +|=== diff --git a/doc/multi/technical.adoc b/doc/multi/technical.adoc new file mode 100644 index 000000000..9b84fb5ad --- /dev/null +++ b/doc/multi/technical.adoc @@ -0,0 +1,59 @@ +[#technical] + += Technical points + +:idprefix: technical_ + +## Indexing (square brackets vs. parenthesis?) + +The chained bracket notation (`A[i][j][k]`) allows you to refer to elements and lower-dimensional subarrays consistently and generically, and it is the recommended way to access array objects. +It is a frequently raised question whether the chained bracket notation is beneficial for performance, as each use of the bracket leads to the creation of temporary objects, which in turn generates a partial copy of the layout. +Moreover, this goes against link:https://isocpp.org/wiki/faq/operator-overloading#matrix-subscript-op[historical recommendations]. + +It turns out that modern compilers with a fair level of optimization (`-O2`) can elide these temporary objects so that `A[i][j][k]` generates identical machine code as `A.base() + i*stride1 + j*stride2 + k*stride3` (+offsets not shown). +In a subsequent optimization, constant indices can have their "partial stride" computation removed from loops. +As a result, these two loops lead to the link:https://godbolt.org/z/eEWWq34Yc[same machine code]: + +```cpp + // given the values of i and k and accumulating variable acc ... + for(long j = 0; j != M; ++j) {acc += A[i][j][k];} +``` +```cpp + auto* base = A.base() + i*std::get<0>(A.strides()) + k*std::get<2>(A.strides()); + for(long j = 0; j != M; ++j) {acc += *(base + j*std::get<1>(A.strides()));} +``` + +Incidentally, the library also supports parenthesis notation with multiple indices `A(i, j, k)` for element or partial access; +it does so as part of a more general syntax to generate sub-blocks. +In any case, `A(i, j, k)` is expanded to `A[i][j][k]` internally in the library when `i`, `j`, and `k` are normal integer indices. +For this reason, `A(i, j, k)`, `A(i, j)(k)`, `A(i)(j)(k)`, `A[i](j)[k]` are examples of equivalent expressions. + +(Since C++23, the library also accepts multidimensional subscript notation `A[i, j, k]`) + +Sub-block notation, when at least one argument is an index range, e.g., `A({i0, i1}, j, k)` has no equivalent square-bracket notation. +Note also that `A({i0, i1}, j, k)` is not equivalent to `A({i0, i1})(j, k)`; their resulting sublocks have different dimensionality. + +Additionally, array coordinates can be directly stored in tuple-like data structures, allowing this functional syntax: + +```cpp +std::array p = {2, 3, 4}; +std::apply(A, p) = 234; // same as assignment A(2, 3, 4) = 234; and same as A[2][3][4] = 234; +``` + +## Iteration past-end in the abstract machine + +It's crucial to grasp that pointers are limited to referencing valid memory in the strict C abstract machine, such as allocated memory. +This understanding is key to avoiding undefined behavior in your code. +Since the library iteration is pointer-based, the iterators replicate these restrictions. + +There are three cases to consider; the first two can be illustrated with one-dimensional arrays, and one is intrinsic to multiple dimensions. + +The first case is that of strided views (e.g. `A.strided(n)`) whose stride value are not divisors of original array size. +The second case is that or negative strides in general. +The third case is that of iterators of transposed array. + +In all these cases, the `.end()` iterator may point to invalid memory. +It's important to note that the act of constructing certain iterators, even if the element is never dereferenced, is undefined in the abstract machine. +This underscores the need for caution when using such operations in your code. + +A thorough description of the cases and workaround is beyond the scope of this section. diff --git a/doc/multi/tutorial.adoc b/doc/multi/tutorial.adoc new file mode 100644 index 000000000..d1cfa26d7 --- /dev/null +++ b/doc/multi/tutorial.adoc @@ -0,0 +1,959 @@ +[#tutorial] + += Tutorial (advanced usage) + +:idprefix: tutorial_ + +In this example, we are going to use memory that is not managed by the library and manipulate the elements. +We can create a static C-array of ``double``s, and refer to it via a bidimensional array `multi::array_ref`. + +(This tutorial can be followed https://godbolt.org/z/4zWTPcoK6[online].) + +```cpp +#include + +namespace multi = boost::multi; + +int main() { + double d_data[20] = { + 150.0, 16.0, 17.0, 18.0, 19.0, + 30.0, 1.0, 2.0, 3.0, 4.0, + 100.0, 11.0, 12.0, 13.0, 14.0, + 50.0, 6.0, 7.0, 8.0, 9.0 + }; // block of 20 elements ... + multi::array_ref d2D_ref(&d_data[0], {4, 5}); // .. interpreted as a 4 by 5 array + ... +``` + +Next, we print the elements in a way that corresponds to the logical arrangement. +To do that, first, we extract the extensions (the 2-dimensional range of valid indexes) of the array, +then we decompose it as the cartesian product of two 1-dimensional ranges of integer indices. + +```cpp +#include // for print + ... + auto [is, js] = d2D_ref.extensions(); + for(auto i : is) { + for(auto j : js) { + std::cout<< d2D_ref[i][j] <<' '; + } + std::cout <<'\n'; + } +``` + +This will output: + +> ``` +> 150 16 17 18 19 +> 30 1 2 3 4 +> 100 11 12 13 14 +> 50 6 7 8 9 +> ``` + +The arrays provide iterator-based access, which allows it to interface with algorithms and implement new ones. + +It is sometimes said (by Sean Parent) that the whole of STL algorithms can be seen as intermediate pieces to implement `std::stable_sort`. +Presumably, if one can sort over a range, one can perform any other standard algorithm. + +```cpp +#include // for sort + ... + std::stable_sort( d2D_ref.begin(), d2D_ref.end() ); +``` + +If we print the result again, we get: + +> ``` +> 30 1 2 3 4 +> 50 6 7 8 9 +> 100 11 12 13 14 +> 150 16 17 18 19 +> ``` + +The array has been changed to be in row-based lexicographical order. +Since the sorted array is a reference to the original data, the original C-array has changed. + +(Note that `std::sort` cannot be applied directly to a multidimensional C-array or to other libraries, such as Boost.MultiArray. +The library here are supports all STL algorithms directly.) + +If we want to order the matrix on a per-column basis, we need to "view" the matrix as a range of columns. +This is done in the bidimensional case, by accessing the matrix as a range of columns: + +```cpp + ... + std::stable_sort( d2D_ref.rotated().begin(), d2D_ref.rotated().end() ); +} +``` + +The `rotate` operation rotates indices, providing a new logical view of the original array without modifying it. + +In this case, the original array will be transformed by sorting the matrix into: + +> ``` +> 1 2 3 4 30 +> 6 7 8 9 50 +> 11 12 13 14 100 +> 16 17 18 19 150 +> ``` + +By combining index rotations and transpositions, an array of dimension `D` can be viewed simultaneously as `D!` (D-factorial) different ranges of different "transpositions" (rotation/permutation of indices.) + +== Initialization + +`array_ref` is initialized from a preexisting contiguous range, the index extensions should be compatible with the total number of elements. + +```cpp +double* dp = new double[12]; +multi::array_ref A({3, 4}, dp); +multi::array_ref B({2, 6}, dp); +... +delete[] dp; +``` + +Array references do not own memory and, just as language references, can not be rebinded (i.e. resized or "reseated") to refer to a different memory location. +Since `array_ref` is an array reference, it can "dangle" if the original memory is deallocated. + +Array objects (`multi::array`), in contrast, own the elements they contain and can be resized later. +An `array` is initialized by specifying the index extensions and, optionally, a default value). + +```cpp +multi::array A1({3} , 11.0); // {11.0, 11.0, 11.0} + +multi::array A2({2, 3} , 22.0); // { {22.0, 22.0, 22.}, {22.0, 22.0, 22.0} } + +multi::array A3({3, 2, 2}, 33.0); // { { { 33., ...}, { ... }, ... } } +``` +... or alternatively from a rectangular list. + +```cpp +multi::array A1 = {1.0, 2.0, 3.0}; +assert( num_elements(A1)==3 ); + +multi::array A2 { + { 1.0, 2.0, 3.0}, + { 4.0, 5.0, 6.0} +}; + +assert( num_elements(A2) == 2*3); + +multi::array const A3 = { + {{ 1.2, 0.0}, { 2.4, 1.0}}, + {{11.2, 3.0}, {34.4, 4.0}}, + {{15.2, 99.0}, {32.4, 2.0}} +}; + +assert( A3.num_elements() == 3 * 2 * 2 ); +``` + +In all cases, constness (`const` declaration) is honored in the expected way. + +== Copy, and assigment (, and aliasing) + +The library offers value semantics for the `multi::array` family of classes. +Constructing or assigning from an existing array generates a copy of the original object, independent of the original one but equal in value. + +```cpp +auto B2 = A2; // same as multi::array B2 = A2; (A2 is defined above) + +assert( B2 == A2 ); // copies have the same element values (and also the same shape) +assert( B2[0][0] == A2[0][0] ) +assert( &B2[0][0] != &A2[0][0] ); // but they are independent +``` + +A (mutable) array can be assigned at any moment, independently of the previous state or shape (extensions). +The dimensionalities must match. +```cpp +B2 = A2; // both have dimensionality 2 +``` + +Sometimes it is necessary to generate copies from views or subblocks. +```cpp +multi::array C2 = A2( {0, 2}, {0, 2} ); +``` +or equivalently, +```cpp +auto C2 = + A2( {0, 2}, {0, 2} ); +``` +Note the use of the prefix `\+` (plus) as an indicator that a copy must be created (it has no arithmetic implications). +Due to a language limitation, omitting the plus symbol will create another non-independent reference view of the left-hand side, which is generally undesired. + +Subarray-references can also assigned, but only if the shapes of the left-hand side (LHS) and right-hand side (RHS) match. +Otherwise, the behavior is undefined (in debug mode, the program will fail an assertion). + +```cpp +C2( {0, 2}, {0, 2} ) = A2( {0, 2}, {0, 2} ); // both are 2x2 views of arrays, *elements* are copied +``` + +Using the same or overlapping arrays in the RHS and LHS of assignment produces undefined behavior in general (and the library doesn't check). +Notably, this instruction does not transpose the array but produces an undefined result: + +```cpp +A2 = A2.transposed(); // undefined result, this is an error +``` + +This is an instance of the problem of _data aliasing_, which describes a common situation in which a data location in memory can be accessed through different parts of an expression or function call. + +This statement below, instead, does produce a transposition, at the cost of making one copy (implied by `+`) of the transposed array first and assigning (or moving) it back to the original array. + +```cpp +A2 = + A2.transposed(); // ok, (might allocate) +``` + +Within the confines of the library interface, this pitfall can only occur on assignment. +A generic workaround is to use the prefix `operator+`, to break "aliasing" as above. + +In general, the problem of aliasing can persist when taking mutable array-references in function arguments. +The most general solution to this problem is to make copies or directly work with completely disjoint objects. +Other case-by-case solutions might be possible. +(For example, in-place transposition (as attempted above) is an active subject of research; +_optimal_ speed and memory transpositions might require specially designed libraries.) + +Finally, arrays can be efficiently moved by transferring ownership of the internal data. + +```cpp +auto B2 = std::move(A2); // A2 is empty after this +``` + +Subarrays do not own the data; therefore they cannot directly take advantage of this feature. +However, individual elements of a view can still be moved; this is particularly useful if the elements are expensive to copy (elements that are containers themselves for exampe). +A "moved" subview is simply another kind of view of the elements. + +```cpp +multi::array, 2> A({10, 10}, std::vector(1000)); +multi::array, 2> B({10, 10}); +... +B[1] = A[2].element_moved(); +``` + +Each of the 10 *elements* of the third row of `A` is moved into the second row of `B`. +`A[2]` still has 10 (moved-from) empty vectors. + + +== Change sizes (extents) + +Arrays can change their size while _preserving elements_ with the `reextent` method. + +```cpp +multi::array A = { + {1, 2, 3}, + {4, 5, 6} +}; + +A.reextent({4, 4}); + +assert( A[0][0] == 1 ); +``` + +An alternative syntax with an additional parameter, `.reextent({...}, value)`, sets _new_ (not preexisting) elements to a specific value. + +The primary purpose of `reextent` is element preservation. +All calls to `reextent` allocate and deallocate memory; therefore, they are not amortized. +If element preservation is not desired, a simple assignment (move) from a new array better expresses the intention and is more efficient since it doesn't need to copy preexisting elements. + +```cpp +A = multi::array({4, 4}); // extensions like A.reextent({4, 4}) but elements are not preserved + +A = multi::array({4, 4}, 99) // for initialization with specific value 99 + +A = {}; // empties the array, equivalent to `A.reextent({0, 0});`. +``` + +Subarrays or views cannot change their size or be emptied (e.g., `A[1].rextent({4})` or `A[1].clear()` will not compile). +For the same reason, subarrays cannot be assigned from an array or another subarray of different size. + +Changing the size of arrays by `reextent`, `clear`, or assignment generally invalidates existing iterators and ranges/views. + +== Iteration (vs range-loops) + +Historically, iteration over arrays has been done with index-based `for`-loops, where each nesting level is associated with a subdimension. +For this type of usage, the valid range of indices in all the dimensions of an array is extracted with `.extensions()`, and in the 2D case, `.extensions()` can be conveniently decomposed into two ranges, one for each dimension. + +```cpp + multi::array A = { + {1, 2, 3}, + {4, 5, 6} + }; + + auto [is, js] = A.extensions(); + for(auto i : is) { // is == {0, 1} (range from 0 to 2, not included) + for(auto j : js) { // ij = {0, 1, 2} (range from 0 to 3, not included) + A[i][j] *= 2; + } + } +``` + +Using C++'s range-based for, the elements of the 2D array can be accessed directly without intermediate indices: + +```cpp + for(auto&& row : A) { + for(auto&& e: row) { // equivalent to for(auto& e: row) or for(int& e: row) + e *= 2; + } + } +``` + +However, in some cases it is better to use the iterator-based interface. +The iterator-based interface is more convenient to express and interact with generic algorithms, which in turn can be parallelized and less prone to index errors (such as off-by-one, and out-of-range access.) + +Array (and subarray-references) provide a members `.begin()` and `.end()` that produce iterators that access the multidimensional structure through the first dimension (leftmost index). +Accessing arrays by iterators (`begin`/`end`) enables the use of many iterator-based algorithms (see the sort example above). +`begin(A)/end(A)` (or equivalently `A.begin()/A.end()`) gives iterators that are linear and random access in the leading dimension. +Since these iterators are categorized as random-access, arithmetic can be performed on them, for example `it += n;` and `++it` will advance `it` by `n` positions or by one position respectively. + +As an alternative, the elements can be iterated in a flat manner, using the `.elements()` member. +This flattening is done in a canonical order (rightmost index changes fastest) and it is provided whether the elements are contiguous or not in memory. +This "elements" range also provides the begin and end iterators (`.elements().begin()`). + +Other non-leading dimensions can be obtained by "rotating" indices first. +`A.rotated().begin()/.end()` gives access to a range of subarrays in the second dimension number (the first dimension is put at the end). +(`.cbegin()/.cend()` give constant (read-only) access.) + +As an example, this function allows printing arrays of arbitrary dimensionality into a linear comma-separated form. + +```cpp +void recursive_print(double const& d) { cout< +void recursive_print(Array const& ma) { + cout << "{"; + if(! ma.empty()) { + flat_print(*ma.begin()); // first element + std::for_each(ma.begin() + 1, ma.end(), [](auto const& e) { cout<<", "; flat_print(e);}); // rest + } + cout << "}"; +} +... +recursive_print(A); +``` +> ``` +> {{{1.2, 1.1}, {2.4, 1}}, {{11.2, 3}, {34.4, 4}}, {{15.2, 99}, {32.4, 2}}} +> ``` + +Except for those corresponding to the one-dimensional case, dereferencing iterators generally produce "proxy"-references (i.e. objects that behave in a large degree like language references). +These references can be given a name; using `auto` can be misleading since the resulting variable does not have value semantics. + +```cpp +auto row = *A.begin(); // accepted by the language but misleading, row is *not* a value independent of A +``` + +In my experience, however, the following usage pattern produces a more consistent idiom for generating references (still without copying elements): + +```cpp +auto&& row0 = *A.begin() ; // same as decltype(A):: reference row0 = * begin(A); +auto const& crow0 = *A.cbegin(); // same as decltype(A)::const_reference crow0 = *cbegin(A); + +auto&& row1 = A [1]; // same as decltype(A):: reference row1 = A [1]; +auto const& crow1 = std::as_const(A)[1]; // same as decltype(A)::const_reference crow0 = std::as_const(A)[1]; +``` + +If a new value is desired, these (equivalent) options express the intention more explicitly: + +```cpp +decltype(A)::value_type row = *begin(A); // there is a real copy of the row + auto row = + *begin(A); // there is another copy, note the use of '+' (unary plus) +``` + +In the examples above all elements are accessed in a nested way, recursively down the dimensions. +To iterate over all the elements regardless of the multidimensional structure the following function can print all the elements. + +```cpp +template +void flat_print(Array const& ma) { + cout << "["; + std::for_each(ma.elements().begin(), ma.elements().end(), [](auto&& e) { cout<< e << ", ";}); + cout << "]"; +} +... +recursive_print(A); +``` +> ``` +> [1.2, 1.1, 2.4, 1, 11.2, 3, 34.4, 4, 15.2, 99, 32.4, 2] +> ``` + +This feature allows to view the array as a flat sequence using the `.elements()` range, which also has `.begin()`/`.end()` and indexing. + +// == "Pointer" to subarray + +// The library strongly relies on value-semantics, and it doesn't entertain the concept of "shallow" copy; +// however, it supports reference- and pointer-semantics. + +// Subarrays (e.g., rows in a 2D array) are reference-like objects with a concrete address-like value that identifies them uniquely. +// These addresses, which behave like pointers, can be helpful to "mark" subviews; these markers can be copied and stored in arrays. + +// ```cpp +// auto A = multi::array({4, 4}); + +// auto row2_ptr = &A[2]; // A[2] is a row of A (not an element) +// assert( row2_ptr == &*(A.begin() + 2) ); +// ``` + +// The expression `A[2]` above is technically a C++ temporary object, and therefore it doesn't have a C++ address (taking `std::addressof` gives a compilation error). +// However, in the library's abstraction, `A[2]` references an existing part of the original array, i.e. it is a "library reference", whose "library address" can be obtained with the `&` operator. +// The case is an illustration that, in the library, operator `&` is, for subarrays, different than the `std::addressof` operator; the latter may not be defined and even not compile for some expressions. + +// Comparing these markers/pointers with different provenance, i.e., originating from different arrays, is generally undefined. + +== Indexing + +Arrays provide random access to elements or subviews. +Many algorithms on arrays are oriented to linear algebra, +which are ubiquitously implemented in terms of multidimensional index access. + +Iterator access and index access are two alternatives for accessing elements. +For example `*(begin(A) + n)` and `A[n]` are equivalent +and the range defined by the pair `begin(A), end(A)` is equivalent to `A(extension(A))` and, in turn, to `A()` (even for a multidimensional array, `D > 1`). +The syntax can be combined in arbitrary ways, for example `*begin(A[n])` is equivalent to `A[n][0]`. + +== Element access and partial access + +Index access mimics that of C-fixed sizes arrays. +For example, a 2-dimensional array will access to an element by specifying two indices `A[1][2]`, +which can be used for direct write and read operations; +while _partial_ index arguments `A[1]` generate a view 1-dimensional object (a reference). + +```cpp +A // is a 2D value array +A[0] // is a 1D "reference"/"view" array +A[0][0] // is a an element reference, zero-D +``` + +Transpositions are also multidimensional arrays _views_ in which the index are *logically* rearranged, for example `rotated(m)[2][3][1] == m[1][2][3]`. +(`rotated`/`unrotated` refers to the fact that the logical _indices_ are rotated to the left/right.) + +As an illustration of an algorithm based on index access (as opposed to iterators), +this example code implements Gauss Jordan Elimination without pivoting: + +```cpp +template +auto gj_solve(Matrix&& A, Vector&& y) -> decltype(y[0]/=A[0][0], y) { + std::ptrdiff_t Asize = size(A); + for(std::ptrdiff_t r = 0; r != Asize; ++r) { + auto&& Ar = A[r]; + auto&& Arr = Ar[r]; + for(std::ptrdiff_t c = r + 1; c != Asize; ++c) {Ar[c] /= Arr;} + auto const yr = (y[r] /= Arr); + for(std::ptrdiff_t r2 = r + 1; r2 != Asize; ++r2) { + auto&& Ar2 = A[r2]; + auto const& Ar2r = Ar2[r]; // auto&& Ar = A[r]; + for(std::ptrdiff_t c = r + 1; c != Asize; ++c) {Ar2[c] -= Ar2r*Ar[c];} + y[r2] -= Ar2r*yr; + } + } + for(std::ptrdiff_t r = Asize - 1; r > 0; --r) { + auto const& yr = y[r]; + for(std::ptrdiff_t r2 = r-1; r2 >=0; --r2) {y[r2] -= yr*A[r2][r];} + } + return y; +} +``` + +This function can be applied to a `multi::array` container: + +```cpp +multi::array A = {{-3.0, 2.0, -4.0},{0.0, 1.0, 2.0},{2.0, 4.0, 5.0}}; +multi::array y = {12.0, 5.0, 2.0}; // (M); assert(y.size() == M); iota(y.begin(), y.end(), 3.1); +gj_solve(A, y); +``` + +and also to a combination of `MultiArrayView`-type objects (including standard vectors): + +```cpp +multi::array A({6000, 7000}); std::iota(A.data_elements(), A.data_elements() + A.num_elements(), 0.1); +std::vector y(3000); std::iota(y.begin(), y.end(), 0.2); // could be also a multi::array y({3000}); +gj_solve(A({1000, 4000}, {0, 3000}), y); +``` + +== Slices and strides + +Given an array, a slice in the first dimension can be taken with the `sliced` function. +`sliced` takes two arguments, the first index of the slice and the last index (not included) of the slice. For example, + +```cpp +multi::array A({4, 5}); // A is a value +assert( std::get<0>(A.sizes()) == 4 ); +assert( std::get<1>(A.sizes()) == 5 ); + +auto&& A_sliced = A.sliced(1, 3); // {{d2D[1], d2D[2]}} +assert( std::get<0>(A_sliced.sizes()) == 2 ); +assert( std::get<1>(A_sliced.sizes()) == 5 ); +``` + +The number of rows in the sliced matrix is 2 because we took only two rows, row 1 and row 2 (row 3 is excluded). + +In the same way a strided view of the original array can be taken with the `strided` function. + +```cpp +auto&& d2D_strided = d2D.strided(2); // {{ d2D[0], d2D[1] }}; +assert( d2D_strided.size(0) == 2 and d2D_strided.size(1) == 5 ); +``` + +In this case the number of rows is 2 because, out of the 4 original rows we took one every two. + +Operations can be combined in a single line: + +```cpp +auto&& d2D_slicedstrided = d2D.sliced(1, 3).strided(2); // {{ d2D[1] }}; +assert( std::get<0>(d2D_slicedstrided.sizes()) == 1 and std::get<1>(d2D_slicedstrided.sizes()) == 5 ); +``` + +For convenience, `A.sliced(a, b, c)` is the same as `A.sliced(a, b).strided(c)`. + +By combining `rotated`, `sliced` and `strided` one can take sub arrays at any dimension index. +For example in a two dimensional array one can take a subset of columns by defining. + +```cpp +auto&& subA = A.rotated().sliced(1, 3).strided(2).unrotated(); +``` + +Other notations are available, for example this is equivalent to `A(multi::_ , {1, 3, /*every*/2})` or `~(~A)({1, 3, 2})`. +The `rotated/strided/sliced/rotated` and combinations of them provides the most control over the subview operations. + +Blocks (slices) in multidimensions can be obtained by pure index notation using parentheses `()` (`.operator()`): + +```cpp +auto A = multi::array({6, 7}); // 2D value array + +auto&& A_block1 = A({1, 4}, {2, 4}); // 2D subarray reference (modifiable) +auto const& A_block2 = A({1, 4}, {2, 4}); // 2D subarray reference (non-modifiable) + +auto A_block3 = A({1, 4}, {2, 4}); // works but it can be confusing, use `auto&&` instead +``` + +Sometimes copies are necessary, specifically from a subarray block, this can be done by constructing a new array. +The value array can be deduced by using `auto` and the `decay` member, which in turn is equivalent to the prefix `+` operator. + +```cpp +multi::array block_value_1 = A({1, 4}, {2, 4}) ; +auto block_value_2 = A({1, 4}, {2, 4}).decay(); +auto block_value_3 = + A({1, 4}, {2, 4}) ; +``` + +Any parenthesis argument can be either a range (with or without stride) or an index. +Range argument can be substituted by `multi::all` to obtain the whole range. + +== Conversions + +Conversion between arrays of distinct types is possible if the underlying elements allow it. +The result is as if elements are converted one by one; +array sizes (extensions) are preserved. +Allowed conversions can be implicit or explicit and reflect the behavior of the element types. + +```cpp +// implicit conversions from real to complex is allowed ... +double d = 5.0; std::complex z = d; +// ... therefore it is also allowed from array of reals to arrays of complex +multi::array D({10, 10}); multi::array, 2> Z = D; +// (implicit or explicit) conversions from real to complex are disallowed (compilation error) +// multi::array D = Z; // or D{Z}; +``` + +Another case is illustrated by `std::complex` and `std::complex`; +in one direction, the conversion can be implicit, while in the other, it can only be explicit. +This behavior is reflected in the corresponding arrays: +```cpp +multi::array> C; +multi::array> Z = C; // implicit conversion ok +multi::array> C2{Z}; // explicit conversion is allowed +// multi::array> C3 = Z; // implicit conversion is disallowed (compilation error) +``` + +Implicit conversions are generally considered harmful, but inconsistent conversions are worst; therefore, the library allows them when appropriate. +The main drawback of implicit conversions in this context is that they might incur unexpected (e.g. costly) data conversions when passing arguments to functions. + +```cpp +void fun(multi::array> Z) { ... }; +... +multi::array D({10, 10}); +fun(D); // real elements are converted to complex silently here +``` +In many instances, specially in generic code, it might still be a desirable behavoir. + +To prevent implicit conversions, use element types with no implicit conversions when possible. + +Finally, arrays of unrelated element types are prevented from producing direct conversions, resulting in compilation errors. +Element-wise transformations can be used instead. +For example, to convert an array of integers to an array of text strings: + +```cpp + multi::array const A = {{1, 2}, {3, 4}}; + + auto to_string = [](int e) {return std::to_string(e);}; + multi::array B = A.element_transformed(to_string); + assert( B[1][1] == "4" ); +``` + +== Uninitialized vs. initialized elements + +If available, the library can take advantage of trivial initialization for specific element types that support it. + +Primitive types or some user-defined are defined in such a way that their construction does nothing, not even set values. +When used in the stack, these types can be declared with no initialization (e.g., `double x;`), +in which case their initial value is not well defined or the state is called "partially-formed"; +or they can be declared with initialization (e.g., `double x{};`, same as `double x = 0.0;`). +When these types are used as array elements, (e.g. `multi::array`), +the library does not initialize individual elements unless specified. + +For example, after this construction of the array, the values of the six elements of this array are unspecified (partially-formed). +```cpp +multi::array A2({2, 3}); // A2 elements have unspecified value +``` + +No behavior of the program should depend on these values. +(Fortunately, address sanitizers and memory checkers can detect use of uninitialized values, for example Valgrind diagnoses "Uninitialized Memory Read".) +This design is a slight departure from the STL's design, which https://lemire.me/blog/2012/06/20/do-not-waste-time-with-stl-vectors/[eagerly initializes elements in containers]. + +If trivial construction is unavailable, the library uses the default initialization. +```cpp +multi::array A2({2, 3}); // A2 elements have specified value: empty std::string{} +``` + +For types that afford this partially formed states, elements can be later specified via assignment or assigning algorithms (e.g., copy or transform destination). + +Initialization can be enforced by passing a single value argument after the extensions. +```cpp +multi::array A2({2, 3}, 0); // generically multi::array({2, 3}, T{}); or multi::array({2, 3}, {}) +``` + +This design is particularly advantageous for *numeric* types for which external low-level libraries can fill values. +(or when data sits in GPUs, where the initialization step would require an expensive kernel launch and subsequent synchronization). + +Unfortunately, regarding the numeric types, STL's `std::complex` was standardized as not-trivially constructible. +A workaround built-in in this library is available by forcing a particular flag on the client code in global scope, for example, immediately after including the library: +```cpp +#include + +template<> inline constexpr +bool multi::force_element_trivial_default_construction> = true; // should be defined as early as possible +``` + +With this line, `std::complex` elements inside arrays will be left uninitialized unless a value is specified. +The rule will only apply to this library's containers (`multi::array`, etc), and not to other containers (such as `std::vector`) or individual `std::complex` variables. + +Finally, the library offers a tag to explicitly state the intention of having uninitialized elements: `multi::arrray A2({2, 3}, multi::uninitialized_elements)`. +This syntax will compile only if elements are trivially-default constructible; +therefore and as an example `multi::arrray A2({2, 3}, multi::uninitialized_elements)` will not compile because `std::string` does not have a trivial constructor. + +== Const-correctness + +Const-correctness refers to the property of a program to disallow mutation of certain objects when it is undesired or logically incorrect. +Honoring the const-ness declaration is fundamental not only to avoid bugs and typos but also for thread safety and generic programming. +The library goes to great lengths to ensure const-correctness for the whole or parts of any object. + +Arrays are resizable, and their elements can be mutated unless declared constant (using the keyword `const`). + +A reference array or subarray is never resizable, but its elements are mutable if not declared `const`. +The design ensures that the const-ness of references and values propagates to subarrays (views) and, ultimately, their elements. + +```cpp +template +void print(Array1D const& coll) { +// *coll.begin() = 99; // doesn't compile, "assignment of read-only location" + + for(auto const& e : coll) {std::cout<< e <<", ";} + std::cout << std::endl; +} + +int main() { + multi::array const coll1 = {0, 8, 15, 47, 11, 42}; + + print( coll1 ); // prints "0, 8, 15, 47, 11, 42" + print( coll1({0, 3}) ); // prints "0, 8, 15" +} +``` + +As a general rule for passing generic arrays as arguments, pass them as `Array const&` (in the context of `template`); +unless mutation is expected, in which case take arguments as `Array&&` (note the double ampersand, i.e., universal/forwarding reference). +Analogously, subarrays can be locally *named* into "constant language references" using `auto const&` and, if mutation is desired, `auto&&` should be used. +Regular references `Array&` or `auto&` in general do not have the expected behavior for views. + +```cpp +template +void fill_99(Array1D&& coll) { + for(auto& e : coll) { e = 99; } +} + +int main() { + multi::array coll1 = {0, 8, 15, 47, 11, 42}; + + fill_99( coll1 ); + fill_99( coll1({0, 3}) ); + + auto&& coll1_take3 = coll1({0, 3}); + fill_99( coll1_take3 ); + + auto const& coll2 = coll1; +// fill_99( coll2 ); // doesn't compile because coll2 is const +// fill_99( coll2({0, 3}) ); // similar to coll2 | take(3) doesn't compile + + auto const& coll1_take3_const = coll1({0, 3}); +// fill_99( coll1_take3_const ); // doesn't compile because coll1_take3_const is const +} +``` + +== Compile-time evaluation (constexpr) + +With certain limitations imposed by the language, arrays can be declared in contexts with compile-time evaluation. + +```cpp +constexpr auto trace() { + multi::array arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + arr[2][2] = 10; + return std::accumulate(arr.diagonal().begin(), arr.diagonal().end()); +} + +static_assert( trace() == 4 + 2 + 10 ); +``` +link:https://godbolt.org/z/Porre3z8s[(live)] + +== Restrictions (lazy expression evaluation) + +So far, we have examined the use of arrays that contain elements or references to elements stored in memory; these are referred to as container arrays or concrete arrays. +In this section, we will describe another type of multidimensional array in which the elements are not stored but calculated on the fly instead. +The arrays are called "restrictions"; they are also commonly referred to as "lazy arrays". + +Multi's restriction arrays are functions that are wrapped in the interface of a multidimensional array. +These functions are therefore restricted (in the mathematical sense) to a discrete range of values in a multidimensional grid (a Cartesian product of integer intervals). +The dimensionality of a restriction is the number of indices or coordinates that this function accepts as inputs. + +Restrictions are constructed from a function (generally a lambda) and an `extensions` object, combined using the wedge operator (`operator^`, which can be interpreted as an arrow pointing up). +Unlike concrete arrays, restrictions do not necessarily allocate space; instead, the elements are generated on demand. + +For example, we can restrict this simple function `10*i + j` where `i` and `j` belong to the domain `[0, 2) x [0, 3)` (i.e., `i = 0...1` and `j = 0...2`). + +```cpp +auto R = [](auto i, auto j) { return 10*i + j; } ^ multi::extensions_t<2>(2, 3); +... +``` +Note that `R` does not request memory to store the values; it generates them only when needed. For example, `R[1][2]` yields `10*1 + 2`, or `12`. +For this reason, they can be regarded as "lazy" arrays, a multidimensional generalization of ranges such as `std::ranges::iota`, `std::ranges::iota | std::ranges::transform(...)` or `thrust::counting_iterator`.) + +The main property of a restriction array is that it can be generically used in place of a concrete array because they have the same generic interface as `multi::array`, such as `.elements()`, `.transposed()`, `.rotated()` and (partial) indexing (`operator[]`). +They also provide a complete iterator inteface, `.begin()`, `.end()` and they can be used as standard ranges. +This means that generic algorithms, including some outside of Multi, can take advantage of these arrays. + +As a simple example, the sum of all the elements of `R` is `std::reduce(R.elements().begin(), R.elements().end())`; the same expression as if it were a concrete array). + +The main difference with concrete arrays is that their elements cannot be mutated, and that there are no internal pointers or layouts to extract (e.g., through functions like `.data_elements()`, `.base()`, `.layout()` or `.stride()`). + +The simplest use of restrictions is for generating concrete arrays from coordinates. +The following is a commonly used nested loop for initialization: +```cpp +multi::array A({3, 2}); + +auto [is, js] = A.extensions(); + +for(auto i : is) { + for(auto j : js) { + A[i][j] = 10*i + j; + } +} +``` +...which can be replaced by a simpler construction without intermediate steps: +```cpp +multi::array const A([](auto i, auto j) { return 10*i + j; } ^ multi::extensions_t<2>(3, 2)); +``` + +Note that since there is no immediate need to assign elements and `A` can be conveniently declared `const` if no further mutation is necessary). +The element initialization here is delegated to the library; the initialization could occur using optimal hardware- or library-specific operations (e.g., on a GPU device). + +Restrictions can be used in many other contexts beyond initialization and assignment; +for example in combination with `std::ranges`, or mixed with concrete arrays. +In some cases, functions can be generically written to work both on `multi::array`s (or `multi::subarray`s) and on restrictions, when a functional programming style is desirable. + +For example, here we define a transformation that subtracts the mean value of each row, row by row: + +```cpp +auto shift = std::views::transform( + [](Row&& row) { + auto mean = std::reduce(row.begin(), row.end()) / row.size(); + return std::forward(row) | std::views::transform([=](auto ele) { return ele - mean; }); + } +); +``` + +This operation can be applied to the lazy version or to the concrete version of the array: + +```cpp +auto lazy = [](auto i, auto j) -> double { return 10 * i + j; } ^ multi::extensions_t<2>(2, 3); +multi::array concrete = {{0.0, 1.0, 2.0}, {10.0, 11.0, 12.0}}; +``` + +The primary advantage of using lazy arrays and defining transformation functions is that operations can occur without requiring temporary space (allocators), which can be advantageous. +The allocation would only happen at the point at which the result is materialized as a concrete array. + +```cpp +multi::array S1(shift(lazy)); // allocation might ocurr here +multi::array S2(shift(concrete)); // allocation might ocurr here +``` +link:https://godbolt.org/z/qK9cYzrzv[(live)] + +Another possible application of restrictions is the ability to define lazy operations, also known as lazy expressions or expression templates. +These are complex C++ expressions composed of objects (typically in through operator overload) that are evaluated only after the whole expression is built. +In the array programming space, eliminating the need of temperaries is essential. + +Since arrays are expensive to copy or generate, this feature is highly regarded for array libraries. +Here is a basic implementation example using restrictions, that can be customized for specific domains of application. + +For example, given two concrete arrays `A`, `B`, one can evaluate the result of a complex algebraic expression and store the result in a new array, but without building intermediate arrays: + +```cpp + auto const A = multi::array{{0, 1, 2}, {3, 4, 5}}; + auto const B = multi::array{{0, 10, 20}, {30, 40, 50}}; + + using lazy::operator*; + using lazy::elementwise::operator+; + using lazy::elementwise::operator*; + + multi::array const C = A + (A * B) + (2.0 * B); + + assert(C[1][1] == A[1][1] + (A[1][1] * B[1][1]) + (2.0 * B[1][1])); +``` + +The lazy operations utilized here are be implemented in a fairly intuitive way using a series of array restrictions (C++23 for simplicity): + +```cpp +namespace lazy { + + template + auto operator*(typename A::element scalar, A const& a) { + return [scalar, &a](auto... is) { return scalar * a[is...]; } ^ a.extensions(); + } + + namespace elementwise { + template + auto operator*(A const& a, B const& b) requires(A::dimensionality == B::dimensionality) { + assert( a.extensions() == b.extensions() ); + return [&a, &b](auto... is) { return a[is...] * b[is...]; } ^ a.extensions(); + } + template + auto operator+(A const& a, B const& b) requires(A::dimensionality == B::dimensionality) { + assert( a.extensions() == b.extensions() ); + return [&a, &b](auto... is) { return a[is...] + b[is...]; } ^ a.extensions(); + } + } +} +``` +link:https://godbolt.org/z/413GTx47v[(live)] + +Note that none of the operations (addition or multiplication) generate temporary concrete (allocated) arrays. + +There are many challenges in implementing general lazy expressions: +1) tracking the ownership of the subexpressions (here, everything is taken by reference for simplicity, instead of exploiting copy or move semantics, or reference counting), +2) dealing with ambiguities generated by operator overloading (hence the heavy use of templates and C++23 constraints). +There is no single way to implement lazy operations; it depends strongly on the intended use and minute details in the implementation can have divergent semantics. + +In summary, restrictions can be very powerful since any function and behavior can be injected. +However, there are some limitations to take into account to avoid surprising behavior: +The functions that make up restrictions should behave as much as possible as pure (mathematical) functions and behave deterministically in their context of use. +In the same way, for lambdas that capture references, the validity of the restriction objects would be contigent to the lifetime of these references. +Analogously if the lambda captures by copy, the implications of copying these must be evaluated too. +Just like concrete arrays, the order of evaluation of the indices can impact performance or rely on compiler optimizations. + +// == Broadcast (infinite views) + +// Broadcasting is a technique by which arrays are reinterpreted as having a higher dimension by repeating elements. +// The technique allows the reuse of operations designed for high dimensionality and effectively apply them to arrays of lower dimensionality. +// The result is generally an economy in the number of distinct operations that need to be provided in exchange for understanding how and where to exploit the broadcast operations. + +// Broadcasting is popular in array-based languages, such as Julia and NumPy, and the broadcast operation is generally applied automatically to match the dimension expected by the operation and other operation inputs. +// The library provides a basic form of broadcasting with certain limitations. + +// Here is an example of an algorithm designed for two 2D arrays to obtain the row-by-row inner product. + +// ```cpp +// auto row_by_row_dot = [](auto const& A2D, auto const& B2D, auto& results) { +// std::transform(A2D.begin(), A2D.end(), B2D.begin(), results.begin(), +// [](auto const& Arow, auto const& Brow) {return std::inner_product(Arow.begin(), Arow.end(), Brow.begin(), 0);} +// ); +// }; + +// auto A = multi::array{{ 0, 1}, { 2, 3}, { 4, 5}}; +// auto B = multi::array{{10, 11}, {12, 13}, {14, 15}}; + +// auto dots = multi::array({A.size()}); + +// row_by_row_dot(A, B, dots); +// ``` + +// If, for some reason, we want to obtain the inner product against a _single_ right-hand vector instead of several (a single 1D array of two elements), we would need to (re)write the function (or copy the repeated vector into the 2D `B` array, which is not ideal.) +// Broadcasting can help reuse the same function without changes. + +// ```cpp +// multi::array b = {10, 11}; + +// row_by_row_dot(A, b.broadcasted(), dots); +// ``` + +// The alternative, not using broadcast, is to write a very similar function, + +// ```cpp +// auto row_fixed_dot = [](auto const& A2D, auto const& b1D, auto& results) { +// std::transform(A2D.begin(), A2D.end(), results.begin(), +// [&b1D](auto const& Arow) {return std::inner_product(Arow.begin(), Arow.end(), b1D.begin(), 0);} +// ); +// }; + +// row_fixed_dot(A, b, dots3); +// ``` +// (https://godbolt.org/z/9ndvfKqhc) + +// Broadcasted arrays do not behave like normal array views in several aspects: +// First, broadcasted arrays are infinite in the broadcasted dimension; iteration will never reach the end position, and calling `.size()` is undefined behavior. +// Explicit loops or algorithms that depend on reaching `.end()` from `.begin()` will effectively be non-terminating. +// Second, these array views are strictly read-only and alias their element addresses, e.g. `&b.broadcasted()[1][0] == &b.broadcasted()[2][0]` (since internal layouts' strides can be zero). + +// + +// Unlike in popular languages, broadcasting is not automatic in the library and is applied to the leading dimension only, one dimension at a time. +// Broadcasting in non-leading dimensions can be achieved by transpositions and index rotation. + +// Abuse of broadcast can make it harder to reason about operations; +// its primary use is to reuse existing efficient implementations of algorithms when implementations for a specific lower dimensions are not available. +// These algorithms need to be compatible with broadcasted views (e.g., no explicit use of `.size()` or infinite loops stemming from problematic use of `.begin()/end()`.) + +// (In STL, algorithms ending with `_n` should be friendly to broadcast arrays, unfortunately `std::copy_n` is sometimes internally implemented in terms of `std::copy` causing a problematic iterator arithmetic on infinite arrays. +// NB: `thrust::copy_n` can be used instead.) + +// As a final example, consider a function that computes the elements-by-element product of two 2D arrays, + +// ```cpp +// auto hadamard = [](auto const& A, auto const& B, auto&& C) { +// auto const [is, js] = C.extensions(); +// for(auto i : is) for(auto j : js) C[i][j] = A[i][j]*B[i][j]; +// }; +// ``` + +// As it is, this function can be reused to calculate the outer product of two 1D arrays: + +// ```cpp +// auto outer = [&](auto const& a, auto const& b, T&& C) { +// return hadamard(~(a.broadcasted()), b.broadcasted(), std::forward(C)); +// }; +// ``` +// (https://godbolt.org/z/5o95qGdKz) + +// Note that the function `hadamard`, acting on 2D arrays, doesn't use the undefined (infinite) sizes (second dimension of `A` and first dimension of `B`). + +// NB: A zero-dimensional broadcasts into a one-dimensional. +// Zero-dimensional arrays can contain, at most, one element; and after a broadcast, it can represent an infinite sequence of such element. + +// ```cpp +// multi::array const single{7}; +// multi::array const sevens = {7, 7, 7}; + +// single.broadcasted().front() == 7; +// assert( std::equal(sevens.begin(), sevens.end(), single.broadcasted().begin()) ); +// ``` +// (https://godbolt.org/z/nnxjsrvM1) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3184e2925..9a45f178b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,12 +1,3 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- -#[=[Multi Test suite can be run like this: - mkdir -p build - cd build - cmake .. [-DENABLE_CUDA=1] - make -j - ctest -j --output-on-error [-T memcheck] - exit -#]=] cmake_minimum_required(VERSION 3.18) project( @@ -21,7 +12,8 @@ include_directories(../include) #... #target_link_library(my_target PUBLIC multi) -find_package(Boost REQUIRED COMPONENTS serialization unit_test_framework timer) +find_package(Boost REQUIRED COMPONENTS iostreams serialization unit_test_framework timer) +add_subdirectory("../" multi-bin) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -30,9 +22,24 @@ enable_testing() include(CTest) add_executable(serialization.cpp.x serialization.cpp) -target_link_libraries(serialization.cpp.x PRIVATE ${Boost_LIBRARIES} stdc++fs) +target_link_libraries(serialization.cpp.x PUBLIC multi) +target_link_libraries(serialization.cpp.x PRIVATE ${Boost_LIBRARIES}) add_test(NAME serialization.cpp.x COMMAND serialization.cpp.x) add_executable(gj_solve.cpp.x gj_solve.cpp) -target_link_libraries(gj_solve.cpp.x PRIVATE ${Boost_LIBRARIES} stdc++fs) +target_link_libraries(gj_solve.cpp.x PUBLIC multi) +target_link_libraries(gj_solve.cpp.x PRIVATE ${Boost_LIBRARIES}) add_test(NAME gj_solve.cpp.x COMMAND gj_solve.cpp.x) + +add_executable(redux.cpp.x redux.cpp) +target_link_libraries(redux.cpp.x PUBLIC multi) +target_link_libraries(redux.cpp.x PRIVATE ${Boost_LIBRARIES}) + +find_package(TBB) + +if(TBB_FOUND) + target_link_libraries (redux.cpp.x PRIVATE TBB::tbb) + target_compile_definitions(redux.cpp.x PUBLIC TBB_FOUND=1) +endif() + +add_test(NAME redux.cpp.x COMMAND redux.cpp.x) diff --git a/examples/bip_mapped_file.cpp b/examples/bip_mapped_file.cpp index 74b1ca1c7..163f5b255 100644 --- a/examples/bip_mapped_file.cpp +++ b/examples/bip_mapped_file.cpp @@ -1,21 +1,14 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXX $CXXFLAGS -std=c++17 $0 -o $0x -lstdc++fs -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2019-2022 +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi interacting with Boost Interprocess" -#define BOOST_TEST_DYN_LINK -#include +#include +#include +#include -#include - -#include -#include - -#include -#include - -#include +#include +#include +#include namespace bip = boost::interprocess; @@ -25,139 +18,149 @@ using std::filesystem::path; using std::filesystem::remove; template using mallocator = bip::allocator; -static auto get_allocator(manager& m) {return m.get_segment_manager();} -static void mremove(path f) {remove(f);} +static auto get_allocator(manager& m) { return m.get_segment_manager(); } +static void mremove(path f) { remove(f); } -std::set candidates(manager& m) { +auto objects_directory(manager& m) { std::set ret; - for(auto it = get_allocator(m)->named_begin(); it != get_allocator(m)->named_end(); ++it) { - ret.insert(std::string(it->name(), it->name_length())); - } + std::transform( + get_allocator(m)->named_begin(), get_allocator(m)->named_end(), + std::inserter(ret, ret.end()), + [](auto const& e) { return e.name(); } + ); return ret; } -#include "../include/multi/array.hpp" +#include -#include // iota +#include // iota namespace multi = boost::multi; template using marray = multi::array>; -BOOST_AUTO_TEST_CASE(multi_test_bip) { +template using alloc = bip::adaptive_pool< + T, bip::managed_shared_memory::segment_manager>; -path file = "bip_mapped_file.bin"; -mremove(file); -{ - manager m{bip::create_only, file.c_str(), 1 << 25}; - auto&& arr1d = - *m.construct>("arr1d")(multi::extensions_t<1>( 10 ), 99 , get_allocator(m)); - auto&& arr2d = - *m.construct>("arr2d")(multi::extensions_t<2>({10, 10 }), 0.0, get_allocator(m)); - auto&& arr3d = - *m.construct>("arr3d")(multi::extensions_t<3>({10, 10, 10}), 0u , get_allocator(m)); +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ - arr1d[3] = 33; - arr2d[4][5] = 45.001; +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) - std::iota(arr3d[6][7].begin(), arr3d[6][7].end(), 100); + BOOST_AUTO_TEST_CASE(multi_test_bip const) { -// m.flush(); // this produces uninitialized access in icpc 19.1 and might not be necessary -} -{ - manager m{bip::open_only, file.c_str()}; + path const file = "bip_mapped_file.bin"; + { + mremove(file); + manager m{bip::create_only, file.c_str(), 1 << 25}; // objects with same name produce boost::interprocess_exception::library_error + auto&& arr1d = *m.construct>("arr1d")(multi::extensions_t{10}, 99, get_allocator(m)); + auto&& arr2d = *m.construct>("arr2d")(multi::extensions_t{10, 10}, 0.0, get_allocator(m)); + auto&& arr3d = *m.construct>("arr3d")(multi::extensions_t{10, 10, 10}, 0u, get_allocator(m)); - auto s = candidates(m); - BOOST_REQUIRE( s.find("arr1d") != s.end() ); - BOOST_REQUIRE( s.find("arr2d") != s.end() ); - BOOST_REQUIRE( s.find("arr3d") != s.end() ); + arr1d[3] = 33; + arr2d[4][5] = 45.001; - auto&& arr1d = *m.find>("arr1d").first; - BOOST_REQUIRE(std::addressof(arr1d)); + std::iota(arr3d[6][7].begin(), arr3d[6][7].end(), 100); - auto&& arr2d = *m.find>("arr2d").first; - BOOST_REQUIRE(std::addressof(arr2d)); + auto const& arr3d_copy = *m.construct>("arr3d_copy")(arr3d, get_allocator(m)); + BOOST_TEST( arr3d == arr3d_copy ); + } + { + manager m{bip::open_only, file.c_str()}; - auto&& arr3d = *m.find>("arr3d").first; - BOOST_REQUIRE(std::addressof(arr3d)); + auto const s = objects_directory(m); + BOOST_TEST( s.find("arr1d") != s.end() ); + BOOST_TEST( s.find("arr2d") != s.end() ); + BOOST_TEST( s.find("arr3d") != s.end() ); - BOOST_REQUIRE( arr1d[5] == 99 ); - BOOST_REQUIRE( arr1d[3] == 33 ); + auto&& arr1d = *m.find>("arr1d").first; + BOOST_TEST(std::addressof(arr1d)); - BOOST_REQUIRE( arr2d[7][8] == 0. ); - BOOST_REQUIRE( arr2d[4][5] == 45.001 ); + auto&& arr2d = *m.find>("arr2d").first; + BOOST_TEST(std::addressof(arr2d)); - BOOST_REQUIRE( arr3d[6][7][3] == 103 ); + auto&& arr3d = *m.find>("arr3d").first; + BOOST_TEST(std::addressof(arr3d)); - m.destroy>("arr1d"); - m.destroy>("arr2d"); - m.destroy>("arr3d"); -} -mremove(file); + BOOST_TEST( arr1d[5] == 99 ); + BOOST_TEST( arr1d[3] == 33 ); -} + BOOST_TEST( arr2d[7][8] == 0.0 ); + BOOST_TEST( arr2d[4][5] == 45.001 ); -template using alloc = bip::adaptive_pool< - T, bip::managed_shared_memory::segment_manager ->; + BOOST_TEST( arr3d[6][7][3] == 103 ); -BOOST_AUTO_TEST_CASE(scoped_allocator_vector_of_arrays) { + auto&& arr3d_copy = *m.find>("arr3d_copy").first; + BOOST_TEST(std::addressof(arr3d_copy)); - using bipc_row = multi::array>; - using bipc_matrix = std::vector>>; + BOOST_TEST( arr3d == arr3d_copy ); - bip::shared_memory_object::remove("Demo"); - { - bip::managed_shared_memory s{bip::create_only, "Demo", 65536}; + m.destroy>("arr1d"); + m.destroy>("arr2d"); + m.destroy>("arr3d"); + mremove(file); + } + } - bipc_matrix v(s.get_segment_manager()); + BOOST_AUTO_TEST_CASE(scoped_allocator_vector_of_arrays const) { - v.emplace_back( multi::extensions_t<1>( 3 ), 99. ); - std::iota( v[0].begin(), v[0].end() , 42 ); + using bipc_row = multi::array>; + using bipc_matrix = std::vector>>; - assert( v[0][1] == 43 ); bip::shared_memory_object::remove("Demo"); - } -} + { + bip::managed_shared_memory s{bip::create_only, "Demo", 65536}; -BOOST_AUTO_TEST_CASE(scoped_allocator_arrays_of_vector) { + bipc_matrix v(s.get_segment_manager()); - using bipc_row = std::vector>; - using bipc_matrix = multi::array>>; + v.emplace_back(multi::extensions_t<1>(3), 99.); + std::iota(v[0].begin(), v[0].end(), 42); - bip::shared_memory_object::remove("Demo"); - { - bip::managed_shared_memory s{bip::create_only, "Demo", 65536}; + assert(v[0][1] == 43); + bip::shared_memory_object::remove("Demo"); + } + } - bipc_matrix v(bipc_matrix::extensions_type(10), bipc_row{s.get_segment_manager()}, s.get_segment_manager()); - std::vector row(3, 99); - v[0].assign(row.begin(), row.end()); + BOOST_AUTO_TEST_CASE(scoped_allocator_arrays_of_vector const) { + + using bipc_row = std::vector>; + using bipc_matrix = multi::array>>; - BOOST_REQUIRE( v[0][1] == 99 ); bip::shared_memory_object::remove("Demo"); + { + bip::managed_shared_memory s{bip::create_only, "Demo", 65536}; + + bipc_matrix v(bipc_matrix::extensions_type(10), bipc_row{s.get_segment_manager()}, s.get_segment_manager()); + std::vector row(3, 99); + v[0].assign(row.begin(), row.end()); + + BOOST_TEST( v[0][1] == 99 ); + bip::shared_memory_object::remove("Demo"); + } } -} -BOOST_AUTO_TEST_CASE(scoped_allocator_arrays_of_array) { + BOOST_AUTO_TEST_CASE(scoped_allocator_arrays_of_array const) { - using bipc_row = multi::array>; - using bipc_matrix = multi::array>>; + using bipc_row = multi::array>; + using bipc_matrix = multi::array>>; - bip::shared_memory_object::remove("Demo"); - { - bip::managed_shared_memory s{bip::create_only, "Demo", 165536}; + bip::shared_memory_object::remove("Demo"); + { + bip::managed_shared_memory s{bip::create_only, "Demo", 165536}; - bipc_matrix v(bipc_matrix::extensions_type(10), bipc_row{bipc_matrix::extensions_type(3), 5, s.get_segment_manager()}, s.get_segment_manager()); - multi::array row = {97, 98, 99}; - std::copy(row.begin(), row.end(), v[0].begin()); + bipc_matrix v(bipc_matrix::extensions_type(10), bipc_row{bipc_matrix::extensions_type(3), 5, s.get_segment_manager()}, s.get_segment_manager()); + multi::array row = {97, 98, 99}; + std::copy(row.begin(), row.end(), v[0].begin()); - BOOST_REQUIRE( v[0][1] == 98 ); - BOOST_REQUIRE( v[1][1] == 5 ); + BOOST_TEST( v[0][1] == 98 ); + BOOST_TEST( v[1][1] == 5 ); - v.reextent( bipc_matrix::extensions_type(12) , bipc_row{bipc_matrix::extensions_type(3), 5, s.get_segment_manager()} ); + v.reextent(bipc_matrix::extensions_type(12), bipc_row{bipc_matrix::extensions_type(3), 5, s.get_segment_manager()}); - bip::shared_memory_object::remove("Demo"); + bip::shared_memory_object::remove("Demo"); + } } -} + return boost::report_errors(); +} \ No newline at end of file diff --git a/examples/boost_iterator_transform.cpp b/examples/boost_iterator_transform.cpp index 79a78d40a..633796ae4 100644 --- a/examples/boost_iterator_transform.cpp +++ b/examples/boost_iterator_transform.cpp @@ -1,137 +1,124 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- +#ifdef COMPILATION // clang-format off ${CXX:-c++} -std=c++17 $CXXFLAGS -I../include $0 -o $0.$X&&$0.$X&&rm $0.$X;exit -#endif -// Copyright 2018-2022 Alfredo A. Correa +#endif // clang-format on +// Copyright 2018-2023 Alfredo A. Correa #include "./multi/array.hpp" -#include -#include +// #include +// #include -#include #include +#include #include -#include +#include -namespace multi = boost::multi; + namespace multi = boost::multi; -constexpr auto conj = [](auto const& c) -> auto const {return std::conj(c);}; +constexpr auto conj = [](auto const& c) -> auto const { return std::conj(c); }; template struct conjr : boost::transform_iterator { - template conjr(As const&... as) : boost::transform_iterator{as...} {} + template conjr(As const&... as) : boost::transform_iterator{as...} {} // TODO(correaa) not working here }; template auto hermitized(Array2D const& arr) { return arr - .transposed() // lazily tranposes the array + .transposed() // lazily tranposes the array .template static_array_cast>(conj) // lazy conjugate elements - ; -} - -int main(){ -{ - using namespace std::complex_literals; - multi::array A = { - { 1. + 2.i, 3. + 4.i}, - { 8. + 9.i, 10. + 11.i} - }; - - auto const& Ah = hermitized(A); - - assert( Ah[1][0] == std::conj(A[0][1]) ); -} - -{ - auto r = multi::make_range(5, 10); - auto f = [](auto x) {return x + 1;}; - std::vector v( - boost::make_transform_iterator(r.begin(), f), - boost::make_transform_iterator(r.end() , f) - ); - assert( v[1] == 7. ); -} -{ - auto r = multi::make_range(5, 10); - auto f = [](auto x) {return x + 1;}; - multi::array v( - boost::make_transform_iterator(r.begin(), f), - boost::make_transform_iterator(r.end() , f) - ); - assert( v[1] == 7. ); -} -{ - multi::array v(10); - auto r = extension(v); - auto f = [](auto x) {return x * 2;}; - v.assign( - boost::make_transform_iterator(r.begin(), f), - boost::make_transform_iterator(r.end() , f) - ); - assert( v[1] == 2. ); -} -{ - multi::array v(10); - multi::array r = {1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}; - auto f = [](auto x) {return x * 2;}; - v.assign( - boost::make_transform_iterator(r.base() , f), - boost::make_transform_iterator(r.base() + r.size(), f) - ); - assert( v[1] == 4. ); -} -{ - auto r = multi::make_extension_t(10l); - auto f = [](auto x){ - std::size_t seed = 1234; - // boost::hash_combine(seed, ); - seed ^= boost::hash{}(x) + 0x9e3779b9 + (seed<<6) + (seed>>2); - return static_cast(seed)/static_cast(std::numeric_limits::max()); - }; - multi::array v( - boost::make_transform_iterator(r.begin(), f), - boost::make_transform_iterator(r.end() , f) - ); - - std::size_t seed = 12349l; - // boost::hash_combine(seed, ); -// seed ^= boost::hash{}(13) + 0x9e3779b9 + (seed<<6) + (seed>>2); - boost::hash_combine(seed, 13); - - assert( v.size() == r.size() ); - assert( v[1] >= 0. ); - assert( v[1] < 1. ); - assert( std::all_of(begin(v), end(v), [](auto x){ - return x >= 0. and x < 1.; - }) ); -} - -//struct conj_t : thrust::unary_function, std::complex> { -// std::complex operator()(std::complex const& e) const {return std::conj(e);} -//} conj; - -{ - using namespace std::complex_literals; - multi::array, 1> A = { 1. + 2.i, 3. + 4.i, 5. + 7.i}; - - auto const conj = [](auto e) {return std::conj(e);}; - - std::vector> v(thrust::make_transform_iterator(A.elements().begin(), conj), thrust::make_transform_iterator(A.elements().end(), conj)); - std::cout << v[1] << std::endl; - assert( v[1] == 3. - 4.i ); - -// using ittc = std::iterator_traits>>::iterator_category; - -// using rt = boost::result_of &(std::complex &)>::type; -// using conjugater = decltype(boost::make_transform_iterator(A.data_elements(), conj)); -// conjugater ll(A.data_elements(), conj); -// auto conjA = A.template static_array_cast, thrust::transform_iterator*> >(); -// A.static_array_cast, transformer, decltype(conj)> >(conj); - - -// return {this->layout(), P2{this->base(), std::forward(args)...}}; -// boost::multi::basic_array, 1, std::decay_t> bb{A.layout(), thrust::make_transform_iterator(A.base(), conj)}; + ; } +int main() { + { + using namespace std::complex_literals; + multi::array A = { + {1.0 + 2.0i, 3.0 + 4.0i}, + {8.0 + 9.0i, 10.0 + 11.0i}, + }; + + auto const& Ah = hermitized(A); + + assert(Ah[1][0] == std::conj(A[0][1])); + } + + { + auto r = multi::make_range(5, 10); + auto f = [](auto x) { return x + 1; }; + + std::vector v( + boost::make_transform_iterator(r.begin(), f), + boost::make_transform_iterator(r.end(), f) + ); + assert(v[1] == 7.); + } + { + auto r = multi::make_range(5, 10); + auto f = [](auto x) { return x + 1; }; + + multi::array v( + boost::make_transform_iterator(r.begin(), f), + boost::make_transform_iterator(r.end(), f) + ); + assert(v[1] == 7.0); + } + { + multi::array v(10); + + auto r = extension(v); + auto f = [](auto x) { return x * 2; }; + + v.assign( + boost::make_transform_iterator(r.begin(), f), + boost::make_transform_iterator(r.end(), f) + ); + assert(v[1] == 2.0); + } + { + multi::array v(10); + multi::array r = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}; + + auto f = [](auto x) { return x * 2; }; + + v.assign( + boost::make_transform_iterator(r.base(), f), + boost::make_transform_iterator(r.base() + r.size(), f) + ); + assert(v[1] == 4.0); + } + { + auto r = multi::make_extension_t(10L); + auto f = [](auto x) { + std::size_t seed = 1234; + // boost::hash_combine(seed, ); + seed ^= boost::hash{}(x) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return static_cast(seed) / static_cast(std::numeric_limits::max()); + }; + + multi::array v( + boost::make_transform_iterator(r.begin(), f), + boost::make_transform_iterator(r.end(), f) + ); + + std::size_t seed = 12349L; + boost::hash_combine(seed, 13); + + assert(v.size() == r.size()); + assert(v[1] >= 0.0); + assert(v[1] < 1.0); + assert(std::all_of(begin(v), end(v), [](auto x) { + return x >= 0.0 and x < 1.0; + })); + } + { + using namespace std::complex_literals; + multi::array, 1> A = {1.0 + 2.0i, 3.0 + 4.0i, 5.0 + 7.0i}; + + auto const conj = [](auto e) { return std::conj(e); }; + + std::vector> v(thrust::make_transform_iterator(A.elements().begin(), conj), thrust::make_transform_iterator(A.elements().end(), conj)); + std::cout << v[1] << std::endl; + assert(v[1] == 3.0 - 4.0i); + } } diff --git a/examples/cai_1d_heat.cpp b/examples/cai_1d_heat.cpp new file mode 100644 index 000000000..dbed041cd --- /dev/null +++ b/examples/cai_1d_heat.cpp @@ -0,0 +1,75 @@ +#if COMPILE_RUN_INSTRUCTIONS +${CXX:-c++} -std=c++2b $0 -I../include && ./a.out; exit +#endif +#include + +#include // for std::exp +#include +#include + +namespace multi = boost::multi; +namespace stv = std::views; + +void plot(auto const& x, auto const& f, std::string const& title = "") { + assert(x.size() == f.size()); + std::cout << "set title '" << title << "'\n" + << "plot '-' with linespoints\n"; + for(auto i : x.extension()) { + std::cout << x[i] << " " << f[i] << "\n"; + } + std::cout << 'e' << std::endl + << "pause 0.1\n"; +} + +template +auto append(Range const& range, T const& value) { + return stv::iota(typename Range::size_type{}, range.size() + 1) | stv::transform([&](auto i) -> decltype(auto) {return (i +auto prepend(Range const& range, T const& value) { + return stv::iota(typename Range::size_type{}, range.size() + 1) | stv::transform([&](auto i) -> decltype(auto) {return (i==0)?value:range[i-1];}); +} + +auto main() -> int { + + using multi::operator+; + + // dx = 0.2; + // x = [0:1:20]*dx; + // f = x.*exp(-x.^2); + // plot(x, f, 'ro'); + + auto dx = 0.2; + auto x = +(stv::iota(0, 20) | stv::transform([dx](auto i) { return i * dx; })); + auto f = +(x | stv::transform([](auto e) { return e * std::exp(-e * e); })); + plot(x, f); + + // f_my_left = [NaN, f(1:end-1)]; + // f_my_right = [f(2:end), NaN]; + // d2f = (f_my_right - 2*f + f_my_left)/(dx^2); + auto f_my_left = +prepend(f.taked(f.size() - 1), NAN); + auto f_my_right = +append(f.dropped(1), NAN); + auto d2f = +stv::zip_transform([dx2 = dx * dx](auto r, auto m, auto l) { return (r - 2 * m + l) / dx2; }, f_my_right, f, f_my_left); + + // dt = 0.01; D = 1; + // for k=1:100, + // f_my_left = [NaN, f(1:end-1)]; + // f_my_right = [f(2:end), NaN]; + // d2f = (f_my_right - 2*f + f_my_left)/(dx^2); + // f(2:end-1) = f(2:end-1) + D*dt*d2f(2:end-1); + // plot(x, f, 'ro-'); ylim([0 0.45]); drawnow + // pause(0.1) + // end + + auto dt = 0.01; + auto D = 1.0; + for(auto k = 0; k != 100; ++k) { + f_my_left({1, f.size()}) = f({0, f.size() - 1}); + f_my_right({0, f.size() - 1}) = f({1, f.size()}); + + d2f = stv::zip_transform([dx2 = dx * dx](auto r, auto m, auto l) { return (r - 2 * m + l) / dx2; }, f_my_right, f, f_my_left); + f({1, f.size() - 1}) = stv::zip_transform([&](auto eff, auto d2) { return eff + D * dt * d2; }, f({1, f.size() - 1}), d2f({1, f.size() - 1})); + plot(x, f, "k=" + std::to_string(k)); + } +} diff --git a/examples/llnl_metall.cpp b/examples/llnl_metall.cpp index 4d55e9c9c..bbd480a0f 100644 --- a/examples/llnl_metall.cpp +++ b/examples/llnl_metall.cpp @@ -1,77 +1,85 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXX -std=c++17 -I~/https/github.com/LLNL/metall.git/include/ $0 -o $0x -lstdc++fs&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2019-2020 +#ifdef COMPILATION // clang-format off +${CXX:-c++} -std=c++17 $CXXFLAGS -I../include -I$HOME/metall/include $0 -o$0.$X&&$0.$X&&rm $0.$X;exit +#endif // clang-format on +// Copyright 2019-2023 Alfredo A. Correa -#include -#include // iota -#include +#include +#include +#include // for std::iota -#include +#include -#include "../../multi/array.hpp" +#include -template using mallocator = metall::manager::allocator_type; +template +using mallocator = metall::manager::allocator_type; namespace multi = boost::multi; -template +template using marray = multi::array>; using std::tuple; -int main(){ -std::filesystem::path dir = "llnl_metall_mapped_file.bin/"; -remove_all(dir); -{ - metall::manager m{metall::create_only, dir.c_str(), 1<<25}; - auto&& arr1d = - *m.construct>("arr1d")(tuple{10} , 99 , m.get_allocator()); - auto&& arr2d = - *m.construct>("arr2d")(tuple{1000, 1000}, 1.0, m.get_allocator()); - auto&& arr3d = - *m.construct>("arr3d")(tuple{10, 10, 10}, 1u , m.get_allocator()); - auto&& arr3d_cpy = - *m.construct>("arr3d_cpy")(tuple{0, 0, 0}, m.get_allocator()); - - assert( arr1d[3] == 99 ); - assert( arr2d[4][5] == 1.0 ); - assert( arr3d[2][3][4] == 1u ); - - arr1d[3] = 33; - arr2d[4][5] = 45.001; - std::iota(arr3d[6][7].begin(), arr3d[6][7].end(), 100); - - arr3d_cpy = arr3d; - assert( arr3d_cpy[6][7][8] == arr3d[6][7][8] ); - m.flush(); -} -{ - metall::manager m{metall::open_only, dir.c_str()}; - - auto&& arr1d = - *m.find>("arr1d").first; assert(std::addressof(arr1d)); - auto&& arr2d = - *m.find>("arr2d").first; assert(std::addressof(arr2d)); - auto&& arr3d = - *m.find>("arr3d").first; assert(std::addressof(arr3d)); - auto&& arr3d_cpy = - *m.find>("arr3d_cpy").first; assert(std::addressof(arr3d)); - - assert( arr1d[5] == 99 ); - assert( arr1d[3] == 33 ); - - assert( arr2d[7][8] == 1.0 ); - assert( arr2d[4][5] == 45.001 ); - - assert( arr3d[6][7][3] == 103 ); - assert( arr3d_cpy == arr3d ); - - m.destroy>("arr1d"); - m.destroy>("arr2d"); - m.destroy>("arr3d"); - m.destroy>("arr3d_cpy"); -} -remove_all(dir); -} +int main() { + std::filesystem::path const dir = "llnl_metall_mapped_file.bin/"; + remove_all(dir); + { + metall::manager m{metall::create_only, dir.c_str(), 1 << 25}; + auto&& arr1d = *m.construct>("arr1d")(std::tuple{10}, 5, m.get_allocator()); + auto&& arr2d = *m.construct>("arr2d")(std::tuple{10, 10}, 1.0, m.get_allocator()); + auto&& arr3d = *m.construct>("arr3d")(std::tuple{10, 10, 10}, 1u, m.get_allocator()); + + auto&& arr3d_copy = *m.construct>("arr3d_copy")(arr3d, m.get_allocator()); + + assert(arr1d[3] == 5); + assert(arr2d[4][5] == 1.0); + assert(arr3d[2][3][4] == 1U); + + arr1d[3] = 33; + arr2d[4][5] = 45.001; + std::iota(arr3d[6][7].begin(), arr3d[6][7].end(), 100); + + assert(arr3d_copy[6][7][8] == 1U); + + auto&& arr3d_assign = *m.construct>("arr3d_assign")(m.get_allocator()); + arr3d_assign = arr3d; + + assert(arr3d_assign == arr3d); + + assert(arr3d_assign[6][7][8] == arr3d[6][7][8]); + // m.flush(); + } + { + metall::manager m{metall::open_only, dir.c_str()}; + + auto const& arr1d = *m.find>("arr1d").first; + auto const& arr2d = *m.find>("arr2d").first; + auto const& arr3d = *m.find>("arr3d").first; + + auto const& arr3d_copy = *m.find>("arr3d_copy").first; + assert(std::addressof(arr3d)); + + auto const& arr3d_assign = *m.find>("arr3d_assign").first; + assert(std::addressof(arr3d)); + + assert(arr1d[5] == 5); + assert(arr1d[3] == 33); + + assert(arr2d[7][8] == 1.0); + assert(arr2d[4][5] == 45.001); + + assert(arr3d[6][7][3] == 103); + + assert(arr3d_assign == arr3d); + + m.destroy>("arr1d"); + m.destroy>("arr2d"); + m.destroy>("arr3d"); + m.destroy>("arr3d_copy"); + m.destroy>("arr3d_assign"); + } + + remove_all(dir); +} \ No newline at end of file diff --git a/examples/loops_timings.cpp b/examples/loops_timings.cpp index 0564c00ba..196cf388e 100644 --- a/examples/loops_timings.cpp +++ b/examples/loops_timings.cpp @@ -12,9 +12,7 @@ #include #include #include -#if __cplusplus >= 201703L #include -#endif #include #include @@ -199,14 +197,14 @@ cout<<"------------"<= 201603 { auto tic = watch::now(); @@ -232,11 +230,11 @@ for(auto&& p : timings) cout<< p.first.count()/1e9 <<"\t...."<< p.second <> vbs(nbasis, vector(nstates)); // v[basis][state] + vector> vbs(nbasis, vector(nstates)); // v[basis][state] for_each(begin(vbs), end(vbs), [&](auto& e){generate(begin(e), end(e), gen);}); vector d(nstates); { - cout<<"\traw loops state/basis\n"; + cout<<"\traw loops state/basis\n"; auto tic = watch::now(); fill(begin(d), end(d), 0.); for(std::size_t s = 0; s != nstates; ++s){ @@ -245,7 +243,7 @@ return 0; cout<<"\t\t"<< ns{watch::now()-tic}.count()/1e9 <<" seconds\t"<= 201603 { - cout<<"\traw loop basis, parallel transform states\n"; + cout<<"\traw loop basis, parallel transform states\n"; auto tic = watch::now(); fill(begin(d), end(d), 0.); for(std::size_t b = 0; b != nbasis; ++b) // cannot parallelize this loop @@ -274,11 +272,11 @@ return 0; } { cout<<"v[state][basis] storage\n"; - vector> vsb(nstates, vector(nbasis)); // v[state][basis] + vector> vsb(nstates, vector(nbasis)); // v[state][basis] for_each(begin(vsb), end(vsb), [&](auto& e){generate(begin(e), end(e), gen);}); vector d(nstates); { - cout<<"\traw loops state/basis\n"; + cout<<"\traw loops state/basis\n"; auto tic = watch::now(); fill(begin(d), end(d), 0.); for(std::size_t s = 0; s != nstates; ++s){ @@ -288,7 +286,7 @@ return 0; cout<<"\t\t"<< ns{toc-tic}.count()/1e9 <<" sec\t"<= 201603 { - cout<<"\traw loop basis transform state\n"; + cout<<"\traw loop basis transform state\n"; auto tic = watch::now(); fill(begin(d), end(d), 0.); for(std::size_t b = 0; b != nbasis; ++b) @@ -318,7 +316,7 @@ return 0; } #endif { - cout<<"\traw loop basis transform state\n"; + cout<<"\traw loop basis transform state\n"; auto tic = watch::now(); fill(begin(d), end(d), 0.); for(std::size_t b = 0; b != nbasis; ++b) @@ -328,7 +326,7 @@ return 0; } #if 0 { - cout<<"\traw loops basis/state\n"; + cout<<"\traw loops basis/state\n"; auto tic = watch::now(); fill(begin(d), end(d), 0.); for(std::size_t b = 0; b != nbasis; ++b){ @@ -337,7 +335,7 @@ return 0; cout<<"\t\t"<< ns{watch::now()-tic}.count()/1e9 <<" seconds\t"< A(v.data(), {100, 100}); assert(size(A) == 100); begin(A)[4][3] = 2.; // ok using multi::static_array_cast; - // auto const& A_const = static_array_cast(A); - // begin(A_const)[4][3] = 2.; // error, read only + // auto const& A_const = static_array_cast(A); + // begin(A_const)[4][3] = 2.; // error, read only } { std::vector dd(10000); multi::array_ref::iterator> arr(begin(dd), {100, 100}); assert(size(arr) == 100); begin(arr)[4][3] = 2.; - // assert( cbegin(arr)/2 ); - // assert( cbegin(arr) < cend(arr) ); + // assert( cbegin(arr)/2 ); + // assert( cbegin(arr) < cend(arr) ); } return 0; @@ -412,8 +410,8 @@ return 0; assert( begin(A) < end(A) ); assert( cbegin(A) < cend(A) ); -// assert( crbegin(A) < crend(A) ); -// assert( crend(A) > crbegin(A) ); +// assert( crbegin(A) < crend(A) ); +// assert( crend(A) > crbegin(A) ); assert( end(A) - begin(A) == size(A) ); assert( rend(A) - rbegin(A) == size(A) ); diff --git a/examples/lu_fact.cpp b/examples/lu_fact.cpp index 558a2b4b6..8577cb53b 100644 --- a/examples/lu_fact.cpp +++ b/examples/lu_fact.cpp @@ -1,13 +1,16 @@ #ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- $CXX $0 -o $0x -lboost_timer `pkg-config --libs tbb` &&$0x&&rm $0x;exit #endif -// © Alfredo A. Correa 2018-2020 +// Copyright 2018-2024 Alfredo A. Correa #include "../../multi/array.hpp" -#include // iota -#include // transform +#include // transform #include +#include +#include // iota + +#include namespace multi = boost::multi; @@ -22,8 +25,8 @@ Matrix&& lu_fact(Matrix&& A){ std::for_each(std::execution::par, begin(A) + k + 1, end(A), [&](auto&& Ai){ std::transform( - begin(Ai)+k+1, end(Ai), begin(Ak)+k+1, begin(Ai)+k+1, - [z=(Ai[k]/=Akk)](auto&& a, auto&& b){return a-z*b;} + begin(Ai)+k+1, end(Ai), begin(Ak)+k+1, begin(Ai)+k+1, + [z=(Ai[k]/=Akk)](auto a, auto b){return a - z*b;} ); } ); @@ -34,13 +37,13 @@ Matrix&& lu_fact(Matrix&& A){ template Matrix&& lu_fact2(Matrix&& A){ using multi::size; - auto m = A.size(), n = std::get<1>(sizes(A)); - - for(auto k = 0*m; k != m - 1; ++k){ + auto const [m, n] = A.sizes(); + + for(decltype(m) k = 0; k != m - 1; ++k){ for(auto i = k + 1; i != m; ++i){ auto const z = A[i][k]/A[k][k]; A[i][k] = z; - std::transform(begin(A[i]) + k + 1, begin(A[i]) + std::max(n, k + 1), A[k].begin() + k + 1, begin(A[i]) + k + 1, [&](auto&& a, auto&& b){return a - z*b;}); + std::transform(begin(A[i]) + k + 1, begin(A[i]) + std::max(n, k + 1), A[k].begin() + k + 1, begin(A[i]) + k + 1, [&](auto a, auto b){return a - z*b;}); } } return std::forward(A); @@ -49,10 +52,10 @@ Matrix&& lu_fact2(Matrix&& A){ template Matrix&& lu_fact3(Matrix&& A){ using multi::size; - auto m = A.size(), n = std::get<1>(sizes(A)); + auto const [m, n] = A.sizes(); for(auto k = 0*m; k != m - 1; ++k){ auto&& Ak = A[k]; - std::for_each(std::execution::par, begin(A) + k + 1, end(A), [&](auto&& Ai){ + std::for_each(std::execution::par, begin(A) + k + 1, end(A), [&](auto& Ai){ auto const z = Ai[k]/Ak[k]; Ai[k] = z; assert( k + 1 <= n ); @@ -62,19 +65,16 @@ Matrix&& lu_fact3(Matrix&& A){ return std::forward(A); } -#include -#include - using std::cout; int main(){ { multi::array A = { - {-3., 2., -4.}, - { 0., 1., 2.}, - { 2., 4., 5.} + {-3.0, 2.0, -4.0}, + { 0.0, 1.0, 2.0}, + { 2.0, 4.0, 5.0}, }; - multi::array y = {12.,5.,2.}; - double AA[3][3]; + multi::array y = {12.0, 5.0, 2.0}; + double AA[3][3]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types using std::copy; copy( begin(A), end(A), begin(*multi::array_ptr(&AA)) ); @@ -82,17 +82,13 @@ int main(){ lu_fact(AA); assert( std::equal(begin(A), end(A), begin(*multi::array_ptr(&AA)), end(*multi::array_ptr(&AA))) ); } -// return 0; { multi::array A({6000, 7000}); std::iota(A.data(), A.data() + A.num_elements(), 0.1); - std::transform(A.data(), A.data() + A.num_elements(), A.data(), [](auto x){return x/=2.e6;}); - // std::vector y(3000); std::iota(y.begin(), y.end(), 0.2); + std::transform(A.data(), A.data() + A.num_elements(), A.data(), [](auto x){return x/=2.0e6;}); { boost::timer::auto_cpu_timer t; lu_fact(A({3000, 6000}, {0, 4000})); cout << A[456][123] << std::endl; } - // cout << y[45] << std::endl; } } - diff --git a/examples/lu_solve.cpp b/examples/lu_solve.cpp index 752f74d33..3a2bc36f8 100644 --- a/examples/lu_solve.cpp +++ b/examples/lu_solve.cpp @@ -1,113 +1,186 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4-*- -$CXX -DNDEBUG $0 -o $0x -lboost_timer&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2021 - -#include "../array.hpp" - -#include // for std::abs -#include // for std::iota - -struct lup{ // LU method for decomposition and solution - -// translated from https://en.wikipedia.org/wiki/LU_decomposition#C_code_example -template -static auto decompose(Matrix&& A, Permutation&& P, double tol = std::numeric_limits::epsilon()){ - std::iota(begin(P), end(P), typename std::decay_t::value_type{0}); - auto const N = std::min(size(A), size(~A)); - assert( P.size() >= N ); - - auto&& ret = A({0, N}, {0, N}); - for(auto i : extension(ret)){ - if(lup::permute_max_diagonal(A, P, i) < tol) return A({0, i}, {0, i}); - - for(auto&& row : A({i + 1, N})){ - auto&& urow = row({i + 1, N}); - std::transform( - cbegin(urow), cend(urow), cbegin(A[i]({i + 1, N})), begin(urow), - [f = row[i] /= A[i][i]](auto const& a, auto const& b){return a - f*b;} - ); +#ifdef COMPILATION // clang-format off +set -x;${CXX:-c++} -std=c++20 -O3 -DNDEBUG -I../include $0 -o $0x &&time $0x&&rm $0x;exit +#endif // clang-format on + +// Copyright 2021-2024 Alfredo A. Correa + +#include + +#include // for std::abs +#include +#include // for std::iota +#include +#include + + namespace { + struct lup { // LU method for decomposition and solution + + // translated from https://en.wikipedia.org/wiki/LU_decomposition#C_code_example + template + static auto decompose(Matrix&& A, Permutation&& P, double tol = std::numeric_limits::epsilon()) { + auto const N = std::min(size(A), size(~A)); + assert(P.size() >= N); + + if(A.size() == 0) + return A({0, 1}, {0, 1}); + + auto&& ret = A({0, N}, {0, N}); + + // if(lup::permute_max(A, P) < tol) return A({0, 1}, {0, 1}); + // for(auto&& row : A({1, N})) { + // auto&& urow = row({1, N}); + // std::transform( + // cbegin(urow), cend(urow), cbegin(A[0]({1, N})), begin(urow), + // [f = row[i] /= A[0][0]](auto const& a, auto const& b) { return a - f * b; } + // ); + // } + + // decompose(A({1, N}, {1, N}), P({1, N})); + + for(auto i : extension(ret)) { + auto&& A_rest = A({i, N}); + auto&& P_rest = P({i, N}); + auto&& A_rest_rest = A_rest({0, N - i}, {i, N}); + if(lup::permute_max_diagonal(A_rest({0, N - i}, {0, N}), P_rest({0, N - i}), i) < tol) + return A({0, i}, {0, i}); + + for(auto&& row : A_rest({1, N - i})) { + auto&& urow = row({i + 1, N}); + std::transform( + cbegin(urow), cend(urow), cbegin(A_rest_rest[0]({1, N - i})), begin(urow), + [f = row[i] /= A_rest_rest[0][0]](auto const& a, auto const& b) { return a - f * b; } + ); + } + } + return std::move(ret); } - } - return std::move(ret); -} -template -static auto solve(Matrix const& LU, Permutation const& P, VectorSol&& x) -> VectorSol&&{ - return upper_solve(LU, lower_solve(LU, permute(P, x))); -} + // translated from https://en.wikipedia.org/wiki/LU_decomposition#C_code_example + template< + class Permutation, class Matrix> + static auto decompose(Matrix&& A, double tol = std::numeric_limits::epsilon()) { + Permutation P(A.size()); + std::iota(begin(P), end(P), typename std::decay_t::value_type{0}); -private: + decompose(A, P, tol); + return std::pair(std::forward(A), std::move(P)); + } -template -static auto permute_max_diagonal(Matrix&& LU, Permutation&& P, Index i){ - auto mi = std::max_element(begin(LU) + i, end(LU), [i](auto const& a, auto const& b){return std::abs(a[i]) < std::abs(b[i]);}) - begin(LU); - swap(LU[i], LU[mi]); - std::swap(P [i], P [mi]); - return std::abs(LU[i][i]); -} + template + static auto solve(Matrix const& LU, Permutation const& P, VectorSol&& x) -> VectorSol&& { + return upper_solve(LU, lower_solve(LU, permute(P, x))); + } -template -static auto permute(Permutation const& p, Vector&& data) -> Vector&&{ - assert(size(p) <= size(data)); - using index = typename Permutation::size_type; - for(index i = 0; i != size(p); ++i){ - index k = p[i]; - for( ; k > i; k = p[k]){} - index pk = p[k]; - if(k >=i and pk != i){ - auto const t = data[i]; - for( ; pk != i; k = pk, pk = p[k]){ - data[k] = data[pk]; - }; - data[k] = t; + private: + template + static auto permute_max_diagonal(Matrix&& LU, Permutation&& P, Index i) { + auto mi = std::max_element(begin(LU), end(LU), [i](auto const& a, auto const& b) { return std::abs(a[i]) < std::abs(b[i]); }) - begin(LU); + using std::swap; + swap(LU[0], LU[mi]); + swap(P[0], P[mi]); + return std::abs(LU[0][i]); } - } - return std::forward(data); -} -template -static auto lower_solve(LUMatrix const& LU, Vector&& x) -> Vector&&{ - assert(size(LU) <= size(x)); - auto const N = size(LU); - for(typename LUMatrix::size_type i = 0; i != N; ++i){ - auto const& Lrowi = LU[i]({0, i}); - x[i] -= std::inner_product(begin(Lrowi), end(Lrowi), cbegin(x), 0.); - } - return std::forward(x); + template + static auto permute_max(Matrix&& LU, Permutation&& P) { + auto mi = std::max_element(begin(LU), end(LU), [](auto const& a, auto const& b) { return std::abs(a[0]) < std::abs(b[0]); }) - begin(LU); + if(mi != 0) { + using std::swap; + swap(LU[0], LU[mi]); + swap(P[0], P[mi]); + } + return std::abs(LU[0][0]); + } + + template + static auto permute(Permutation const& p, Vector&& data) -> Vector&& { + assert(size(p) <= size(data)); + using index = typename Permutation::size_type; + for(index i = 0; i != size(p); ++i) { + index k = p[i]; + for(; k > i; k = p[k]) { + } + index pk = p[k]; + if(k >= i and pk != i) { + auto const t = data[i]; + for(; pk != i; k = pk, pk = p[k]) { + data[k] = data[pk]; + }; + data[k] = t; + } + } + return std::forward(data); + } + + template + static auto lower_solve(LUMatrix const& LU, Vector&& x) -> Vector&& { + assert(size(LU) <= size(x)); + auto const N = size(LU); + for(typename LUMatrix::size_type i = 0; i != N; ++i) { + auto const& Lrowi = LU[i]({0, i}); + x[i] -= std::inner_product(begin(Lrowi), end(Lrowi), cbegin(x), 0.); + } + return std::forward(x); + } + + template + static auto upper_solve(LUMatrix const& LU, Vector&& x) -> Vector&& { + assert(size(LU) <= size(x)); + auto const N = size(LU); + for(typename LUMatrix::size_type i = N - 1; i >= 0; --i) { + auto const& Urowi = LU[i]({i + 1, N}); + (x[i] -= std::inner_product(begin(Urowi), end(Urowi), cbegin(x) + i + 1, 0.)) /= LU[i][i]; + } + return std::forward(x); + } + }; } -template -static auto upper_solve(LUMatrix const& LU, Vector&& x) -> Vector&&{ - assert(size(LU) <= size(x)); - auto const N = size(LU); - for(typename LUMatrix::size_type i = N - 1; i >= 0; --i){ - auto const& Urowi = LU[i]({i + 1, N}); - (x[i] -= std::inner_product(begin(Urowi), end(Urowi), cbegin(x) + i + 1, 0.)) /= LU[i][i]; +namespace multi = boost::multi; + +int main() try { + { + multi::array const Aconst = { + { 6.80, -6.05, -0.45, 8.32, -9.67}, + {-2.11, -3.30, 2.58, 2.71, -5.14}, + { 5.66, 5.36, -2.70, 4.35, -7.26}, + { 5.97, -4.44, 0.27, -7.17, 6.08}, + { 8.23, 1.08, 9.04, 2.14, -6.87} + }; + auto A = Aconst; + // multi::array P(multi::array::extensions_type{5}); + auto P = std::get<1>(lup::decompose>(A)); + + multi::array x = {4.02, 6.19, -8.22, -7.57, -3.03}; + + lup::solve(A, P, x); + + std::cout << std::abs(x[4]) << std::endl; + (std::abs(x[4] - 0.565756) < 1e-4) ?: throw std::source_location::current(); } - return std::forward(x); -} + { + multi::array A({4000, 4000}); -}; + std::random_device rd; // Non-deterministic random number generator + std::mt19937 gen(rd()); // Mersenne Twister engine seeded with rd() -namespace multi = boost::multi; + std::uniform_real_distribution dist(-1.0, 1.0); + for(auto&& e : A.elements()) { + e = dist(gen); + } -int main(){ - multi::array const Aconst = { - { 6.80, -6.05, -0.45, 8.32, -9.67}, - {-2.11, -3.30, 2.58, 2.71, -5.14}, - { 5.66, 5.36, -2.70, 4.35, -7.26}, - { 5.97, -4.44, 0.27,-7.17, 6.08}, - { 8.23, 1.08, 9.04, 2.14, -6.87} - }; - auto A = Aconst; - multi::array P({5}, 0.); - lup::decompose(A, P); + multi::array P(multi::array::extensions_type{A.size()}); + std::iota(P.begin(), P.end(), int{}); + lup::decompose(A, P); - multi::array x = {4.02, 6.19, -8.22, -7.57, -3.03}; + multi::array x(A.size()); + for(auto&& e : x.elements()) { + e = dist(gen); + } - lup::solve(A, P, x); - - assert( std::abs(x[4] - 0.565756) < 1e-4); + lup::solve(A, P, x); + } +} catch(std::source_location const& loc) { + std::cerr << loc.file_name() << ':' << loc.line() << '\n'; + throw; } - diff --git a/examples/mpi3.cpp b/examples/mpi3.cpp index a7133151d..0251e8597 100644 --- a/examples/mpi3.cpp +++ b/examples/mpi3.cpp @@ -14,41 +14,40 @@ namespace multi = boost::multi; void test_1D(mpi3::communicator& comm){ - switch(comm.rank()){ - case 0:{ + auto const rank = comm.rank(); + switch(rank){ + case 0: multi::array v(100); - std::iota(v.begin(), v.end(), 0.); + std::iota(v.begin(), v.end(), 0.0); assert( v.strided(2).size() == 50 and v.strided(2)[9] == 18 ); comm.send(v.strided(2).begin(), v.strided(2).end(), 1); - return; - } - case 1:{ - multi::array v(50); - comm.receive(v.begin(), v.end(), 0); - assert( v[9] == 18 ); - return; - } + break; + case 1: + multi::array w(50); + comm.receive(w.begin(), w.end(), 0); + assert( w[9] == 18 ); + break; + default: assert(0); } - assert(0); - + return; } void test_2D(mpi3::communicator& comm){ - auto const v = []{ + auto const v = std::invoke([] { multi::array v({4, 5}); - std::iota(begin(v.elements()), end(v.elements()), 0.); + std::iota(begin(v.elements()), end(v.elements()), 0.0); return v; - }(); + }); auto&& vpart = v({1, 4}, {1, 3}); - switch(comm.rank()){ + switch(comm.rank()) { case 0: comm.send(begin(vpart), end(vpart), 1); return; case 1: - multi::array w(extensions(vpart), 99.); - comm.receive(begin(w), end(w), 0); + multi::array w(extensions(vpart), 99.0); + comm.receive(begin(w), end(w), 0); assert( w == vpart ); return; } @@ -57,69 +56,70 @@ void test_2D(mpi3::communicator& comm){ } void test_2D_complex(mpi3::communicator& comm){ - using complex = std::complex; multi::array v({4, 5}); using std::get; - if(auto x = v.extensions()) - for(auto i: get<0>(x)) - for(auto j: get<1>(x)) + if(auto const x = v.extensions()) { + auto const [is, js] = x; + for(auto i: is) { + for(auto j: js) { v[i][j] = complex(i, j); + } + } + } - switch(comm.rank()){ + switch(comm.rank()) { case 0: - comm.send(begin(v), end(v), 1); break; + comm.send(begin(v), end(v), 1); + break; case 1: - multi::array w(extensions(v), 99.); + multi::array w(extensions(v), 99.0); comm.receive(begin(w), end(w), 0); - assert( w[2][3] == std::complex(2., 3.) ); + assert( w[2][3] == std::complex(2.0, 3.0) ); break; + default: assert(0); } - } void test_3D(mpi3::communicator& comm){ - auto const v = []{ + auto const v = std::invoke([]{ multi::array v({4, 5, 7}); - std::iota(begin(v.elements()), end(v.elements()), 0.); + std::iota(begin(v.elements()), end(v.elements()), 0.0); return v; - }(); + }); auto&& vpart = v({1, 4}, {1, 3}, {3, 6}); + switch(comm.rank()){ case 0: comm.send(begin(vpart), end(vpart), 1); return; case 1: - multi::array w(extensions(vpart), 99.); + multi::array w(extensions(vpart), 99.0); comm.receive(begin(w), end(w), 0); assert( w == vpart ); return; } assert(0); - } void test_2D_strides(mpi3::communicator& comm){ multi::array v({4, 5}); - std::iota(v.elements().begin(), v.elements().end(), 0.); + std::iota(v.elements().begin(), v.elements().end(), 0.0); std::cout << std::endl; - switch(comm.rank()){ - case 0:{ + switch(comm.rank()) { + case 0: comm.send_n(v({1, 3}, {2, 4}).begin(), v({1, 3}, {2, 4}).size(), 1); return; - } - case 1:{ - multi::array w({4, 5}, 99.); + case 1: + multi::array w({4, 5}, 99.0); comm.receive(w({1, 3}, {2, 4}).begin(), w({1, 3}, {2, 4}).end(), 0); assert( w({1, 3}, {2, 4}) == v({1, 3}, {2, 4}) ); return; - } } assert(0); - } /* @@ -146,49 +146,21 @@ void test_3D(mpi3::communicator& comm){ } */ -void test_vector_nonpod(mpi3::communicator& comm){ - - switch(comm.rank()){ - case 0:{ - std::vector v(10); - v[2] = "hola"; - comm.send_n(v.begin(), v.size(), 1); - return; - } - case 1:{ - std::vector v(10); - comm.receive_n(v.begin(), v.size(), 0); - assert( v[2] == "hola" ); - return; - } - } - assert(0); - -} - -#if 0 -void test_1D_nonpod(mpi3::communicator& comm){ +void test_vector_nonpod(mpi3::communicator& comm) { + std::vector v(10); - switch(comm.rank()){ - case 0:{ - multi::array v(10); - v[2] = "hola"; - comm.send_n(v.begin(), v.size(), 1); - return; - } - case 1:{ - multi::array v(10); - comm.receive_n(v.begin(), v.size(), 0); - assert( v[2] == "hola" ); - return; - } - } - assert(0); + if(comm.rank() == 0) { + v[2] = "hola"; + comm.send_n(v.begin(), v.size(), 1); + } else if(comm.rank() == 1) { + comm.receive_n(v.begin(), v.size(), 0); + assert( v[2] == "hola" ); + } else { assert(0); } + return; } -#endif -int mpi3::main(int, char*[], mpi3::environment& env){ +int mpi3::main(int, char*[], mpi3::environment& env) { auto world = env.world(); @@ -200,12 +172,12 @@ int mpi3::main(int, char*[], mpi3::environment& env){ { auto self = env.get_self_instance(); - auto const v = []{ + auto const v = std::invoke({ multi::array v({4, 5}); - std::iota(v.elements().begin(), v.elements().end(), 0.); + std::iota(v.elements().begin(), v.elements().end(), 0.0); return v; - }(); - multi::array w({4, 2}, 0.); + }); + multi::array w({4, 2}, 0.0); self.gather_n(v({0, 4}, {2, 4}).begin(), v({0, 4}, {2, 4}).size(), w.begin()); @@ -225,10 +197,10 @@ int mpi3::main(int, char*[], mpi3::environment& env){ auto self = env.get_self_instance(); auto const v = []{ multi::array v({4, 5}); - std::iota(v.elements().begin(), v.elements().end(), 0.); + std::iota(v.elements().begin(), v.elements().end(), 0.0); return v; }(); - multi::array w({5, 4}, 0.); + multi::array w({5, 4}, 0.0); self.gather_n(v.rotated().begin(), v.rotated().size(), w.begin()); assert( v.rotated() == w ); } @@ -245,22 +217,21 @@ int mpi3::main(int, char*[], mpi3::environment& env){ assert( t.size() == sizeof(double) ); assert( t.extent() == sizeof(double)*2 ); } - { auto self = env.get_self_instance(); multi::array v(10); - std::iota(v.elements().begin(), v.elements().end(), 0.); + std::iota(v.elements().begin(), v.elements().end(), 0.0); multi::array w(10); self.gather_n(v.begin(), v.size(), w.begin()); assert( w == v ); } { auto self = env.get_self_instance(); - auto const v = []{ + auto const v = std::invoke([] { multi::array v(60); - std::iota(v.elements().begin(), v.elements().end(), 0.); + std::iota(v.elements().begin(), v.elements().end(), 0.0); return v; - }(); + }); multi::array w(30); assert( v.strided(2).size() == 30 ); @@ -269,45 +240,44 @@ int mpi3::main(int, char*[], mpi3::environment& env){ } { auto self = env.get_self_instance(); - auto const v = []{ + auto const v = std::invoke([] { multi::array v(30); - std::iota(v.elements().begin(), v.elements().end(), 0.); + std::iota(v.elements().begin(), v.elements().end(), 0.0); return v; - }(); + }); multi::array w(10); self.gather_n(v({10, 30}).strided(2).begin(), v({10, 30}).strided(2).size(), w.data_elements()); assert( v({10, 30}).strided(2) == w ); } { auto self = env.get_self_instance(); - auto const v = []{ + auto const v = std::invoke([] { multi::array v({4, 5}); - std::iota(v.elements().begin(), v.elements().end(), 0.); + std::iota(v.elements().begin(), v.elements().end(), 0.0); return v; - }(); - multi::array w({2, 5}, 0.); + }); + multi::array w({2, 5}, 0.0); self.gather_n(v.strided(2).begin(), v.strided(2).size(), w.begin()); assert( v.strided(2) == w ); } - { auto self = env.get_self_instance(); - auto const v = []{ + auto const v = std::invoke([] { multi::array v({6, 4, 5}); - std::iota(v.elements().begin(), v.elements().end(), 0.); + std::iota(v.elements().begin(), v.elements().end(), 0.0); return v; - }(); - multi::array w({3, 4, 5}, 0.); + }); + multi::array w({3, 4, 5}, 0.0); for(auto const& e : v.elements()) std::cout<< e <<','; std::cout << std::endl; self.gather_n(v.strided(2).begin(), v.strided(2).size(), w.begin()); - assert( v.strided(2) == w ); + assert( v.strided(2) == w ); } + return 0; } - diff --git a/examples/polymorphic_memory_resource.cpp b/examples/polymorphic_memory_resource.cpp index c9b3224e1..a719c4bfa 100644 --- a/examples/polymorphic_memory_resource.cpp +++ b/examples/polymorphic_memory_resource.cpp @@ -1,7 +1,7 @@ #ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- ${CXX:-c++} -std=c++17 $CXXFLAGS $0 -o $0x&&$0x&&rm $0x;exit #endif -// © Alfredo A. Correa 2020 +// © Alfredo A. Correa 2020-2024 #include "../../multi/array.hpp" @@ -13,6 +13,7 @@ namespace multi = boost::multi; int main() { static_assert( sizeof(multi::array) < sizeof(multi::pmr::array) , "!"); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) make a small buffer TODO(correaa) replace with a std::array char buffer[13] = "____________"; // flawfinder: ignore , a small buffer on the stack or an allocation std::pmr::monotonic_buffer_resource pool{ std::data(buffer), std::size(buffer), @@ -23,12 +24,12 @@ int main() { multi::pmr::array B({3, 2}, 'b', &pool); assert( A.get_allocator() == B.get_allocator() ); - assert( buffer == std::string{"aaaabbbbbb__"} ); + assert( std::data(buffer) == std::string{"aaaabbbbbb__"} ); try { multi::pmr::array C({9, 9}, 'c', &pool); // there is no upstream resource so it throws } catch(std::bad_alloc&) { - assert( buffer == std::string{"aaaabbbbbb__"} ); + assert( std::data(buffer) == std::string{"aaaabbbbbb__"} ); } std::array buffer2; @@ -38,6 +39,7 @@ int main() { }; { multi::pmr::array D = A; + D[0][0] = 'c'; assert(D.get_allocator() != A.get_allocator() ); assert(D.get_allocator().resource() == std::pmr::get_default_resource() ); } diff --git a/examples/redux.cpp b/examples/redux.cpp new file mode 100644 index 000000000..08259c8d0 --- /dev/null +++ b/examples/redux.cpp @@ -0,0 +1,769 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdouble-promotion" +#pragma GCC diagnostic ignored "-Wunused-macros" +#endif +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#pragma clang diagnostic ignored "-Wpadded" +#endif +#if defined(_MSC_VER) +#pragma warning(disable : 4244) // warning C4244: 'initializing': conversion from '_Ty' to '_Ty', possible loss of data +#endif + +#include // IWYU pragma: keep +#include // for array, implicit_cast, explicit_cast + +#include + +#include // IWYU pragma: keep +#include // NOLINT(build/c++11) +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include +#include +#include // for std::decay_t +#include // for move // IWYU pragma: keep // NOLINT(misc-include-cleaner) bug in clang-tidy 19 + +// IWYU pragma: no_include // for reduce, transform_reduce +// IWYU pragma: no_include // for abs +// IWYU pragma: no_include // for bad_alloc + +#ifndef __NVCC__ +#if defined(__has_include) && __has_include() && (!defined(__INTEL_LLVM_COMPILER) || (__INTEL_LLVM_COMPILER > 20240000)) +#if !(defined(__clang__) && defined(__CUDA__)) +#include // IWYU pragma: keep +#define HAS_STD_EXECUTION 1 +#endif +#endif +#endif + +namespace multi = boost::multi; + +#if (__cplusplus >= 202302L) +#if __has_include() +#include +#endif +#endif + +#if defined(__cpp_lib_generator) && (__cpp_lib_generator >= 202207L) && !defined(_MSC_VER) +template +auto co_extensions_elements(Arr2D const& arr2d) -> std::generator { + auto const [is, js] = arr2d.extensions(); + for(auto const i : is) { + for(auto const j : js) { + co_yield typename Arr2D::indexes{i, j}; + } + } +} + +template +std::generator +co_celements(Arr2D const& arr2d) { + auto const [is, js] = arr2d.extensions(); + for(auto const i : is) { + for(auto const j : js) { + co_yield arr2d[i][j]; + } + } +} + +#endif + +class watch { + std::chrono::time_point start_ = std::chrono::high_resolution_clock::now(); + + std::string msg_; + bool running_ = true; + + template +#if defined(_MSC_VER) + inline __forceinline static auto do_not_optimize_(T&& value) noexcept -> T&& { + return std::forward(value); + } +#else + inline __attribute__((always_inline)) // NOLINT(readability-redundant-inline-specifier) + static auto + do_not_optimize_(T&& value) noexcept -> T&& { + if constexpr(std::is_pointer_v) { + asm volatile("" : "+m"(value)::"memory"); // NOLINT(hicpp-no-assembler) + } else { + asm volatile("" : "+r"(value)::); // NOLINT(hicpp-no-assembler) + } + return std::forward(value); + } +#endif + + public: + explicit watch(std::string_view msg) : msg_(msg) {} // NOLINT(fuchsia-default-arguments-calls) + template + auto lap(T&& some) const -> T&& { + do_not_optimize_(const_cast*>(&some)); // NOLINT(cppcoreguidelines-pro-type-const-cast) + std::cerr << msg_ << ": " << std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_).count() << " ms\n"; + return std::forward(some); + } + template + void stop(T&& some) { + if(running_) { + running_ = false; + lap(std::forward(some)); + } + } + + ~watch() { + if(running_) { + stop(*this); + } + } + watch(watch const&) = delete; + watch(watch&&) = delete; + auto operator=(watch const&) = delete; + auto operator=(watch&&) = delete; + // non-default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator +}; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // multi::array::size_type const maxsize = 39062; // 390625; + // multi::array::size_type const nmax = 1000; // 10000; + + // auto pp = [] /*__host__ __device__*/ (long ix, long iy) -> double { return double(ix) * double(iy); }; + + auto const nx = 40000; // nmax; // for(long nx = 1; nx <= nmax; nx *= 10) + auto const ny = 2000; // maxsize; // for(long ny = 1; ny <= maxsize; ny *= 5) + + // auto const nx = 4000; // nmax; // for(long nx = 1; nx <= nmax; nx *= 10) + // auto const ny = 200; // maxsize; // for(long ny = 1; ny <= maxsize; ny *= 5) + + // auto total = nx*ny; + + // nx = 2; + // ny = total / nx; + + multi::array K2D({nx, ny}); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-id-dependent-backward-branch) + for(multi::array::index iy = 0; iy != ny; ++iy) { // NOLINT(altera-id-dependent-backward-branch,altera-unroll-loops) + K2D[ix][iy] = static_cast(ix) * static_cast(iy); + } + } + + { + auto const accumulator = [&]() { + multi::array ret({nx}, 0.0); + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-id-dependent-backward-branch) + for(multi::array::index iy = 0; iy != ny; ++iy) { // NOLINT(altera-id-dependent-backward-branch,altera-unroll-loops) + ret[ix] += K2D[ix][iy]; + } + } + return ret; + }(); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - (static_cast(ix) * ny * (ny - 1.0) / 2.0) ) < 1.0e-8); + } + } + +#if defined(NDEBUG) && !defined(RUNNING_ON_VALGRIND) + + { + auto const accumulator = [&](watch = watch("raw loop")) { // NOLINT(fuchsia-default-arguments-declarations) + multi::array ret({nx}, 0.0); + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-id-dependent-backward-branch) + for(multi::array::index iy = 0; iy != ny; ++iy) { // NOLINT(altera-id-dependent-backward-branch,altera-unroll-loops) + ret[ix] += K2D[ix][iy]; + } + } + return ret; + }(); // NOLINT(fuchsia-default-arguments-calls) + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&] { + watch const _("accumulate for"); + return std::accumulate( + (~K2D).begin(), (~K2D).end(), + multi::array(multi::array::extensions_type{K2D.extension()}, 0.0), + [](auto const& acc, auto const& col) { + multi::array res(acc.extensions()); + for(auto const i : col.extension()) { // NOLINT(altera-unroll-loops) + res[i] = acc[i] + col[i]; + } + return res; + } + ); + }(); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&](auto init) { + watch const _("accumulate move"); + return std::accumulate( + (~K2D).begin(), (~K2D).end(), std::move(init), [](auto&& acc, auto const& col) { + multi::array ret(std::forward(acc)); + for(auto const i : col.extension()) { // NOLINT(altera-unroll-loops) + ret[i] += col[i]; + } + return ret; + } + ); + }(multi::array(K2D.extension(), 0.0)); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&](auto init) { + watch const _("accumulate forward"); + return std::accumulate( + (~K2D).begin(), (~K2D).end(), std::move(init), [](auto&& acc, auto const& col) -> decltype(acc) { + for(auto const i : col.extension()) { // NOLINT(altera-unroll-loops) + acc[i] += col[i]; + } + return std::forward(acc); + } + ); + }(multi::array(K2D.extension(), 0.0)); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&](auto init, watch = watch("accumulate transform forward")) { // NOLINT(fuchsia-default-arguments-declarations) + return std::accumulate( + (~K2D).begin(), (~K2D).end(), std::move(init), [](auto&& acc, auto const& col) -> decltype(acc) { + std::transform(col.begin(), col.end(), acc.begin(), acc.begin(), [](auto const& cole, auto&& acce) { return std::forward(acce) + cole; }); + return std::forward(acc); + } + ); + }(multi::array(K2D.extension(), 0.0)); // NOLINT(fuchsia-default-arguments-calls) + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + +#if (!defined(__GLIBCXX__) || (__GLIBCXX__ >= 20190502)) + { + auto const accumulator = [&] { + watch const _("reduce transform forward"); + return std::reduce( + (~K2D).begin(), (~K2D).end(), multi::array(K2D.extension(), 0.0), [](auto acc, auto const& col) { + multi::array ret(std::move(acc)); + std::transform(col.begin(), col.end(), ret.begin(), ret.begin(), [](auto const& cole, auto&& acce) { return std::forward(acce) + cole; }); + return ret; + } + ); + }(); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } +#endif + + { + auto const accumulator = [&] { + watch const _("transform accumulate element zero"); + + multi::array ret(K2D.extension()); + std::transform( + K2D.begin(), K2D.end(), ret.begin(), [](auto const& row) { return std::accumulate(row.begin(), row.end(), 0.0); } + ); + return ret; + }(); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + +#if (!defined(__GLIBCXX__) || (__GLIBCXX__ >= 20190502)) + { + auto const accumulator = [&] { + watch const _("transform reduce element zero"); + + multi::array ret(K2D.extension()); + std::transform( + K2D.begin(), K2D.end(), ret.begin(), [](auto const& row) { return std::reduce(row.begin(), row.end(), 0.0); } + ); + return ret; + }(); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } +#endif + + { + auto const accumulator = [&](auto&& init) { + watch const _("transform accumulate"); + std::transform( + K2D.begin(), K2D.end(), init.begin(), init.begin(), [](auto const& row, auto rete) { return std::accumulate(row.begin(), row.end(), std::move(rete)); } + ); + return std::forward(init); + }(multi::array(K2D.extension(), 0.0)); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + +#if (!defined(__GLIBCXX__) || (__GLIBCXX__ >= 20200000)) + { + auto const accumulator = [&](auto&& init) { + watch const _("> transform reduce"); + std::transform( + K2D.begin(), K2D.end(), init.begin(), init.begin(), [](auto const& row, auto rete) { return std::reduce(row.begin(), row.end(), std::move(rete)); } + ); + return std::forward(init); + }(multi::array(K2D.extension(), 0.0)); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } +#endif + +#if (__cplusplus >= 202002L) +#if (defined(__has_include) && __has_include()) +#if !defined(__NVCC__) && !defined(__NVCOMPILER) && !(defined(__clang__) && defined(__CUDA__)) +#if (!defined(__clang_major__) || (__clang_major__ > 7)) +#if (!defined(__GLIBCXX__) || (__GLIBCXX__ >= 20220000)) && !defined(_LIBCPP_VERSION) +#if !defined(__apple_build_version__) && (!defined(__INTEL_LLVM_COMPILER) || (__INTEL_LLVM_COMPILER > 20240000)) + { + auto const accumulator = [&](watch = watch("transform reduce[unseq]")) { // NOLINT(fuchsia-default-arguments-declarations) + multi::array ret(K2D.extension(), 0.0); + std::transform( + K2D.begin(), K2D.end(), + ret.begin(), + ret.begin(), + [](auto const& row, auto rete) { return std::reduce(std::execution::unseq, row.begin(), row.end(), std::move(rete)); } + ); + return ret; + }(); // NOLINT(fuchsia-default-arguments-calls) + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&] { + watch const _("transform reduce[par]"); + + multi::array ret(K2D.extension(), 0.0); + std::transform( + K2D.begin(), K2D.end(), + ret.begin(), + ret.begin(), + [](auto const& row, auto rete) { return std::reduce(std::execution::par, row.begin(), row.end(), std::move(rete)); } + ); + return ret; + }(); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&] { + watch const _("transform reduce[par_unseq]"); + + multi::array ret(K2D.extension(), 0.0); + std::transform( + K2D.begin(), K2D.end(), + ret.begin(), + ret.begin(), + [](auto const& row, auto rete) { return std::reduce(std::execution::par_unseq, row.begin(), row.end(), std::move(rete)); } + ); + return ret; + }(); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&]() { + watch const _("transform[par] reduce"); + + multi::array ret(K2D.extension(), 0.0); + std::transform( + std::execution::par, + K2D.begin(), K2D.end(), + ret.begin(), + ret.begin(), + [](auto const& row, auto rete) { return std::reduce(row.begin(), row.end(), std::move(rete)); } + ); + return ret; + }(); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&](auto ret) { + watch const _("* transform[par] reduce[unseq]"); + std::transform( + std::execution::par, + K2D.begin(), K2D.end(), + ret.begin(), + ret.begin(), + [](auto const& row, auto rete) { return std::reduce(std::execution::unseq, row.begin(), row.end(), std::move(rete)); } + ); + return ret; + }(multi::array(K2D.extension(), 0.0)); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + multi::array accumulator(K2D.extension(), 0.0); + [&](auto acc_begin) { + watch const _("transform[par] reduce[unseq] iterator"); + return std::transform( + std::execution::par, + K2D.begin(), K2D.end(), + acc_begin, acc_begin, + [](auto const& row, auto rete) { return std::reduce(std::execution::unseq, row.begin(), row.end(), std::move(rete)); } + ); + }(accumulator.begin()); + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + } + } + + { + auto const accumulator = [&](auto zero_elem, watch = watch("transform[par] reduce[unseq] element zero")) { // NOLINT(fuchsia-default-arguments-declarations) + multi::array ret(K2D.extension()); + std::transform( + std::execution::par, + K2D.begin(), K2D.end(), + ret.begin(), + [zz = std::move(zero_elem)](auto const& row) { return std::reduce(std::execution::unseq, row.begin(), row.end(), std::move(zz)); } + ); + return ret; + }(0.0); // NOLINT(fuchsia-default-arguments-calls) + + for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-5); + } + } +#endif +#endif +#endif +#endif +#endif +#endif // __NVCC__ +#endif + + // { + // auto const accumulator = [&](auto&& init) { + // watch const _("blas gemv"); + // multi::array ones({init.extension()}, 1.0); + // multi::blas::gemv_n(1.0, K2D.begin(), K2D.size(), ones.begin(), 0.0, init.begin()); + // return std::forward(init); + // }(multi::array(K2D.extension(), 0.0)); + + // for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + // BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + // } + // } + + // { + // auto const accumulator = [&](auto&& init) { + // watch const _("blas gemv smart"); + // multi::blas::gemv_n(1.0, K2D.begin(), K2D.size(), init[0].begin(), 0.0, init[1].begin()); + // return +init[1]; + // }(multi::array({2, K2D.extension()}, 1.0)); + + // for(multi::array::index ix = 0; ix != nx; ++ix) { // NOLINT(altera-unroll-loops) + // BOOST_TEST( std::abs( accumulator[ix] - static_cast(ix) * ny * (ny - 1.0) / 2.0 ) < 1.0e-8); + // } + // } + +#if defined(NDEBUG) + // Chris + { + multi::size_t const em = 1200 / 2; + multi::size_t const en = 1000 / 2; + multi::size_t const ell = 800 / 2; + + multi::array a3d({em, en, ell}); + multi::array b2d({en, ell}); + + std::mt19937 gen(42); // NOLINT(cert-msc32-c,cert-msc51-cpp) use for unpredictable std::random_device{} + std::uniform_real_distribution<> distrib; + + std::generate(a3d.elements().begin(), a3d.elements().end(), [&]() { return distrib(gen); }); + std::generate(b2d.elements().begin(), b2d.elements().end(), [&]() { return distrib(gen); }); + + // std::iota(a3d.elements().begin(), a3d.elements().end(), 20.0); + // std::iota(b2d.elements().begin(), b2d.elements().end(), 30.0); + + multi::array c_gold(em, 0.0); + { + for(multi::index k = 0; k != em; ++k) { + for(multi::index j = 0; j != en; ++j) { // NOLINT(altera-unroll-loops) + for(multi::index i = 0; i != ell; ++i) { // NOLINT(altera-unroll-loops) + c_gold[k] += a3d[k][j][i] * b2d[j][i]; + } + } + } + c_gold = multi::array(em, 0.0); + + for(multi::index const k : a3d.extension()) { + auto const& a3dk = a3d[k]; + for(multi::index const j : b2d.extension()) { // NOLINT(altera-unroll-loops) + auto const& a3dkj = a3dk[j]; + auto const& b2dj = b2d[j]; + for(multi::index const i : b2dj.extension()) { // NOLINT(altera-unroll-loops) + c_gold[k] += a3dkj[i] * b2dj[i]; + } + } + } + } + + { + watch _("chris raw 3-loop"); + + multi::array c_flat(em, 0.0); + + for(multi::index const k : a3d.extension()) { + auto const& a3dk = a3d[k]; + for(multi::index const j : b2d.extension()) { // NOLINT(altera-unroll-loops) + auto const& a3dkj = a3dk[j]; + auto const& b2dj = b2d[j]; + for(multi::index const i : b2dj.extension()) { // NOLINT(altera-unroll-loops) + c_flat[k] += a3dkj[i] * b2dj[i]; + } + } + } + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } + + { + watch _("chris raw 2-loop flat"); + + multi::array c_flat(em, 0.0); + + for(auto const k : c_flat.extension()) { + auto const& a3d_rowes = a3d[k].elements().base(); + auto const& b2d_elements = b2d.elements().base(); + for(auto const ji : b2d.elements().extension()) { // NOLINT(altera-unroll-loops) + c_flat[k] += a3d_rowes[ji] * b2d_elements[ji]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + } + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } + + { + watch _("chris raw 2-loop flat reversed"); + + multi::array c_flat(em, 0.0); + + for(auto const ji : b2d.elements().extension()) { // NOLINT(altera-unroll-loops) + auto const& b2deji = b2d.elements().base()[ji]; + for(auto const k : c_flat.extension()) { // NOLINT(altera-unroll-loops) + c_flat[k] += a3d[k].elements().base()[ji] * b2deji; + } + } + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } + + { + watch _("chris raw 1-loop flat reversed transform"); + + multi::array c_flat(em, 0.0); + + for(auto const ji : b2d.elements().extension()) { // NOLINT(altera-unroll-loops) + std::transform(c_flat.begin(), c_flat.end(), a3d.begin(), c_flat.begin(), [&](auto&& c_flat_elem, auto const& a3d_row) { + return std::forward(c_flat_elem) + a3d_row.elements().base()[ji] * b2d.elements().base()[ji]; + }); + } + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } + + { + watch _("chris raw 1-loop transform_reduce"); + + multi::array c_flat(em); + + for(auto const k : c_flat.extension()) { // NOLINT(altera-unroll-loops) + auto const& a3dkes = a3d[k].elements(); + c_flat[k] = std::transform_reduce( + a3dkes.base(), a3dkes.base() + a3dkes.size(), + b2d.elements().base(), 0.0 + ); + } + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } + + { + watch _("chris transform transform_reduce pointer"); + multi::array c_flat(em); + + std::transform( + a3d.begin(), a3d.end(), c_flat.begin(), + [&](auto const& a3d_row) { + return std::transform_reduce( + a3d_row.base(), a3d_row.base() + a3d_row.elements().size(), + b2d.base(), 0.0 + ); + } + ); + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } +#if defined(__cpp_lib_generator) && (__cpp_lib_generator >= 202207L) && !defined(_MSC_VER) + { + sasa + watch _("chris transform transform_reduce elements"); + multi::array c_flat(em); + + std::transform( + a3d.begin(), a3d.end(), c_flat.begin(), + [&](auto const& a3d_row) { + return std::transform_reduce( + co_celements(a3d_row).begin(), co_celements(a3d_row).end(), + co_celements(b2d).begin(), 0.0 + ); + } + ); + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } +#endif + +#if defined(HAS_STD_EXECUTION) +#if defined(__cpp_lib_execution) + { + watch _("chris transform(par) transform_reduce"); + multi::array c_flat(em); + + std::transform( + std::execution::par, + a3d.begin(), a3d.end(), c_flat.begin(), + [&](auto const& a3d_row) { + return std::transform_reduce( + std::execution::par, + a3d_row.base(), a3d_row.base() + a3d_row.elements().size(), + b2d.base(), 0.0 + ); + } + ); + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } +#endif +#endif + { + watch _("chris accumulate"); + + auto const c_flat = std::accumulate( + b2d.elements().extension().begin(), b2d.elements().extension().end(), + multi::array(em, 0.0), + [&](auto&& acc, auto const ij) { + for(auto const k : acc.extension()) { // NOLINT(altera-unroll-loops) + acc[k] += a3d[k].base()[ij] * b2d.base()[ij]; + } + return std::forward(acc); + } + ); + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } + + { + watch _("chris accumulate move"); + + auto const c_flat = std::accumulate( + b2d.elements().extension().begin(), b2d.elements().extension().end(), + multi::array(em, 0.0), + [&](auto&& acc, auto const ij) { + std::transform( + acc.begin(), acc.end(), a3d.begin(), acc.begin(), + [&](auto&& acce, auto const& a3de) { return std::forward(acce) + a3de.base()[ij] * b2d.base()[ij]; } + ); + return std::forward(acc); + } + ); + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } + + { + watch _("chris transform reduce move transforms"); + + auto const c_flat = [&] { + return std::transform_reduce( + b2d.elements().extension().begin(), b2d.elements().extension().end(), + multi::array(em, 0.0), + [&](auto&& acc, auto const& rhs) { + std::transform( + acc.begin(), acc.end(), rhs.begin(), acc.begin(), + [&](auto&& acce, auto const& rhse) { return std::forward(acce) + rhse; } + ); + return std::forward(acc); + }, + [&](auto const ij) { + multi::array ret(em); + std::transform( + a3d.begin(), a3d.end(), ret.begin(), + [&](auto const& a3de) { return a3de.base()[ij] * b2d.base()[ij]; } + ); + return ret; + } + ); + }(); + _.stop(c_flat); + + BOOST_TEST( std::transform_reduce(c_gold.begin(), c_gold.end(), c_flat.begin(), 0.0, std::plus<>{}, [](auto const& alpha, auto const& omega) { return std::abs(alpha - omega); }) < 1.0e-5 ); + } + } +#endif + return boost::report_errors(); +} diff --git a/examples/save.cpp b/examples/save.cpp index eabb664e3..567d535a2 100644 --- a/examples/save.cpp +++ b/examples/save.cpp @@ -1,68 +1,78 @@ -#ifdef COMPILATION_INSTRUCTIONS -$CXX $0 -o $0x -lboost_serialization -lstdc++fs &&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020 -#include +// Copyright 2020-2024 Alfredo A. Correa + +#include #include #include -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include "../../multi/array.hpp" -#include +#include -enum format {xml, txt, bin}; +enum format { xml, + txt, + bin }; namespace barch = boost::archive; namespace bs11n = boost::serialization; -#define UNSWITCH __builtin_unreachable(); +#define BOOST_MULTI_UNSWITCH __builtin_unreachable template -void save(Array const& a, std::string name, format f){ +void save(Array const& a, std::string const& name, format f) { std::ofstream ofs(name); - *[&]()->std::unique_ptr{switch(f){ - case xml: return std::make_unique(ofs); - case txt: return std::make_unique(ofs); + *[&]() -> std::unique_ptr { + switch(f) { + case xml: return std::make_unique(ofs); + case txt: return std::make_unique(ofs); case bin: return std::make_unique(ofs); - }UNSWITCH;}() << bs11n::make_nvp("root", a); + } + BOOST_MULTI_UNSWITCH(); + }() << bs11n::make_nvp("root", a); assert(ofs); } template -void save(Array const& a, std::experimental::filesystem::path p){ - if(p.extension()==".xml") return save(a, p.string(), xml); - else if(p.extension()==".txt") return save(a, p.string(), txt); - else return save(a, p.string(), bin); +void save(Array const& a, std::experimental::filesystem::path p) { + if(p.extension() == ".xml") + return save(a, p.string(), xml); + else if(p.extension() == ".txt") + return save(a, p.string(), txt); + else + return save(a, p.string(), bin); } template -void load(Array& a, std::string name, format f){ +void load(Array& a, std::string const& name, format f) { std::ifstream ifs(name); - *[&]()->std::unique_ptr{switch(f){ + *[&]() -> std::unique_ptr {switch(f){ case xml: return std::make_unique(ifs); case txt: return std::make_unique(ifs); case bin: return std::make_unique(ifs); - }UNSWITCH;}() >> bs11n::make_nvp("root", a); + }BOOST_MULTI_UNSWITCH(); }() >> bs11n::make_nvp("root", a); assert(ifs); } template -void save_xml(Array const& a, std::string name){ +void save_xml(Array const& a, std::string const& name) { std::ofstream ofs(name); barch::xml_oarchive(ofs) << bs11n::make_nvp("root", a); } namespace multi = boost::multi; -int main(){ - multi::array const arrD2d = {{1., 2., 3.}, {4.,5.,6.}, {7.,8.,9.}}; +int main() { + multi::array const arrD2d = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + {7.0, 8.0, 9.0}, + }; save(arrD2d, "arrD2d.xml"); multi::array arrD2d_copy; @@ -70,3 +80,4 @@ int main(){ assert(arrD2d_copy == arrD2d); } +#undef BOOST_MULTO_SWITCH diff --git a/examples/serialization.cpp b/examples/serialization.cpp index 1e61c40ea..819ba8b69 100644 --- a/examples/serialization.cpp +++ b/examples/serialization.cpp @@ -1,339 +1,416 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4-*- -$CXX $0 -o $0x -lboost_unit_test_framework -lstdc++fs -lboost_serialization -lboost_iostreams&&$0x&&rm $0x;exit +#if 0 && defined(COMPILATION) +${CXX:-c++} $0 -o $0x -I../include -lstdc++fs -lboost_serialization -lboost_iostreams&& $0x&& rm $0x; +exit #endif -// © Alfredo Correa 2018-2020 - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi serialization" -#define BOOST_TEST_DYN_LINK -#include - -//#include "../adaptors/serialization/xml_archive.hpp" -#include -#include - -#include -#include -#include -#include -#include -#include -#include +// Copyright 2018-2025 Alfredo A. Correa #include -//#include "../adaptors/cuda.hpp" - -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include -#include +#include +#include +#include +#include +#include -#include -#include -#include +namespace multi = boost::multi; -#include +namespace fs = std::filesystem; -#include +struct watch : private std::chrono::high_resolution_clock { + std::string name_; + time_point start_ = std::chrono::high_resolution_clock::now(); + mutable bool engaged = true; -namespace multi = boost::multi; -namespace fs = std::experimental::filesystem; + watch() = default; + watch(std::string_view name) : name_{name} {} -struct watch : private std::chrono::high_resolution_clock{ - std::string name_; - time_point start_; - mutable bool engaged = true; - watch(std::string name = "") : name_{name}, start_{now()}{} - auto operator*() const{engaged = false; return std::chrono::duration(now() - start_).count();} - ~watch(){ - if(engaged){ + auto operator*() const { + engaged = false; + return std::chrono::duration(now() - start_).count(); + } + auto operator=(watch const&) = delete; + ~watch() { + if(engaged) { auto count = operator*(); - std::cerr<< name_ <<": "<< count <<" sec"< d2D({10, 10}); - std::mt19937 eng{std::random_device{}()}; - auto gen = [&](){return std::uniform_real_distribution<>{}(eng);}; - std::for_each(begin(d2D), end(d2D), [&](auto&& r){std::generate(begin(r), end(r), gen);}); - auto const name = "serialization-static-small.xml"; - { - std::ofstream ofs{name}; assert(ofs); - boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); - } - { - std::ifstream ifs{name}; assert(ifs); decltype(d2D) d2D_copy(extensions(d2D), 99.); - boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_copy); - BOOST_REQUIRE( d2D_copy == d2D ); - } - std::cout<< fs::file_size(name) <<'\n'; - fs::remove(name); +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ - { - std::ostringstream oss; - { - boost::archive::text_oarchive xoa{oss}; +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) - std::vector v = {1, 2, 3}; - // std::for_each(v.begin(), v.end(), [&xoa](auto const& e){xoa << e;}); - std::accumulate(v.begin(), v.end(), &xoa, [](boost::archive::text_oarchive* x, int e) {return &(*x << BOOST_SERIALIZATION_NVP(e));}); - } - std::cout << oss.str() << std::endl; + BOOST_AUTO_TEST_CASE(print_xml) { + multi::array const A{ + {"w", "x"}, + {"y", "z"}, + }; + boost::archive::xml_oarchive(std::cout, boost::archive::no_header) + << boost::make_nvp("A", A()); } -} -BOOST_AUTO_TEST_CASE(multi_serialization_small_xml){ - multi::array d2D({10, 10}); - std::mt19937 e{std::random_device{}()}; -// auto g = std::bind(std::uniform_real_distribution<>{}, e);// - auto g = [&](){return std::uniform_real_distribution<>{}(e);}; - std::for_each(begin(d2D), end(d2D), [&](auto&& r){std::generate(begin(r), end(r), g);}); - auto const name = "serialization-small.xml"; - { - std::ofstream ofs{name}; assert(ofs); - boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); - } - { - std::ifstream ifs{name}; assert(ifs); decltype(d2D) d2D_copy(extensions(d2D)); - boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_copy); - BOOST_REQUIRE( d2D_copy == d2D ); - } - { - std::ofstream ofs{"serialization-small-part.xml"}; assert(ofs); - auto&& a = d2D({0, 5}, {0, 5}); - boost::archive::xml_oarchive{ofs} << boost::serialization::make_nvp("d2D_part", a);//BOOST_SERIALIZATION_NVP(d2D); - fs::remove("serialization-small-part.xml"); + BOOST_AUTO_TEST_CASE(print_xml) { + multi::array const A{ + {"w", "x"}, + {"y", "z"}, + }; + auto&& AA = A(); + boost::archive::xml_oarchive(std::cout, boost::archive::no_header) + << boost::make_nvp("A", AA); } - std::cout<< fs::file_size(name) <<'\n'; - fs::remove(name); -} + BOOST_AUTO_TEST_CASE(multi_serialization_static_small_xml) { + multi::static_array d2D({10, 10}); -BOOST_AUTO_TEST_CASE(multi_serialization_static_large_xml){ - watch w("static_large_xml"); - multi::static_array d2D({1000, 1000}); - auto gen = [e=std::mt19937{std::random_device{}()}]() mutable{return std::uniform_real_distribution<>{}(e);}; - std::for_each(begin(d2D), end(d2D), [&](auto&& r){std::generate(begin(r), end(r), gen);}); - auto const name = "serialization-static-large.xml"; - { - std::ofstream ofs{name}; assert(ofs); - boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); - } - { - std::ifstream ifs{name}; assert(ifs); decltype(d2D) d2D_copy(extensions(d2D)); - boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_copy); - BOOST_REQUIRE( d2D_copy == d2D ); - } - std::cout<< fs::file_size(name) <<'\n'; - fs::remove(name); -} + std::mt19937_64 eng(std::random_device{}()); -BOOST_AUTO_TEST_CASE(multi_serialization_static_small){ - { - multi::static_array d0D{12.0}; - std::ofstream ofs{"serialization-static_0D.xml"}; assert(ofs); - boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d0D); - fs::remove("serialization-static_0D.xml"); - } - { - multi::array d2D = { - {150.0, 16.0, 17.0, 18.0, 19.0}, - { 5.0, 5.0, 5.0, 5.0, 5.0}, - {100.0, 11.0, 12.0, 13.0, 14.0}, - { 50.0, 6.0, 7.0, 8.0, 9.0}, + auto gen = [&]() { + return std::uniform_real_distribution<>{}(eng); }; - auto gen = [d = std::uniform_real_distribution{-1, 1}, e = std::mt19937{std::random_device{}()}]() mutable{return d(e);}; - std::for_each( - begin(d2D), end(d2D), - [&](auto&& r){std::generate(begin(r), end(r), gen);} - ); - auto name = "serialization-small-double2D.xml"; - [&, _ = watch("xml write double")]{ - std::ofstream ofs{"serialization-small-double2D.xml"}; assert(ofs); + + std::for_each(begin(d2D), end(d2D), [&](auto&& r) { std::generate(begin(r), end(r), gen); }); + std::string const filename = "serialization-static-small.xml"; + { + std::ofstream ofs{filename}; + assert(ofs); boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); - }(); - std::cerr<<"size "<< double(fs::file_size(name))/1e6 <<"MB\n"; - fs::remove("serialization-small-double2D.xml"); + } + { + std::ifstream ifs{filename}; + assert(ifs); + decltype(d2D) d2D_copy(extensions(d2D), 99.0); + boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_copy); + BOOST_TEST( d2D_copy == d2D ); + } + std::cout << fs::file_size(filename) << '\n'; + fs::remove(filename); + + { + std::ostringstream oss; + { + boost::archive::text_oarchive xoa{oss}; + + std::vector v = {1, 2, 3}; + std::for_each(v.begin(), v.end(), [&xoa](auto const& e) { xoa << e; }); + // std::accumulate(v.begin(), v.end(), &xoa, [](boost::archive::text_oarchive* x, int e) {return &(*x << BOOST_SERIALIZATION_NVP(e));}); + } + std::cout << oss.str() << std::endl; + } } - { - multi::array d2D = { - {150.0, 16.0, 17.0, 18.0, 19.0}, - { 5.0, 5.0, 5.0, 5.0, 5.0}, - {100.0, 11.0, 12.0, 13.0, 14.0}, - { 50.0, 6.0, 7.0, 8.0, 9.0}, + + BOOST_AUTO_TEST_CASE(multi_serialization_small_xml) { + multi::array d2D({10, 10}); + std::mt19937_64 e(std::random_device{}()); + + // auto g = std::bind(std::uniform_real_distribution<>{}, e);// + auto g = [&]() { + return std::uniform_real_distribution<>{}(e); }; - d2D.reextent({2000, 2000}); - auto gen = [d = std::uniform_real_distribution{-1, 1}, e = std::mt19937{std::random_device{}()}]() mutable { return d(e); }; - std::for_each( - begin(d2D), end(d2D), - [&](auto&& r) { std::generate(begin(r), end(r), gen); } - ); - [&, _ = watch("xml write double")] { - std::ofstream ofs{"serialization-double.xml"}; + + std::for_each(begin(d2D), end(d2D), [&](auto&& row) { std::generate(begin(row), end(row), g); }); + std::string const filename = "serialization-small.xml"; + { + std::ofstream ofs{filename}; assert(ofs); boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); - }(); - std::cerr << "size " << double(fs::file_size("serialization-double.xml")) / 1e6 << "MB\n"; - fs::remove("serialization-double.xml"); + } + { + std::ifstream ifs{filename}; + assert(ifs); + decltype(d2D) d2D_copy(extensions(d2D)); + boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_copy); + BOOST_TEST( d2D_copy == d2D ); + } + { + std::ofstream ofs{"serialization-small-part.xml"}; + assert(ofs); + auto&& a = d2D({0, 5}, {0, 5}); + boost::archive::xml_oarchive{ofs} << boost::serialization::make_nvp("d2D_part", a); // BOOST_SERIALIZATION_NVP(d2D); + fs::remove("serialization-small-part.xml"); + } + std::cout << fs::file_size(filename) << '\n'; + fs::remove(filename); } - using complex = std::complex; - - auto const d2D = []{multi::array _({10000, 1000}); - auto gen = [d = std::uniform_real_distribution{-1, 1}, e = std::mt19937{std::random_device{}()}]() mutable{return std::complex{d(e), d(e)};}; - std::for_each(begin(_), end(_), [&](auto&& r){std::generate(begin(r), end(r), gen);}); return _; - }(); - auto size = sizeof(double)*d2D.num_elements(); - using std::cerr; - std::cout<<"data size (in memory) "<< size < d2D_cpy; - auto count_load = [&, w=watch("binary load")]{ - std::ifstream ifs{file}; assert(ifs); - boost::archive::binary_iarchive{ifs} >> d2D_cpy; - return *w; - }(); - std::cerr<<"load speed "<< double(file_size(file))/1e6/count_load <<"MB/s\n"; - BOOST_REQUIRE( d2D == d2D_cpy ); - fs::remove(file); - } - { - using std::cout; - fs::path file{"serialization.xml"}; - cout<< file << std::endl; - auto count = [&, w = watch("xml write base64")]{ - std::ofstream ofs{file}; assert(ofs); + BOOST_AUTO_TEST_CASE(multi_serialization_static_large_xml) { + + multi::static_array d2D({1000, 1000}); + + auto gen = [e = std::mt19937_64(std::random_device{}())]() mutable { + return std::uniform_real_distribution<>{}(e); + }; + std::for_each(begin(d2D), end(d2D), [&](auto&& row) { std::generate(begin(row), end(row), gen); }); + + watch w("static_large_xml"); + + std::string const filename = "serialization-static-large.xml"; + { + std::ofstream ofs{filename}; + assert(ofs); boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); - return *w; - }(); - cout<<"data size "<< double(size)/1e6 << "MB\n"; - cout<<"file size "<< double(file_size(file))/1e6 <<"MB\n"; - cout<<"save speed "<< double(size)/1e6/count <<"MB/s"<< std::endl; - multi::array d2D_cpy; - auto count2 = [&, w = watch("xml load base64")]{ - std::ifstream ifs{file}; assert(ifs); - boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_cpy); - return *w; - }(); - cout<<"load speed "<< double(size)/1e6/count2 <<"MB/s"<< std::endl; - BOOST_REQUIRE( d2D_cpy == d2D ); - fs::remove(file); - } - return; -#if 0 - { - multi::cuda::managed::array cud2D({2000, 2000}); - [&, _=watch("cuda binary write")]{ - std::ofstream ofs{"serialization.bin"}; assert(ofs); - boost::archive::binary_oarchive{ofs} << cud2D; - }(); - std::cerr<<"size "<< (fs::file_size("serialization.bin")/1e6) <<"MB\n"; - } -#endif - { - [&, _ = watch("text write")]{ - std::ofstream ofs{"serialization.txt"}; assert(ofs); - boost::archive::text_oarchive{ofs} << d2D; - }(); - std::cerr<<"size "<< double(fs::file_size("serialization.txt"))/1e6 <<"MB\n"; - fs::remove("serialization.txt"); - } - { - multi::array d2D_copy; //(extensions(d2D), 9999.0); - [&, _ = watch("text read")]{ - std::ifstream ifs{"serialization.txt"}; assert(ifs); - boost::archive::text_iarchive{ifs} >> d2D_copy; - }(); - BOOST_REQUIRE( d2D_copy == d2D ); - fs::remove("serialization.txt"); - } - { - multi::array d2D_copy; //(extensions(d2D), 9999.0); - [&, _=watch("binary read")]{ - std::ifstream ifs{"serialization.bin"}; assert(ifs); - boost::archive::binary_iarchive{ifs} >> d2D_copy; - }(); - BOOST_REQUIRE( d2D_copy == d2D ); - fs::remove("serialization.bin"); - } - { - [&, _=watch("binary compressed write")]{ - std::ofstream ofs{"serialization_compressed.bin.gz"}; - { - boost::iostreams::filtering_stream f; - f.push(boost::iostreams::gzip_compressor()); - f.push(ofs); - boost::archive::binary_oarchive{f} << d2D; - } - }(); - std::cerr<<"size "<< double(fs::file_size("serialization.bin.gz"))/1e6 <<"MB\n"; - fs::remove("serialization.bin.gz"); - } - { - [&, _ = watch("compressed xml write")]{ - std::ofstream ofs{"serialization.xml.gz"}; assert(ofs); - { - boost::iostreams::filtering_stream f; - f.push(boost::iostreams::gzip_compressor()); - f.push(ofs); - boost::archive::xml_oarchive{f} << BOOST_SERIALIZATION_NVP(d2D); - } - }(); - std::cerr<<"size "<< double(fs::file_size("serialization.xml.gz"))/1e6 <<"MB\n"; - fs::remove("serialization.xml.gz"); - } - { - multi::array d2D_copy;//(extensions(d2D), 9999.); - [&, _ = watch("xml read")]{ - std::ifstream ifs{"serialization.xml"}; assert(ifs); + } + { + std::ifstream ifs{filename}; + assert(ifs); + decltype(d2D) d2D_copy(extensions(d2D)); boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_copy); - }(); - BOOST_REQUIRE( d2D_copy == d2D ); - fs::remove("serialization.xml"); + BOOST_TEST( d2D_copy == d2D ); + } + std::cout << fs::file_size(filename) << '\n'; + fs::remove(filename); } -} -BOOST_AUTO_TEST_CASE(test_utility_serialization_2d){ - double carr[3][10] = { - {0, 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}, - }; - multi::array_ref marr(&carr[0][0], {3, 10}); - boost::multi_array_ref Marr(&carr[0][0], boost::extents[3][10]); - - namespace arxiv = boost::archive; - { - std::ofstream ofs{"utility_serialization_marr.xml"}; assert(ofs); - arxiv::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(marr); - fs::remove("utility_serialization_marr.xml"); - } - { - std::ofstream ofs{"utility_serialization_marr_as_value.xml"}; assert(ofs); - multi::array const& marr_value = decay(marr);//static_cast const&>(marr); - BOOST_REQUIRE( marr_value.data_elements() == marr.data_elements() ); - arxiv::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(marr_value); - fs::remove("utility_serialization_marr_as_value.xml"); - } - { - std::ofstream ofs{"utility_serialization_carr.xml"}; assert(ofs); - arxiv::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(carr); - fs::remove("utility_serialization_carr.xml"); + BOOST_AUTO_TEST_CASE(multi_serialization_static_small) { + { + multi::static_array d0D{12.0}; + + std::ofstream ofs{"serialization-static_0D.xml"}; + assert(ofs); + + boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d0D); + fs::remove("serialization-static_0D.xml"); + } + { + multi::array d2D = { + {150.0, 16.0, 17.0, 18.0, 19.0}, + { 5.0, 5.0, 5.0, 5.0, 5.0}, + {100.0, 11.0, 12.0, 13.0, 14.0}, + { 50.0, 6.0, 7.0, 8.0, 9.0}, + }; + auto gen = [d = std::uniform_real_distribution{-1, 1}, e = std::mt19937{std::random_device{}()}]() mutable { + return d(e); + }; + std::for_each( + begin(d2D), end(d2D), + [&](auto&& row) { std::generate(begin(row), end(row), gen); } + ); + std::string const filename = "serialization-small-double2D.xml"; + [&, _ = watch("xml write double")] { + std::ofstream ofs{filename}; + assert(ofs); + boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); + }(); + std::cerr << "size " << double(fs::file_size(filename)) / 1e6 << "MB\n"; + fs::remove(filename); + } + { + multi::array d2D = { + {150.0, 16.0, 17.0, 18.0, 19.0}, + { 5.0, 5.0, 5.0, 5.0, 5.0}, + {100.0, 11.0, 12.0, 13.0, 14.0}, + { 50.0, 6.0, 7.0, 8.0, 9.0}, + }; + d2D.reextent({2000, 2000}); + auto gen = [d = std::uniform_real_distribution{-1, 1}, e = std::mt19937{std::random_device{}()}]() mutable { + return d(e); + }; + std::for_each( + begin(d2D), end(d2D), + [&](auto&& r) { std::generate(begin(r), end(r), gen); } + ); + [&, _ = watch("xml write double")] { + std::ofstream ofs{"serialization-double.xml"}; + assert(ofs); + boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); + }(); + std::cerr << "size " << double(fs::file_size("serialization-double.xml")) / 1e6 << "MB\n"; + fs::remove("serialization-double.xml"); + } + + using complex = std::complex; + + auto const d2D = [] { + multi::array _({10000, 1000}); + auto gen = [d = std::uniform_real_distribution{-1, 1}, e = std::mt19937{std::random_device{}()}]() mutable { + return std::complex{d(e), d(e)}; + }; + std::for_each(begin(_), end(_), [&](auto&& r) { std::generate(begin(r), end(r), gen); }); + return _; + }(); + auto size = sizeof(double) * d2D.num_elements(); + using std::cerr; + std::cout << "data size (in memory) " << size << std::endl; + { + fs::path file{"serialization.bin"}; + auto count = [&, w = watch("binary write")] { + std::ofstream ofs{file}; + assert(ofs); + boost::archive::binary_oarchive{ofs} << d2D; + return *w; + }(); + cerr << "size " << double(file_size(file)) / 1e6 << "MB\n"; + cerr << "speed " << double(size) / 1e6 / count << "MB/s\n"; + std::decay_t d2D_cpy; + + auto count_load = [&, w = watch("binary load")] { + std::ifstream ifs{file}; + assert(ifs); + boost::archive::binary_iarchive{ifs} >> d2D_cpy; + return *w; + }(); + + std::cerr << "load speed " << double(file_size(file)) / 1e6 / count_load << "MB/s\n"; + BOOST_TEST( d2D == d2D_cpy ); + fs::remove(file); + } + // { + // using std::cout; + // fs::path file{"serialization.xml"}; + // cout << file << std::endl; + // auto count = [&, w = watch("xml write base64")] { + // std::ofstream ofs{file}; + // assert(ofs); + // boost::archive::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(d2D); + // return *w; + // }(); + // cout << "data size " << double(size) / 1e6 << "MB\n"; + // cout << "file size " << double(file_size(file)) / 1e6 << "MB\n"; + // cout << "save speed " << double(size) / 1e6 / count << "MB/s" << std::endl; + // multi::array d2D_cpy; + + // auto count2 = [&, w = watch("xml load base64")] { + // std::ifstream ifs{file}; + // assert(ifs); + // boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_cpy); + // return *w; + // }(); + + // cout << "load speed " << double(size) / 1e6 / count2 << "MB/s" << std::endl; + // BOOST_TEST( d2D_cpy == d2D ); + // fs::remove(file); + // } + // #if 0 + // { + // multi::cuda::managed::array cud2D({2000, 2000}); + // [&, _=watch("cuda binary write")]{ + // std::ofstream ofs{"serialization.bin"}; assert(ofs); + // boost::archive::binary_oarchive{ofs} << cud2D; + // }(); + // std::cerr<<"size "<< (fs::file_size("serialization.bin")/1e6) <<"MB\n"; + // } + // #endif + // { + // [&, _ = watch("text write")] { + // std::ofstream ofs("serialization.txt"); + // assert(ofs); + // boost::archive::text_oarchive{ofs} << d2D; + // assert(ofs); + // }(); + // std::cerr << "size " << double(fs::file_size("serialization.txt")) / 1e6 << "MB\n"; + // fs::remove("serialization.txt"); + // } + // { + // multi::array d2D_copy; //(extensions(d2D), 9999.0); + // [&, _ = watch("text read")] { + // std::ifstream ifs("serialization.txt"); + // assert(ifs); + // boost::archive::text_iarchive{ifs} >> d2D_copy; + // assert(ifs); + // }(); + // BOOST_TEST( d2D_copy == d2D ); + // fs::remove("serialization.txt"); + // } + // { + // multi::array d2D_copy; //(extensions(d2D), 9999.0); + // [&, _ = watch("binary read")] { + // std::ifstream ifs{"serialization.bin"}; + // assert(ifs); + // boost::archive::binary_iarchive{ifs} >> d2D_copy; + // }(); + // BOOST_TEST( d2D_copy == d2D ); + // fs::remove("serialization.bin"); + // } + // { + // [&, _ = watch("binary compressed write")] { + // std::ofstream ofs{"serialization_compressed.bin.gz"}; + // { + // boost::iostreams::filtering_stream f; + // f.push(boost::iostreams::gzip_compressor()); + // f.push(ofs); + // boost::archive::binary_oarchive{f} << d2D; + // } + // }(); + // std::cerr << "size " << double(fs::file_size("serialization.bin.gz")) / 1e6 << "MB\n"; + // fs::remove("serialization.bin.gz"); + // } + { + [&, _ = watch("compressed xml write")] { + std::ofstream ofs{"serialization.xml.gz"}; + assert(ofs); + { + boost::iostreams::filtering_stream f; + f.push(boost::iostreams::gzip_compressor()); + f.push(ofs); + boost::archive::xml_oarchive{f} << BOOST_SERIALIZATION_NVP(d2D); + } + }(); + std::cerr << "size " << double(fs::file_size("serialization.xml.gz")) / 1e6 << "MB\n"; + fs::remove("serialization.xml.gz"); + } + // { + // multi::array d2D_copy; //(extensions(d2D), 9999.); + // [&, _ = watch("xml read")] { + // std::ifstream ifs{"serialization.xml"}; + // assert(ifs); + // boost::archive::xml_iarchive{ifs} >> BOOST_SERIALIZATION_NVP(d2D_copy); + // }(); + // BOOST_TEST( d2D_copy == d2D ); + // fs::remove("serialization.xml"); + // } } - { -// std::ofstream ofs{"utility_serialization_Marr.xml"}; assert(ofs); -// arxiv::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(Marr); + + BOOST_AUTO_TEST_CASE(test_utility_serialization_2d) { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types + double carr[3][10] = { + { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}, + {10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0}, + {20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0}, + }; + multi::array_ref marr(&carr[0][0], {3, 10}); + boost::multi_array_ref Marr(&carr[0][0], boost::extents[3][10]); + + namespace arxiv = boost::archive; + { + std::ofstream ofs{"utility_serialization_marr.xml"}; + assert(ofs); + arxiv::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(marr); + fs::remove("utility_serialization_marr.xml"); + } + { + std::ofstream ofs{"utility_serialization_marr_as_value.xml"}; + assert(ofs); + multi::array const& marr_value = decay(marr); // static_cast const&>(marr); + BOOST_TEST( marr_value.data_elements() == marr.data_elements() ); + arxiv::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(marr_value); + fs::remove("utility_serialization_marr_as_value.xml"); + } + { + std::ofstream ofs{"utility_serialization_carr.xml"}; + assert(ofs); + arxiv::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(carr); + fs::remove("utility_serialization_carr.xml"); + } + { + // std::ofstream ofs{"utility_serialization_Marr.xml"}; assert(ofs); + // arxiv::xml_oarchive{ofs} << BOOST_SERIALIZATION_NVP(Marr); + } } -} + return boost::report_errors(); +} diff --git a/examples/serialization2.cpp b/examples/serialization2.cpp index e006d2b8e..2ab7ed3ea 100644 --- a/examples/serialization2.cpp +++ b/examples/serialization2.cpp @@ -1,8 +1,9 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2021 Alfredo A. Correa +// c++ serialization2.cpp -I../include -lboost_serialization -lboost_unit_test_framework +// Copyright 2019-2024 Alfredo A. Correa #define BOOST_TEST_MODULE "C++ Unit Tests for Multi allocators" -#include +#define BOOST_TEST_DYN_LINK +// #include #include "multi/array.hpp" @@ -39,7 +40,7 @@ using BIArchive = boost::archive::binary_iarchive; using boost::serialization::make_nvp; -// using boost::serialization::make_array; +// using boost::serialization::make_array; #endif #include @@ -84,35 +85,35 @@ BOOST_AUTO_TEST_CASE(extensions_serialization) { { XOArchive xoa{ss}; xoa<< make_nvp("x", x); - // xoa<< multi::archive_traits::make_nvp("x", x); - // xoa<< AR_NVP(x); - // xoa<< CEREAL_NVP(x); - // xoa<< x ; + // xoa<< multi::archive_traits::make_nvp("x", x); + // xoa<< AR_NVP(x); + // xoa<< CEREAL_NVP(x); + // xoa<< x ; } { multi::extensions_t<2> y; { XIArchive xia{ss}; xia>> make_nvp("x", y); - // xia>> multi::archive_traits::make_nvp("x", y); - // xia>> cereal::make_nvp("x", y); - // xia>> y ; + // xia>> multi::archive_traits::make_nvp("x", y); + // xia>> cereal::make_nvp("x", y); + // xia>> y ; } BOOST_REQUIRE(x == y); } } BOOST_AUTO_TEST_CASE(carray_serialization) { - double const A[3][3] = {{0., 1., 2.}, {3., 4., 5.}, {6., 7., 8.}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types + double const A[3][3] = {{0.0, 1.0, 2.0}, {3.0, 4.0, 5.0}, {6.0, 7.0, 8.0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types std::stringstream ss; { { XOArchive xoa{ss}; xoa<< make_nvp("A", A); - // xoa<< AR_NVP(A); - // xoa<< CEREAL_NVP(A); - // xoa<< A ; - // xoa<< multi::archive_traits::make_nvp("A", A); + // xoa<< AR_NVP(A); + // xoa<< CEREAL_NVP(A); + // xoa<< A ; + // xoa<< multi::archive_traits::make_nvp("A", A); } std::ofstream ofs{"serialization_A.xml"}; ofs<< ss.str(); @@ -121,39 +122,39 @@ BOOST_AUTO_TEST_CASE(carray_serialization) { double B[3][3]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types XIArchive xia{ss}; xia>> make_nvp("A", B); - // xia>> cereal::make_nvp("A", B); - // xia>> B ; - // xia>> multi::archive_traits::make_nvp("A", B); - BOOST_REQUIRE( B[1][2] == 5. ); // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult) is it? + // xia>> cereal::make_nvp("A", B); + // xia>> B ; + // xia>> multi::archive_traits::make_nvp("A", B); + BOOST_REQUIRE( B[1][2] == 5.0 ); // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult) is it? BOOST_REQUIRE( A[1][2] == B[1][2] ); // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult) is it? } } BOOST_AUTO_TEST_CASE(array_serialization) { - multi::array arr({10, 10}, 0.); + multi::array arr({10, 10}, 0.0); BOOST_REQUIRE(( arr.extension() == boost::multi::index_range{0, 10} )); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.); + std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.0); std::stringstream ss{}; { XOArchive xoa{ss}; xoa<< make_nvp("arr", arr); - // xoa<< AR_NVP(arr); - // xoa<< CEREAL_NVP(arr); - // xoa<< arr ; - // xoa<< CEREAL_NVP(arr); - // xoa<< multi::archive_traits::make_nvp("arr", arr); + // xoa<< AR_NVP(arr); + // xoa<< CEREAL_NVP(arr); + // xoa<< arr ; + // xoa<< CEREAL_NVP(arr); + // xoa<< multi::archive_traits::make_nvp("arr", arr); } { multi::array arr2; { XIArchive xia{ss}; xia>> make_nvp("arr", arr2); - // xia>> cereal::make_nvp("arr", arr2); - // xia>> arr2 ; - // xia>> multi::archive_traits::make_nvp("arr", arr2); + // xia>> cereal::make_nvp("arr", arr2); + // xia>> arr2 ; + // xia>> multi::archive_traits::make_nvp("arr", arr2); } BOOST_REQUIRE( extensions(arr2) == extensions(arr) ); BOOST_REQUIRE( arr2 == arr ); @@ -162,9 +163,9 @@ BOOST_AUTO_TEST_CASE(array_serialization) { BOOST_AUTO_TEST_CASE(array_serialization_string) { multi::array arr({10, 10}); - auto const x = extensions(arr); - for(auto i : std::get<0>(x) ) { - for(auto j : std::get<1>(x) ) { + auto const [is, js] = extensions(arr); + for(auto i : is ) { + for(auto j : js ) { arr[i][j] = std::to_string(i) + std::to_string(j); } } @@ -173,20 +174,20 @@ BOOST_AUTO_TEST_CASE(array_serialization_string) { { XOArchive xoa{ss}; xoa<< make_nvp("arr", arr); - // xoa<< AR_NVP(arr) ; - // xoa<< BOOST_SERIALIZATION_NVP(arr) ; - // xoa<< CEREAL_NVP(arr) ; - // xoa<< arr ; - // xoa<< multi::archive_traits::make_nvp("arr", arr); + // xoa<< AR_NVP(arr) ; + // xoa<< BOOST_SERIALIZATION_NVP(arr) ; + // xoa<< CEREAL_NVP(arr) ; + // xoa<< arr ; + // xoa<< multi::archive_traits::make_nvp("arr", arr); } { multi::array arr2{}; { XIArchive xia{ss}; xia>> make_nvp("arr", arr2); - // xia>> cereal::make_nvp("arr", arr2); - // xia>> arr2 ; - // xia>> multi::archive_traits::make_nvp("arr", arr2); + // xia>> cereal::make_nvp("arr", arr2); + // xia>> arr2 ; + // xia>> multi::archive_traits::make_nvp("arr", arr2); } BOOST_REQUIRE( extensions(arr2) == extensions(arr) ); BOOST_REQUIRE( arr2 == arr ); @@ -195,10 +196,10 @@ BOOST_AUTO_TEST_CASE(array_serialization_string) { //#if not defined(__NVCC__) // some code contained here doesn't compile with nvcc 11.0,11.1 and 11.2 BOOST_AUTO_TEST_CASE(array_serialization_binary) { - multi::array arr({10, 10}, 0.); + multi::array arr({10, 10}, 0.0); BOOST_REQUIRE(( arr.extension() == boost::multi::index_range{0, 10} )); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.); + std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.0); std::stringstream ss{}; { @@ -229,14 +230,14 @@ BOOST_AUTO_TEST_CASE(array_serialization_string_binary) { { BOArchive boa{ss}; boa<< arr; - // boa<< multi::archive_traits::make_nvp("arr", arr); + // boa<< multi::archive_traits::make_nvp("arr", arr); } { multi::array arr2{}; { BIArchive bia{ss}; bia>> arr2; - // bia>> multi::archive_traits::make_nvp("arr", arr2); + // bia>> multi::archive_traits::make_nvp("arr", arr2); } BOOST_REQUIRE( extensions(arr2) == extensions(arr) ); BOOST_REQUIRE( arr2 == arr ); @@ -245,69 +246,69 @@ BOOST_AUTO_TEST_CASE(array_serialization_string_binary) { //#if not defined(__NVCC__) // some code contained here doesn't compile with nvcc 11.0,11.1 and 11.2 BOOST_AUTO_TEST_CASE(vector) { - std::vector v(100); std::iota(begin(v), end(v), 10.); + std::vector v(100); std::iota(begin(v), end(v), 10.0); std::stringstream ss; { XOArchive xoa{ss}; xoa<< make_nvp("v_data", multi::archive_traits::make_array(v.data(), v.size())); - // xoa<< make_nvp("v_data", make_array(v.data(), v.size())); - // xoa<< make_nvp("v_data", boost::serialization::make_array(v.data(), v.size())); + // xoa<< make_nvp("v_data", make_array(v.data(), v.size())); + // xoa<< make_nvp("v_data", boost::serialization::make_array(v.data(), v.size())); } { std::vector w(100); XIArchive xia{ss}; xia>> make_nvp("v_data", multi::archive_traits::make_array(w.data(), w.size())); - // xia>> make_nvp("v_data", make_array(w.data(), w.size())); - // xia>> make_nvp("v_data", boost::serialization::make_array(w.data(), w.size())); + // xia>> make_nvp("v_data", make_array(w.data(), w.size())); + // xia>> make_nvp("v_data", boost::serialization::make_array(w.data(), w.size())); BOOST_REQUIRE( v == w ); } } BOOST_AUTO_TEST_CASE(vector_binary) { - std::vector v(100); std::iota(begin(v), end(v), 10.); + std::vector v(100); std::iota(begin(v), end(v), 10.0); std::stringstream ss{}; { BOArchive xoa{ss}; xoa<< make_nvp("v_data", multi::archive_traits::make_array(v.data(), v.size())); - // xoa<< make_nvp("v_data", make_array(v.data(), v.size())); - // xoa<< make_nvp("v_data", boost::serialization::make_array(v.data(), v.size())); + // xoa<< make_nvp("v_data", make_array(v.data(), v.size())); + // xoa<< make_nvp("v_data", boost::serialization::make_array(v.data(), v.size())); } { std::vector w(100); BIArchive xia{ss}; xia>> make_nvp("v_data", multi::archive_traits::make_array(w.data(), w.size())); - // xia>> make_nvp("v_data", make_array(w.data(), w.size())); - // xia>> make_nvp("v_data", boost::serialization::make_array(w.data(), w.size())); + // xia>> make_nvp("v_data", make_array(w.data(), w.size())); + // xia>> make_nvp("v_data", boost::serialization::make_array(w.data(), w.size())); BOOST_REQUIRE( v == w ); } } BOOST_AUTO_TEST_CASE(array_serialization_3D) { - multi::array arr({10, 10, 10}, 0.); + multi::array arr({10, 10, 10}, 0.0); BOOST_REQUIRE(( arr.extension() == boost::multi::index_range{0, 10} )); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.); + std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.0); std::stringstream ss{}; { XOArchive xoa{ss}; xoa<< make_nvp("arr", arr); - // xoa<< AR_NVP(arr); - // xoa<< CEREAL_NVP(arr); - // xoa<< arr ; - // xoa<< multi::archive_traits::make_nvp("arr", arr); + // xoa<< AR_NVP(arr); + // xoa<< CEREAL_NVP(arr); + // xoa<< arr ; + // xoa<< multi::archive_traits::make_nvp("arr", arr); } { multi::array arr2{}; { XIArchive xia{ss}; xia>> make_nvp("arr", arr2); - // xia>> cereal::make_nvp("arr", arr2); - // xia>> arr2 ; - // xia>> multi::archive_traits::make_nvp("arr", arr2); + // xia>> cereal::make_nvp("arr", arr2); + // xia>> arr2 ; + // xia>> multi::archive_traits::make_nvp("arr", arr2); } BOOST_REQUIRE( extensions(arr2) == extensions(arr) ); BOOST_REQUIRE( arr2 == arr ); @@ -315,11 +316,11 @@ BOOST_AUTO_TEST_CASE(array_serialization_3D) { } BOOST_AUTO_TEST_CASE(array_serialization_3D_inplace) { - multi::array arr({10, 10, 10}, 0.); + multi::array arr({10, 10, 10}, 0.0); BOOST_REQUIRE(( arr.extension() == boost::multi::index_range{0, 10} )); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.); + std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.0); std::stringstream ss{}; XOArchive{ss}<< make_nvp("arr", arr); @@ -332,7 +333,7 @@ BOOST_AUTO_TEST_CASE(array_serialization_3D_inplace) { } BOOST_AUTO_TEST_CASE(array_serialization_2D_inplace_file) { - multi::array arr({2, 2}, 99.); + multi::array arr({2, 2}, 99.0); { std::ofstream ofs{"file.xml"}; @@ -347,13 +348,13 @@ BOOST_AUTO_TEST_CASE(array_serialization_2D_inplace_file) { BOOST_REQUIRE( arr2 == arr ); } -#if not defined(__NVCC__) // some code contained here doesn't compile with nvcc 11.0,11.1 and 11.2 +// #if not defined(__NVCC__) // some code contained here doesn't compile with nvcc 11.0,11.1 and 11.2 BOOST_AUTO_TEST_CASE(array_serialization_3D_part_binary_lvalue) { - multi::array arr({10, 10, 10}, 0.); + multi::array arr({10, 10, 10}, 0.0); BOOST_REQUIRE(( arr.extension() == boost::multi::index_range{0, 10} )); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.); + std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.0); std::stringstream ss{}; { @@ -373,11 +374,11 @@ BOOST_AUTO_TEST_CASE(array_serialization_3D_part_binary_lvalue) { } BOOST_AUTO_TEST_CASE(array_serialization_3D_part_xml_lvalue) { - multi::array arr({10, 10, 10}, 0.); + multi::array arr({10, 10, 10}, 0.0); BOOST_REQUIRE(( arr.extension() == boost::multi::index_range{0, 10} )); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.); + std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.0); std::stringstream ss{}; { @@ -397,11 +398,11 @@ BOOST_AUTO_TEST_CASE(array_serialization_3D_part_xml_lvalue) { } BOOST_AUTO_TEST_CASE(array_serialization_3D_part_binary) { - multi::array arr({10, 10, 10}, 0.); + multi::array arr({10, 10, 10}, 0.0); BOOST_REQUIRE(( arr.extension() == boost::multi::index_range{0, 10} )); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.); + std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.0); std::stringstream ss{}; { @@ -419,17 +420,22 @@ BOOST_AUTO_TEST_CASE(array_serialization_3D_part_binary) { } BOOST_AUTO_TEST_CASE(array_serialization_3D_part_xml) { - multi::array arr({10, 10, 10}, 0.); + multi::array arr({10, 10, 10}, 0.0); BOOST_REQUIRE(( arr.extension() == boost::multi::index_range{0, 10} )); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.); + std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 1000.0); std::stringstream ss{}; { XOArchive boa{ss}; boa<< multi::archive_traits::make_nvp("arr2", arr[2]); } + { + std::ofstream ofs("serialization_3D.xml"); + XOArchive boa(ofs); + boa<< multi::archive_traits::make_nvp("arr", arr()); + } { BOOST_REQUIRE( arr[3] != arr[2] ); { @@ -439,4 +445,4 @@ BOOST_AUTO_TEST_CASE(array_serialization_3D_part_xml) { BOOST_REQUIRE( arr[3] == arr[2] ); } } -#endif +// #endif diff --git a/include/boost/multi/adaptors/CMakeLists.txt b/include/boost/multi/adaptors/CMakeLists.txt new file mode 100644 index 000000000..e04c7221b --- /dev/null +++ b/include/boost/multi/adaptors/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) +find_package(MPI) + +if(DISABLE_MPI) + message(WARNING "MPI disabled, MPI adaptor will not be tested.") +else() + if(MPI_FOUND) + add_subdirectory(./mpi) + else() + message(WARNING "MPI not found, MPI adaptor will not be tested.") + endif() +endif() + +# add_subdirectory(./cufft) + diff --git a/include/boost/multi/adaptors/blas.hpp b/include/boost/multi/adaptors/blas.hpp new file mode 100644 index 000000000..48fd59386 --- /dev/null +++ b/include/boost/multi/adaptors/blas.hpp @@ -0,0 +1,25 @@ +// Copyright 2018-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_HPP +#pragma once + +#include // IWYU pragma: export + +#include +#include +#include +#include +#include +#include // IWYU pragma: export +#include +#include +#include +#include +#include +#include +#include + +#endif // BOOST_MULTI_ADAPTORS_BLAS_HPP diff --git a/include/boost/multi/adaptors/blas/CMakeLists.txt b/include/boost/multi/adaptors/blas/CMakeLists.txt new file mode 100644 index 000000000..3a2538d63 --- /dev/null +++ b/include/boost/multi/adaptors/blas/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.16) + +#project( +# boost-multi-adaptors-blas +# VERSION 0.1 +# LANGUAGES CXX +#) + +find_package(BLAS) + +if(BLAS_FOUND) + message("Some BLAS found: linker flags: ${BLAS_LINKER_FLAGS}, libs: ${BLAS_LIBRARIES}, libs95: ${BLAS95_LIBRARIES}") + foreach(lib ${BLAS_LIBRARIES}) + # https://cmake.org/cmake/help/latest/module/FindBLAS.html#blas-lapack-vendors + if(${lib} MATCHES "mkl") + message("Some BLAS found matches MKL") + add_definitions(-DBLAS_DOT_RETURNS_VOID) + target_compile_definitions(multi INTERFACE BLAS_DOT_RETURNS_VOID) + # add_definitions(-D_MULTI_USING_BLAS_MKL) + # in some systems with MKL, regular BLAS headers need to be found for it to work + SET(CMAKE_SKIP_BUILD_RPATH FALSE) + SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib64") + SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib64") + endif() + if(${lib} MATCHES "hpc_sdk") + message("Some BLAS found matches hpc_sdk (nvhpc)") # nvhpc may still use a different version of BLAS + add_definitions(-DBLAS_DOT_RETURNS_VOID) + target_compile_definitions(multi INTERFACE BLAS_DOT_RETURNS_VOID) + endif() + if(${lib} MATCHES "Accelerate") + message("Some BLAS found matches Accelerate (Apple) [beware of sdot and snrm2 bugs]") + # message(WARNING "Apple Accelerate BLAS is known to have bugs in single precission function `sdot` and `smrm2`, be careful: https://stackoverflow.com/a/77017238/225186, https://fortran-lang.discourse.group/t/how-many-blas-libraries-have-this-error/4454/23, https://forums.developer.apple.com/forums/thread/717757") + # add_definitions(-DBLAS_DOT_RETURNS_VOID) + target_compile_definitions(multi INTERFACE BLAS_DOT_RETURNS_VOID) + endif() + endforeach() +endif() + +if(BLAS_FOUND) + find_package(Boost NO_MODULE) # COMPONENTS boost) # headers unit_test_framework) + if(Boost_FOUND) + add_subdirectory(test) + else() + message(WARNING "Boost Unit Test Framework not found, BLAS-adaptor tests will not be compiled and run. If you want this feature, install Boost") + endif() +else() + message(WARNING + "BLAS not found, BLAS-adaptor tests will not be compiled and run. If you want this feature install BLAS, for example please run:" + "\n sudo apt install libblas-dev # in Ubuntu" + "\n sudo dnf install blas-devel # in Fedora") + message(WARNING "BLA_VENDOR was set to ${BLA_VENDOR}\n") +endif() diff --git a/include/boost/multi/adaptors/blas/README.md b/include/boost/multi/adaptors/blas/README.md new file mode 100644 index 000000000..d909f4805 --- /dev/null +++ b/include/boost/multi/adaptors/blas/README.md @@ -0,0 +1,398 @@ + +# Multi BLAS Adaptor + +_© Alfredo A. Correa, 2018-2025_ + +(documentation in progress) + +The BLAS Adaptor provides an interface for the BLAS and BLAS-like linear algebra libraries (cuBLAS and hipBLAS). +Although BLAS is not strictly a multidimensional array library, as it only works on 1D (vectors) and 2D arrays (matrices), it is an extremely popular numeric library. + +The adaptor library has a two-fold purpose: + +First, it allows the abstracting of the stride information and the conjugation/transposition in the BLAS calls, simplifying the interface enormously and making it consistent with their GPU counterparts, such as cuBLAS. +"View manipulators" automatically handle cases related to conjugation, transposition, and real and imaginary parts. + +Second, it provides a functional interface to the BLAS calls, which is easier to use than the C-style interface and plays well with STL algorithms that assume a functional programming style. + +This functions in this adaptor library strictly uses BLAS operations, the data is not processed outside the BLAS calls. + +## Contents +[[_TOC_]] + +## Interfaces + +In the _functional_ interface, most functions return special "views" rather than direct results. +The results are computed when converted to value types or assigned to other views. +Value types can be 2D (`multi::array`), 1D (`multi::array`) or 0D (`multi::array` or scalars `T`). +Views can be assigned to subarrays (e.g. `multi::subarray` or `multi::subarray`). + +In this interface, functions like `gemv` generates views that can be assigned to values or from which constructors can be called in the library without unnecessary copies or allocations. +Expressions such as `multi::blas::gemv(alpha, A, x)` produce a range object that can be used in larger expressions, such as construction and assignemnt. + +- Construction: +```cpp +multi::array const y = multi::blas::gemv(alpha, A, x); // same effect as multi::array y({A.size()}); multi::blas::gemv(alpha, A, x, 0.0, y); +``` +Among other advantages, the functional style gives the possibility of creating constant variables for results. + +Like other parts of the library, when using `auto` and the unary `operator+` help generating concrete values. + +```cpp +auto const y = +multi::blas::gemv(alpha, A, x); // y variable is deduced as multi::array and latter constructed from the gemv operation +``` + +- Assignment: +```cpp + multi::array y; // empty vector + y = multi::blas::gemv(alpha, A, x); // same as multi::blas::gemv(alpha, A, x, 0.0, y), y is resized if necessary +``` + +- Assignment (to subarray): +```cpp +multi::array Y; // empty vector +Y[0] = multi::blas::gemv(alpha, A, x); // same as multi::blas::gemv(alpha, A, x, 0.0, Y[0]), Y[0] can't be resized because it is a subarray must have the correct size, +``` + +- Compound-assign: +```cpp +multi::array y(A.size()); +y += multi::blas::gemv(alpha, A, x); // same as multi::blas::gemv(alpha, A, x, 1.0, y) +``` + +This interface plays well with the style of the STL algorithms. +For example, suppose we have a container of vectors, all of which need to be multiplied by a given array. + +```cpp +std::list > vs = ...; // using std::list to avoid confusion +std::list > ws = ...; +multi::array const A = ...; + +std::transform(vs.begin(), vs.end(), ws.begin(), [&A](auto const& v) {return multi::blas::gemv(1.0, A, v);}) +``` + +Although it shares some of the goals, this interface is independent of the [C++26 Linear Algebra Proposal](https://en.cppreference.com/w/cpp/numeric/linalg). +The main difference with other BLAS adaptors is that this library aims to offer a functional interface. + +## Numeric Arrays, Conjugation Real and Imaginary parts + +Just as with BLAS, the library supports element of real (`double` and `float`) and complex (`std::complex` and `std::complex`) types. +Other types that are semantically equivalent and binary-compatible (such as `thrust::complex`) also work directly. + +## GPU (cublas/hipblas) + +The library can be used with both CPU and GPU arrays, the interface is the same. +Calls to BLAS function on array with GPU pointer types or allocated with `cuda::allocator` or `cuda::universal_allocator` will use cuBLAS automatically. +(https://godbolt.org/z/xeTM3ncnc) + +## View manipulators + +These functions produce views (not copies) related to conjugation, and transposition. +These typically replace the 'T', 'C' and 'N' characted arguments of the BLAS calls in the C or Fortran interfaces. + +### `auto multi::blas::C(`_complex/real vector/matrix_`) -> `_complex/real vector/matrix view_ + +The conjugation operation is a unary operation that conjugates each element of the array, producing a view of the array that preserves the shape of the original array. + +### `multi::blas::T(`_complex/real vector/matrix_`) -> `_complex/real vector/matrix view_ + +The transposition operation is a unary operation that transposes an array, producing a view of the array that transposed the elements (and the shape) of the original array. + +### `multi::blas::N(`_complex/real vector/matrix_`) -> `_complex/real vector/matrix view_ + +This view returns the same array, implies no operations on the array; it is provided for completeness. + +### `multi::blas::H(`_complex/real vector/matrix_`) -> `_complex/real vector/matrix view_ + +```cpp +using complex = std::complex; +complex const I{0.0, 1.0}; + +multi::array B = { + {1.0 - 3.0*I, 6.0 + 2.0*I}, + {8.0 + 2.0*I, 2.0 + 4.0*I}, + {2.0 - 1.0*I, 1.0 + 1.0*I} +}; + +namespace blas = multi::blas; +multi::array conjB = blas::C(B); + +assert( blas::C(B)[1][2] == std::conj(B[1][2]) ); +assert( blas::T(B)[1][2] == B[2][1] ); +assert( blas::N(B)[1][2] == N[1][2] ); +assert( blas::H(B)[1][2] == std::conj(B[2][1]) ); +``` + +Note that views do not play well with self-assignment. +```cpp +multi::array A({10, 10}); +A = multi::blas::T(A); // undefined behavior, this is not the right way to transpose a matrix in-place +``` +The main purpose of these functions is to manipulate arguments to BLAS interface functions. + +## BLAS level 1 + +(https://godbolt.org/z/Kjfa48d4P) + +The functions in this level operate on one-dimensional arrays (vectors). +Here, we use `multi::array` as representative of a vector, but a one-dimensional subarray, such as a row or a column of a 2D array, can also be used as a vector. + +### `auto multi::blas::copy(`_complex/real vector_`) -> `_convertible to complex/real vector_ + +Copies the values of a vector to another. + +This is similar to assigment, except that it used the underlying BLAS function (including parallelization if offered by the BLAS implementation) and has marginal utility. +However, this case serves as illustration of the _functional_ interface, used in the rest of the library: +`multi::blas::copy(v)` doesn't copy or allocates anything, it creates a "view" that can serve different purposes, illustrated in 3 different cases: +1) The view can be used to construct a new vector (needing allocation), +Once again `operator+` helps with automatic type deduction. + +```cpp +multi::array const v = {1.0, 2.0, 3.0}; +multi::array const v2 = multi::blas::copy(v); // case 1: allocates space for 3 elements and copies (using BLAS) +// auto const v2 = +v_copy; // same effect as line above +``` + +(Note that `auto const v2 = v_copy;` would not create a value or perform a copy, it will simply hold a variable with the "copy range". +This is not recommended as it can be confused and create a dangling range.) + +2) to assign to an existing vector (and resize it if is needed and possible) + +```cpp +multi::array v3; // mutable vector +v3 = multi::blas::copy(v); // case 2: resizes v3 (allocates space for 3 elements) and copies +``` + +```cpp +multi::array v4({3}, 0.0); // allocates space for 3 elements +v4 = multi::blas::copy(v); // case 2: assigns copies (no allocation necessary) +``` + +3) to assign to a 1D subarray vector that is _not_ resizable. +The importance of this case is that it guarantees that no allocations are performed. + +```cpp + multi::array cA({3}, 0.0); // allocates space for 3 elements + v4({0, 2}) = multi::blas::copy(v); // case 3: LHS is not resizable, assigns copies to subrange (resizing is not possible or necessary, no allocations) +``` + +### `auto multi::blas::swap(`_complex/real vector_`, `_complex/real vector_`) -> void` + +Swaps the values of two vectors. +Vector extensions must match. + +Note that the utility of `multi::blas::copy` and `multi::blas::swap` is redundant with native features of the library (such as plain assignment, copy construction and swap), the only difference is that these operations will be performed using the BLAS operations elementwise one dimensional arrays (vectors) on real or complex data only. + +These function do not work on 2D arrays (matrices) as the BLAS functions do not support this. +Copying or swapping 2D arrays with arbitrary layouts using BLAS could be done row-by-row: `std::transform(A2.begin(), A2.end(), B2.begin(), [](auto const& arow) {return multi::blas::copy(arow);})` (or, for efficiency column-by-column depending on the layout). + +### `auto multi::blas::nrm2(`_complex/real vector_`) -> `_convertible to real scalar_ + +Unary operation that computes the norm-2 (Euclidean norm) of a vector. +The result is convertible to a real scalar + +```cpp +multi::array const v = {1.0, 2.0, 3.0}; +double const n = multi::blas::nrm2(v); +// auto const n = +multi::blas::nrm2(v); +``` + +```cpp +multi::array, 2> const v = { {1.0, 2.0}, {2.0, 3.0}, {3.0, 4.0} }; +double const n = multi::blas::nrm2(v[0]); // acting on a row view +// auto const n = +multi::blas::nrm2(v[0]); +``` + +[(live)](https://godbolt.org/z/xf61EPWbT) + + +### `auto multi::blas::asum(`_complex/real vector_`) -> `_convertible to real scalar_ + +Returns the sum of the absolute values of the elements of a vector (norm-1). + +### `auto multi::blas::iamax(`_complex/real vector_`) -> `_index_type_ + +Index of the element with the largest absolute value (zero-based) + +### `auto multi::blas::dot(`_complex/real vector_, _complex/real vector_`) -> `_convertible to complex/real scalar_ + +Returns the dot product of two vectors with complex or real elements (`T`). + +```cpp +multi::array const v = {1.0, 2.0, 3.0}; +multi::array const w = {4.0, 5.0, 6.0}; +double const d = multi::blas::dot(v, w); +// auto const d = +multi::blas::dot(v, w); +``` + +Conjugation can be applied to either vector argument, + +```cpp + using multi::blas::dot; + using multi::blas::C; + + auto const d1 = +dot( v , w ); + auto const d2 = +dot(C(v), w ); + auto const d3 = +dot( v , C(w)); + auto const d4 = +dot(C(v), C(w)); +``` + +It is important to note that the left hand side of the assignment can be a scalar that is part of a heap allocation. +In this case, the result is going to directly put at this location. + +```cpp +multi::array z = {0.0, 0.0, 0.0}; +z[1] = multi::blas::dot(v, w); +``` + +This feature regarding scalar results is essential when operating on GPU memory since the whole operation can be performed on the device. + +> In CPUs BLAS `dot` functions has known bugs in different implementations. +> For example BLAS Apple Accelerate has bug in `sdot` while BLAS 32bit has a bug in `cdot`. +> In addition, some implementations of BLAS functions return the complex result in the stack and other write into a pointer. +> For this reason and for consistency, the library uses BLAS's `gemv` functions in place of `dot` in these cases. +> This should not affect the results. + +### `auto multi::blas::scal(`_complex/real scalar`, `_complex/real vector_`)` + +Scales a vector. + +### `auto multi::blas::axpy(`_complex/real scalar`, `_complex/real vector_`) -> `_convertible to complex/real_ + +Vector addition. + +```cpp +multi::array const x = ...; +multi::array y = ...; +y += blas::axpy(2.0, x); // same as blas:::axpy(+2.0, x, y) +y -= blas::axpy(2.0, x); // same as blas:::axpy(-2.0, x, y) +``` + +## BLAS level 2 + +These functions operate on vectors and arrays. +Again, we use `multi::array` as representative of a vector, but a one-dimensional subarray, such as a row or a column of a 2D array, can also be used as a vector. +`multi::array` as representative of a matrices, but a two-dimensional subarray or larger of higher dimensional arrays can be used as long as one of the two interternal strides in 1. +This is limitation of BLAS, that only acts on certain layouts of 2D arrays. + +### `auto multi::blas::gemv(`_complex/real scalar_`,` _complex/real matrix_`) -> `_convertible to complex/real vector_` + +```cpp +multi::array const A({4, 3}); +multi::array const x = {1.0, 2.0, 3.0}; +multi::array const x = {1.0, 2.0, 3.0, 4.0}; + +y = blas::gemv(5.0, A, x); // y <- 5.0 A * x +``` +[(live)](https://godbolt.org/z/jcrEzba8v) + +The gemv expression can be used for addition and subtraction, + +``` +y += blas::gemv(1.0, A, x); // y <- + A * x + y +y -= blas::gemv(1.0, A, x); // y <- - A * x + y +``` + +## BLAS level 3 + +### `auto multi::blas::gemm(`_complex/real scalar_`, `_complex/real_ matrix`, `_complex/real_ matrix`) -> `_convertible to complex/real matrix_` + +```cpp +#include +#include + +namespace multi = boost::multi; + +int main() { + multi::array const A({2, 2}); + multi::array const B({2, 2}); + + multi::array const C1 = multi::blas::gemm(1.0, A, B); + auto const C2 = + multi::blas::gemm(1.0, A, B); +} +``` +[(live)](https://godbolt.org/z/P9qWrW1br) + +(needs linking to BLAS to work, e.g. `-lblas` or `-lopenblas` or `-lmkl`, or throught [CMake](https://godbolt.org/z/jdbEe59ej)) + +### `auto multi::blas::herk(`_complex/real scalar_`, `_complex/real_ matrix`) -> `_convertible to complex/real matrix_` + +```cpp +#include + +namespace multi = boost::multi; +namespace blas = multi::blas; + +int main() { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array const A = { + {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, + {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, + }; + + multi::array const C = blas::herk(1.0, A); + + assert( +blas::gemm(1.0, A, blas::H(A)) == C ); +} +``` +[(live)](https://godbolt.org/z/71P5xT6qx) + +## Table of features + +All these operations are now supported for CPU and GPU memory, real and complex. + +scalars: `aa` ($`\alpha`$), `bb` ($`\beta`$) \ +vectors: `x`, `y` \ +matrices: `A`, `B`, `C` + +vector operations: `C` (`*`) conjugation (element-wise) \ +matrix operations: `J` (`*`) conjugation (element-wise) (use `C` for vectors), `T` transpose, `H` transpose conjugate (also `C`, discouraged), `U`/`L` upper or lower triangular part (logical zeroing other side) + + +| BLAS | mutable form | effect | operator form [³] | functional form | thrust/STL [¹] | +|--- |--- | --- | --- | --- | --- | +| SWAP |`blas::swap(x, y)` | $x_i \leftrightarrow y_i$ | `(x^y)` | | `swap_ranges(begin(x), end(x), begin(y))` | +| COPY |`blas::copy(x, y)` | $`y_i \leftarrow x_i`$ | `y << x` | `y = blas::copy(x)` | `copy(begin(x), end(x), begin(y))` | +| ASUM |`blas::asum(x, res)` | $`r \leftarrow \sum_i \|\Re x_i\| + \|\Im x_i\|`$ | `x==0`/`x!=0` `isinf(x)` `is an(x)`[²] | `res = blas::asum(x)` | `transform_reduce(begin(x), end(x), 0.0, plus<>{}, [](auto const& e){return abs(e.real()) + abs(e.imag());})` | +| NRM2 |`blas::nrm2(x, res)` | $`r \leftarrow \sqrt{\sum_i \|x_i\|^2}`$ | `abs(x)` | `res = blas::nrm2(x);` | `sqrt(trasnform_reduce(begin(x), end(x), 0.0, plus<>{}, [](auto const& e){return norm(e);}));` | +| SCAL |`blas::scal(aa, x);` | $`x_i \leftarrow \alpha x_i`$ | `x*=aa;` | | `for_each(begin(x), end(x), [aa](auto& e){return e*=aa;})` | +| AXPY |`blas::axpy(aa, x, y)` | $`y_i \leftarrow \alpha x_i + y_i`$ | `y+=x` `y-=x` `y+=aa*x` `y-=aa*x` | | `transform(x.begin(), x.end(), y.begin(), y.begin(), [aa](auto ex, auto ey) {return aa*ex + ey;}` | +| DOT | `blas::dot(x, y, res)` | $`r = \sum_i x_i y_i`$ | `res = (x, y);` | `res = blas::dot(x, y)` | `inner_product(begin(x), end(x), begin(y), T{});` | +| | `blas::dot(blas::C(x), y, res)` | $`r = \sum_i \bar x_i y_i`$ | `res = (*x, y);` | `res = blas::dot(blas::C(x), y)` | `inner_product(begin(x), end(x), begin(y), T{}, plus<>{}, [](T const& t1, T const& t2) {return conj(t1)*t2;});` | +| | `blas::dot(x, blas::C(y), res)` | $`r = \sum_i x_i \bar y_i`$ | `res = (x, *y);` | `res = blas::dot(x, blas::C(y));` | `inner_product(x.begin(), x.end(), y.begin(), T{}, plus<>{}, [](T const& t1, T const& t2) {return t1*conj(t2);});` | +| | ~~`blas::dot(blas::C(x), blas::C(y), res)`~~ | $`r = \sum_i \bar x_i \bar y_i`$ not implemented in BLAS, conjugate result | | | `auto res = conj(inner_product(x.begin(), x.end(), y.begin(), T{});` | +| GEMV | `blas::gemv(aa, A, x, bb, y)` | $`y_i \leftarrow \alpha\sum_j A_{ij}x_j + \beta y_i`$ | `y=A%x` `y=aa*A%x` `y+=A%x` `y+=aa*A%x`[¤] | `y=blas::gemv(aa, A, x)` `y+=blas::gemv(aa, A, x)` | `transform(begin(A), end(A), begin(y), [&x, aa] (auto const& Ac) {return aa*blas::dot(Ac, x);})` | +| | `blas::gemv(aa, blas::T(A), x, bb, y)` | $`y_i \leftarrow \alpha\sum_j A_{ji}x_j + \beta y_i`$ | `y= ~A % x` `y=aa*(~A)%x` `y+=(~A)%x` `y+=aa*(~A)%x` | `y=blas::gemv(aa, blas::T(A), x)` `y+=blas::gemv(aa, blas::T(A), x)` | `transform(begin(transposed(A)), end(transposed(A)), begin(y), [&x, aa] (auto const& Ac) {return aa*blas::dot(Ac, x);})` | +| | `blas::gemv(aa, blas::J(A), x, bb, y)` | $`y_i \leftarrow \alpha\sum_j A_{ij}^*x_j + \beta y_i`$ | `y= *A % x` `y=aa*(*A)%x` `y+=(*A)%x` `y+=aa*(*A)%x` | `y=blas::gemv(aa, blas::J(A), x)` `y+=blas::gemv(aa, blas::J(A), x)` | `transform(begin(A), end(A), begin(y), [&x, aa] (auto const& Ac) {return aa*blas::dot(*Ac, x);})` | +| | ~~`blas::gemv(aa, blas::H(A), x, bb, y)`~~ | $`y_i \leftarrow \alpha\sum_j A_{ji}^*x_j + \beta y_i`$ (not BLAS-implemented)| | | `transform(begin(transposed(A)), end(transposed(A)), begin(y), [&x, aa] (auto const& Ac) {return aa*blas::dot(*Ac, x);})` | +| GEMM | `blas::gemm(aa, A, B, bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ik} B_{kj} + \beta C_{ij}`$ | `C = aa*(A*B)` | `C = blas::gemm(aa, A, B)` `C += blas::gemm(aa, A, B)` | `transform(begin(A), end(A), begin(C), begin(C), [&B, aa, bb] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, blas::T(B), Ar, bb, move(Cr));})` | +| | `blas::gemm(aa, A, blas::T(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ik} B_{jk} + \beta C_{ij}`$ | `C = aa*(A* ~B)` | `C = blas::gemm(aa, A, blas::T(B))` `C += blas::gemm(aa, A, blas::T(B))` | `transform(begin(A), end(A), begin(C), begin(C), [&B, aa, bb] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, B, Ar, bb, move(Cr));})` | +| | `blas::gemm(aa, blas::T(A), B, bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ki} B_{kj} + \beta C_{ij}`$ | `C =~A * B` `C = aa*(~A * B)` `C+=~A * B` `C+=aa*(~A * B)` | `C = blas::gemm(aa, blas::T(A), B, bb, C)` (or `+=`) | `transform(begin(transposed(A)), end(transposed(A)), begin(C), begin(C), [&B, aa, bb] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr));})` | +| | `blas::gemm(aa, blas::T(A), blas::T(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ki} B_{jk} + \beta C_{ij}`$ | `C =~A * ~B` `C = aa*(~A * ~B)` `C+=~A * ~B` `C+=aa*(~A * ~B)` | `C = blas::gemm(aa, blas::T(A), blas::T(B), bb, C)` (or `+=`) | `transform(begin(transposed(A)), end(transposed(A)), begin(C), begin(C), [&B, aa, bb] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, B, Ar, bb, std::move(Cr));})` | +| | `blas::gemm(aa, A, blas::J(B), bb, C)` (use `blas::gemm(..., blas::T(B), blas::H(A), ..., HC)` and conjtranspose result) | $`C_{ij} \leftarrow \alpha \sum_k A_{ik} B_{kj}^* + \beta C_{ij}`$ (not BLAS-implemented) | | | `transform(begin(A), end(A), begin(C), begin(C), [BT=transposed(B)](auto const& Ar, auto&& Cr) {transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto&& c) {return aa*blas::dot(Ar, blas::C(Bc)) + bb*c;}); return std::move(Cr);});` | +| | ~~`blas::gemm(aa, blas::J(A), B, bb, C)`~~ | $`C_{ij} \leftarrow \alpha \sum_k A_{ik}^* B_{kj} + \beta C_{ij}`$ (not BLAS-implemented) | | | `transform(begin(A), end(A), begin(C), begin(C), [BT=transposed(B)](auto const& Ar, auto&& Cr) {transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto&& c) {return aa*blas::dot(blas::C(Ar), Bc) + bb*c;}); return std::move(Cr);});` | +| | `blas::gemm(aa, blas::J(A), blas::J(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k \bar{A_{ik}} \bar{B_{kj}} + \beta C_{ij}`$ (not BLAS-implemented) | | | `transform(begin(A), end(A), begin(C), begin(C), [BT=transposed(B)](auto const& Ar, auto&& Cr) {transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto&& c) {return aa*blas::dot(blas::C(Ar), blas::C(Bc)) + bb*c;}); return std::move(Cr);});` | +| | `blas::gemm(aa, A, blas::H(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ik} \bar B_{jk} + \beta C_{ij}`$ | `C = aa*(A* ~*B)` (or `+=`) | `C = blas::gemm(aa, A, blas::H(B))` `C += blas::gemm(aa, A, blas::H(B))` | `transform(begin(A), end(A), begin(CC), begin(CC), [&](auto const& Ar, auto&& Cr){return blas::gemv(aa, blas::J(B), Ar, bb, move(Cr));})` | +| | `blas::gemm(aa, blas::H(A), B, bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ki} B_{kj} + \beta C_{ij}`$ | `CC=~*A *B` | `C=blas::gemm(aa, blas::H(A), B)` | `transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BT=transposed(B)](auto const& Ac, auto&& Cr) {transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c){return aa*blas::dot(blas::C(Ac), Bc) + bb*c;}); return move(Cr);})` | +| | `blas::gemm(aa, blas::H(A), blas::H(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ki} \bar B_{jk} + \beta C_{ij}`$ | `CC=~*A * ~*B` | `C=blas::gemm(aa, blas::H(A), blas::H(B))` | `transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [&B](auto const& Ac, auto&& Cr) {transform(begin(B), end(B), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c) {return conj(std::transform_reduce(begin(Ac), end(Ac), begin(Bc), 0.0*c, std::plus<>{}, [](auto const& a, auto const& b) {return a*b;}));}); return move(Cr);})` | +| | `blas::gemm(aa, blas::T(A), blas::H(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ki} \bar B_{jk} + \beta C_{ij}`$ | `CC=~A * ~*B` | `C=blas::gemm(aa, blas::T(A), blas::H(B))` | `transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [&B](auto const& Ac, auto&& Cr) {transform(begin(B), end(B), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c) {return std::transform_reduce(begin(Ac), end(Ac), begin(Bc), 0.0*c, std::plus<>{}, [](auto const& a, auto const& b) {return a*conj(b);});}); return move(Cr);})` | +| | ~~`blas::gemm(aa, blas::H(A), blas::T(B), bb, C)`~~ | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ki} B_{jk} + \beta C_{ij}`$ (not BLAS-implemented) | | | `transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [&B](auto const& Ac, auto&& Cr) {transform(begin(B), end(B), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c) {return std::transform_reduce(begin(Ac), end(Ac), begin(Bc), 0.0*c, std::plus<>{}, [](auto const& a, auto const& b) {return conj(a)*b;});}); return move(Cr);})` | +| | ~~`blas::gemm(aa, blas::J(A), blas::H(B), bb, C)`~~ | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ik} \bar B_{jk} + \beta C_{ij}`$ (not BLAS-implemented) | | | | +| | ~~`blas::gemm(aa, blas::H(A), blas::J(B), bb, C)`~~ | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ki} \bar B_{kj} + \beta C_{ij}`$ (not BLAS-implemented) | | | | +| TRSM | `blas::trsm(blas::side::right, aa, blas::U(A), B)` | $`B\leftarrow B.U^{-1}`$ | `B /= U(A)` | | TODO | +| | `blas::trsm(blas::side::right, aa, blas::L(A), B)` | $`B\leftarrow B.L^{-1}`$ | `B /= L(A)` | | TODO | +| | `blas::trsm(blas::side::left, aa, blas::U(A), B)` | $`B\leftarrow U^{-1}.B`$ | `B \|= U(A)` | | TODO | +| | `blas::trsm(blas::side::left, aa, blas::L(A), B)` | $`B\leftarrow L^{-1}.B`$ | `B \|= L(A)` | | TODO | +| | ~~`blas::trsm(blas::side::right, aa, blas::U(A), blas::J(B))`~~ | $`B*\leftarrow B*.U^{-1}`$ $`B\leftarrow B.U*^{-1}`$ | | | TODO | +| | ~~`blas::trsm(blas::side::right, aa, blas::L(A), blas::J(B))`~~ | $`B*\leftarrow B*.L^{-1}`$ $`B\leftarrow B.L*^{-1}`$ | | | TODO | +| | `blas::trsm(blas::side::right, aa, blas::U(A), blas::H(B))` | $`B^\dagger\leftarrow B^\dagger.U^{-1}`$ $`B\leftarrow U^\dagger^{-1}.B`$ | | | TODO | +| | `blas::trsm(blas::side::right, aa, blas::L(A), blas::H(B))` | $`B^\dagger\leftarrow B^\dagger.L^{-1}`$ $`B\leftarrow L^\dagger^{-1}.B`$ | | | TODO | + +[¹]: for reference, not optimal. \ +[²]: `asum` is interpreted as a mechanism to detect null vectors or vectors containing NaN or infinities. \ +[³]: needs explicit invocation `using namespace multi::operators` namespace or of specific symbols `using multi::operator*`/`operator/=`/etc. \ +[¤]: `y *=bb +=aa*A%x` (`gemv(aa, A, x, bb, y)`) would also be possible. diff --git a/include/multi/adaptors/blas/asum.hpp b/include/boost/multi/adaptors/blas/asum.hpp similarity index 75% rename from include/multi/adaptors/blas/asum.hpp rename to include/boost/multi/adaptors/blas/asum.hpp index 14174ef9b..78ee1c06c 100644 --- a/include/multi/adaptors/blas/asum.hpp +++ b/include/boost/multi/adaptors/blas/asum.hpp @@ -1,10 +1,12 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_BLAS_ASUM_HPP -#define MULTI_ADAPTORS_BLAS_ASUM_HPP +#ifndef BOOST_MULTI_ADAPTORS_BLAS_ASUM_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_ASUM_HPP +#pragma once -#include "../blas/core.hpp" +#include namespace boost::multi::blas { @@ -18,7 +20,7 @@ using std::begin; using std::end; template auto asum(X1D const& x, A0D&& res) // NOLINT(readability-identifier-length) x conventional blas name //->decltype(asum_n(x.begin(), x.size(), &res)) { -{ return asum_n(x.begin(), x.size(), &res); } +{ return asum_n(std::begin(x), x.size(), &std::forward(res)); } template struct asum_ptr { @@ -33,6 +35,9 @@ struct asum_ptr { template friend auto uninitialized_copy_n(asum_ptr first, As... as) {return copy_n(first, as...);} + + template + static auto uninitialized_copy_n(asum_ptr first, As... as) {return copy_n(first, as...);} }; template @@ -40,9 +45,9 @@ template auto asum(A1D const& x) { // NOLINT(readability-identifier-length) BLAS naming struct ref { A1D const& x_; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-avoid-const-or-ref-data-members) - auto operator&() const& {return asum_ptr{&x_};} // NOLINT(google-runtime-operator) reference type + auto operator&() const& {return asum_ptr{&x_};} // NOLINT(google-runtime-operator) reference type //NOSONAR using decay_type = decltype(abs(std::declval())); - operator decay_type() const {decay_type ret; blas::asum(x_, ret); return ret;} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + operator decay_type() const {decay_type ret; blas::asum(x_, ret); return ret;} //NOSONAR // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax double a = asum(v); auto operator+() const -> decay_type {return operator decay_type();} }; @@ -52,14 +57,16 @@ auto asum(A1D const& x) { // NOLINT(readability-identifier-length) BLAS naming namespace operators { static constexpr double threshold = 1.0e-12; + using zero_type = void*****; + template - auto operator==(A1D const& self, [[maybe_unused]] void***** zero) -> bool { + auto operator==(A1D const& self, [[maybe_unused]] zero_type zero) -> bool { assert( zero == nullptr ); return blas::asum(self) < threshold; } template - auto operator!=(A1D const& self, [[maybe_unused]] void***** zero) -> bool { + auto operator!=(A1D const& self, [[maybe_unused]] zero_type zero) -> bool { assert( zero == nullptr ); return blas::asum(self) > threshold; } diff --git a/include/multi/adaptors/blas/axpy.hpp b/include/boost/multi/adaptors/blas/axpy.hpp similarity index 65% rename from include/multi/adaptors/blas/axpy.hpp rename to include/boost/multi/adaptors/blas/axpy.hpp index dff1cfb86..79c254007 100644 --- a/include/multi/adaptors/blas/axpy.hpp +++ b/include/boost/multi/adaptors/blas/axpy.hpp @@ -1,14 +1,22 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_BLAS_AXPY_HPP -#define MULTI_ADAPTORS_BLAS_AXPY_HPP -#pragma once +#ifndef BOOST_MULTI_ADAPTORS_BLAS_AXPY_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_AXPY_HPP -#include +#include -#include -#include +#include + +#include // for assert +#include // for complex +#include // for iterator_traits, rando... +#include // for enable_if_t, decay_t +#include // for forward, declval + +#define BOOST_MULTI_DECLRETURN(ExpR) -> decltype(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing +#define BOOST_MULTI_JUSTRETURN(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing namespace boost::multi::blas { @@ -17,28 +25,29 @@ using core::axpy; template auto axpy_n(typename It1::value_type alpha, It1 first, Size n, OutIt d_first) ->decltype(axpy(n, &alpha, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n) { - return axpy(n, &alpha, base(first) , stride(first) , base(d_first) , stride(d_first) ), d_first + n; } + return axpy(n, &alpha, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n; } template//, class=std::enable_if_t{}>> auto axpy_n(Context ctxt, typename It1::value_type alpha, It1 first, Size n, OutIt d_first) -//->decltype(ctxt->axpy(n, &alpha, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n) { -{ return ctxt->axpy(n, &alpha, base(first) , stride(first) , base(d_first) , stride(d_first)) , d_first + n; } +->decltype(ctxt->axpy(n, &alpha, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n) { + return ctxt->axpy(n, &alpha, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n; } + +template()[0] = 0.0, *X1DIt{} )> +auto axpy(Context ctxt, typename X1DIt::element alpha, X1DIt x, Y1D&& y) // NOLINT(readability-identifier-length) conventional BLAS names +->decltype( std::forward(y)) { + return axpy_n(ctxt, alpha, x, size(y), begin(y)), std::forward(y); } -template()[0] = 0.0 )> +template()[0] = 0.0, size(std::declval()) )> auto axpy(Context ctxt, typename X1D::element alpha, X1D const& x, Y1D&& y) // NOLINT(readability-identifier-length) conventional BLAS names -->decltype(/*axpy_n(std::forward(ctxt), alpha, x.begin( ), x.size( ), y.begin( )),*/ std::forward(y)) { - assert(size(x)==size(y)); // intel doesn't like ADL in deduced/sfinaed return types // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : bug in clang-tidy https://reviews.llvm.org/D31130 - return axpy_n(ctxt, alpha, begin(x), size(x), begin(y)), std::forward(y); } +->decltype( std::forward(y)) { assert(x.size() == y.size() ); + return axpy_n(ctxt, alpha, x.begin(), y.size(), y.begin()), std::forward(y); } template()[0] = 0.0 )> auto axpy(typename X1D::element alpha, X1D const& x, Y1D&& y) // NOLINT(readability-identifier-length) conventional BLAS names -> decltype(auto) -//->decltype(/*axpy_n(alpha, x.begin(), x.size(), y.begin()),*/ axpy_n(alpha, x.begin(), size(x), y.begin()), std::forward(y)) { auto ctxtp = blas::default_context_of(x.base()); - return axpy(ctxtp, alpha, x, std::forward(y)); -// assert(size(x)==size(y)); // intel doesn't like ADL in deduced/sfinaed return types // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : bug in clang-tidy https://reviews.llvm.org/D31130 -// return axpy_n(ctxtp, alpha, begin(x), size(x), begin(y)), std::forward(y); + return boost::multi::blas::axpy(ctxtp, alpha, x, std::forward(y)); } template @@ -52,12 +61,15 @@ auto axpy(Context&& ctxt, X1D const& x, Y1D&& y) -> Y1D&& { // NOLINT(readabili } template -struct axpy_iterator { +class axpy_iterator { Context ctxt_; Scale alpha_; ItX x_begin_; public: + axpy_iterator(Context ctxt, Scale alpha, ItX x_begin) + : ctxt_{ctxt}, alpha_{alpha}, x_begin_{x_begin} {} + using difference_type = typename std::iterator_traits::difference_type; using value_type = typename std::iterator_traits::value_type; using pointer = void; @@ -96,13 +108,12 @@ class axpy_range { : ctxt_{ctxt}, alpha_{alpha}, x_begin_{x_first}, count_{x_last - x_first} {} using iterator = axpy_iterator; -// using decay_type = DecayType; auto begin() const -> iterator{ return {ctxt_, alpha_, x_begin_ }; } auto end() const -> iterator{ return {ctxt_, alpha_, x_begin_ + count_}; } - auto size() const -> size_type{return end() - begin();} -// auto extensions() const -> typename decay_type::extensions_type {return typename decay_type::extensions_type{{0, size()}};} + auto size() const -> size_type { return end() - begin(); } + auto extensions() const { return extensions_t<1>{ {0, size()} }; } template friend auto operator+=(Other&& other, axpy_range const& self) -> Other&& { @@ -119,7 +130,8 @@ class axpy_range { auto operator*=(Scale s) & -> axpy_range& {alpha_ *= s; return *this;} // NOLINT(readability-identifier-length) conventional BLAS naming }; -template{}>> +template{}>> // NOLINT(modernize-use-constraints) for C++20 auto axpy(Context&& ctxt, Scalar a, X1D const& x) // NOLINT(readability-identifier-length) conventional BLAS naming -> axpy_range { // NOLINT(readability-identifier-length) conventional BLAS naming return {std::forward(ctxt), a, begin(x), end(x)}; @@ -127,10 +139,9 @@ auto axpy(Context&& ctxt, Scalar a, X1D const& x) // NOLINT(readability-identif template auto axpy(Scalar a, X1D const& x) // NOLINT(readability-identifier-length) conventional BLAS naming -//-> axpy_range { { auto ctxtp = blas::default_context_of(x.base()); - return axpy_range{ctxtp, a, begin(x), end(x)}; // TODO(correaa) fix temporary + return axpy_range{ctxtp, a, begin(x), end(x)}; } template @@ -148,15 +159,15 @@ class scaled { namespace operators { -template struct algebraic_traits {static auto one() {return T{1.0};}}; +template struct algebraic_traits {static auto one() { return T{1.0}; }}; -template struct algebraic_traits> {static auto one() {return std ::complex{T{1}, T{0}};}}; -template struct algebraic_traits> {static auto one() {return multi::complex{T{1}, T{0}};}}; +template struct algebraic_traits> {static auto one() {return std ::complex{T{1}, T{0}};}}; -template auto operator+=(X1D&& x, Y1D const& other) DECLRETURN(axpy(+algebraic_traits::one(), other, std::forward(x))) // NOLINT(fuchsia-default-arguments-calls,readability-identifier-length) conventional name in BLAS -template auto operator-=(X1D&& x, Y1D const& other) DECLRETURN(axpy(-algebraic_traits::one(), other, std::forward(x))) // NOLINT(fuchsia-default-arguments-calls,readability-identifier-length) conventional name in BLAS +template auto operator+=(X1D&& x, Y1D const& other) BOOST_MULTI_DECLRETURN(axpy(+algebraic_traits::one(), other, std::forward(x))) // NOLINT(fuchsia-default-arguments-calls,readability-identifier-length) conventional name in BLAS +template auto operator-=(X1D&& x, Y1D const& other) BOOST_MULTI_DECLRETURN(axpy(-algebraic_traits::one(), other, std::forward(x))) // NOLINT(fuchsia-default-arguments-calls,readability-identifier-length) conventional name in BLAS -template =0> +template =0> // NOLINT(modernize-use-constraints) for C++20 auto operator*(typename X::element_type a, X const& x) {return scaled{a, x};} // NOLINT(readability-identifier-length) conventional BLAS naming template auto operator+(X1D const& x, Y1D const& y) -> std::decay_t {auto X = x.decay(); X += y; return X;} // NOLINT(readability-identifier-length) conventional name in BLAS @@ -165,4 +176,8 @@ template auto operator-(X1D const& x, Y1D const& y) -> std } // end namespace operators } // end namespace boost::multi::blas -#endif + +#undef BOOST_MULTI_DECLRETURN +#undef BOOST_MULTI_JUSTRETURN + +#endif // BOOST_MULTI_ADAPTORS_BLAS_AXPY_HPP diff --git a/include/multi/adaptors/blas/complex_traits.hpp b/include/boost/multi/adaptors/blas/complex_traits.hpp similarity index 60% rename from include/multi/adaptors/blas/complex_traits.hpp rename to include/boost/multi/adaptors/blas/complex_traits.hpp index 1f73a11f5..4bd796a64 100644 --- a/include/multi/adaptors/blas/complex_traits.hpp +++ b/include/boost/multi/adaptors/blas/complex_traits.hpp @@ -1,15 +1,16 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_BLAS_COMPLEX_TRAITS_HPP -#define MULTI_ADAPTORS_BLAS_COMPLEX_TRAITS_HPP -#pragma once +#ifndef BOOST_MULTI_ADAPTORS_BLAS_COMPLEX_TRAITS_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_COMPLEX_TRAITS_HPP -#include // for std::complex -#ifdef __NVCC__ +#if defined(__NVCC__) || defined(__HIPCC__) // defined(__HIP_PLATFORM_AMD__) || defined(__HIP_PLATFORM_NVIDIA__) #include #endif +#include // for std::complex + namespace boost::multi::blas { template @@ -24,7 +25,7 @@ struct complex_traits> { constexpr static auto imaginary_unit() { return ::std::complex{0, 1}; } }; -#ifdef __NVCC__ +#if defined(__NVCC__) || defined(__HIPCC__) // defined(__HIP_PLATFORM_AMD__) || defined(__HIP_PLATFORM_NVIDIA__) template struct complex_traits<::thrust::complex> { using real_type = typename ::thrust::complex::value_type; @@ -32,7 +33,6 @@ struct complex_traits<::thrust::complex> { }; #endif - } // end namespace boost::multi::blas -#endif \ No newline at end of file +#endif diff --git a/include/boost/multi/adaptors/blas/copy.hpp b/include/boost/multi/adaptors/blas/copy.hpp new file mode 100644 index 000000000..8ab394ded --- /dev/null +++ b/include/boost/multi/adaptors/blas/copy.hpp @@ -0,0 +1,105 @@ +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_COPY_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_COPY_HPP + +#include // for copy, default_context_of // IWYU pragma: export +// IWYU pragma: no_include "boost/multi/adaptors/blas/core.hpp" // bug in iwyu 18.1.8? + +#include // for assert +#include // for iterator_traits, outpu... +#include // for forward + +namespace boost::multi::blas { + +using core::copy; + +template +auto copy_n(It first, Size n, OutIt d_first) + -> decltype(blas::default_context_of(first.base())->copy(n, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n) { + /**/ return blas::default_context_of(first.base())->copy(n, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n; +} + +template +auto copy(X1D const& x, Y1D&& y) // NOLINT(readability-identifier-length) BLAS naming + -> decltype(blas::copy_n(x.begin(), size(x), y.begin()), std::forward(y)) { + assert((x.size() == y.size())); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : assert + /**/ return blas::copy_n(x.begin(), x.size(), y.begin()), std::forward(y); +} + +template +struct copy_it { + It it_; // NOLINT(misc-non-private-member-variables-in-classes) + + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = void; + using reference = void; + using iterator_category = std::output_iterator_tag; + using iterator_type = copy_it; + + explicit copy_it(It it) : it_{it} {} + + copy_it() = default; + copy_it(copy_it const&) = default; + copy_it(copy_it&&) = default; + auto operator=(copy_it const&) -> copy_it& = default; + auto operator=(copy_it&&) -> copy_it& = default; + ~copy_it() = default; + + friend auto operator-(copy_it const& c1, copy_it const& c2) { return c1.it_ - c2.it_; } + + auto operator==(copy_it const& other) const -> bool { return it_ == other.it_; } + auto operator!=(copy_it const& other) const -> bool { return it_ != other.it_; } + + template + friend constexpr auto copy_n(copy_it first, difference_type count, It1DOut result) -> It1DOut { + return blas::copy_n(first.it_, count, result); + } + template + friend constexpr auto copy(copy_it first, copy_it last, It1DOut d_first) -> It1DOut { + return copy_n(first, distance(first, last), d_first); + } + template + friend constexpr auto uninitialized_copy(copy_it first, copy_it last, It1DOut d_first) -> It1DOut { + return copy_n(first, distance(first, last), d_first); + } + friend constexpr auto distance(copy_it const& self, copy_it const& other) -> difference_type { + return other.it_ - self.it_; + } + constexpr auto operator*() const -> value_type { return *it_; } + + constexpr auto operator++() const -> copy_it&; + constexpr auto operator--() const -> copy_it&; + + constexpr auto operator++(int) const -> copy_it; + constexpr auto operator--(int) const -> copy_it; +}; + +template [[nodiscard]] +auto copy(A1D const& x) { // NOLINT(readability-identifier-length) BLAS naming + struct ref { + A1D const& x_; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-avoid-const-or-ref-data-members) + using iterator = copy_it; + auto begin() const { return iterator{x_.begin()}; } + auto end() const { return iterator{x_.end()}; } + auto size() const { return x_.size(); } + auto extensions() const { return x_.extensions(); } + }; + return ref{x}; +} + +namespace operators { + +template +auto operator<<(A1D&& lhs, B1D const& rhs) -> A1D&& { + return boost::multi::blas::copy(rhs, std::forward(lhs)); +} + +} // end namespace operators + +} // end namespace boost::multi::blas + +#endif diff --git a/include/boost/multi/adaptors/blas/core.hpp b/include/boost/multi/adaptors/blas/core.hpp new file mode 100644 index 000000000..58e5b4630 --- /dev/null +++ b/include/boost/multi/adaptors/blas/core.hpp @@ -0,0 +1,676 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_CORE_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_CORE_HPP + +// https://software.intel.com/en-us/articles/intel-mkl-link-line-advisor + +#include +#include +#include +#include // int64_t +#include // std::memcpy +#include // for debug +#include // numeric_limits +#include // is_convertible + +// #include "../../config/MARK.hpp" + +#include // IWYU pragma: export + +#ifdef __PRETTY_FUNCTION__ +#define BOOST_MULTI_BLAS_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#else +#define BOOST_MULTI_BLAS_PRETTY_FUNCTION "some-multi-blas-function" +#endif + +#ifndef NDEBUG + #include + #include + #define BOOST_MULTI_ASSERT1(ExpR) (void)((ExpR)?0:throw std::logic_error("\n" __FILE__ ":"+std::to_string(__LINE__)+"::\n"+std::string(BOOST_MULTI_BLAS_PRETTY_FUNCTION)+"\nLogic assertion `" #ExpR "' failed.")) /*NOLINT(fuchsia-default-arguments-calls,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ + #define BOOST_MULTI_ASSERT2(ExpR, DescriptioN) (void)((ExpR)?0:throw std::DescriptioN("\n" __FILE__ ":"+std::to_string(__LINE__)+"::\n"+std::string(BOOST_MULTI_BLAS_PRETTY_FUNCTION)+"\nLogic assertion `" #ExpR "' failed.")) +#else + #define BOOST_MULTI_ASSERT1(ExpR) assert(ExpR) + #define BOOST_MULTI_ASSERT2(ExpR, DescriptioN) assert(EXpR) +#endif + +#ifdef CBLAS_H +#define BLAS(NamE) cblas_##NamE +#else +#define BLAS(NamE) NamE##_ + +#ifndef MULTI_BLAS_INT +#ifdef __INTPTR_WIDTH__ + #define MULTI_BLAS_INT __INTPTR_WIDTH__ +#endif +#endif + +#define s float +#define d double +#define c std::complex +#define z std::complex +#define v void + +// cppcheck-suppress unusedStructMember +using Complex_float = struct { float real; float imag; }; +// cppcheck-suppress unusedStructMember +using Complex_double = struct { double real; double imag; }; + +#define C Complex_float // _Complex s +#define Z Complex_double // _Complex d + +#ifdef MULTI_BLAS_INT + #if MULTI_BLAS_INT==32 + using INT = std::int32_t; // #define INT int32_t + #elif MULTI_BLAS_INT==64 + using INT = std::int64_t; // #define INT int64_t + #else + using INT = std::int32_t; // #define INT int32_t // 32bit safe? pesimistic? + #endif +#else + using INT = std::int32_t; // #define INT int32_t // 32bit safe? pesimistic? +#endif + +namespace core { + using size_t = INT; + using ssize_t = std::make_signed_t; +} // end namespace core + +extern "C" { + +#define INTEGER INT const& +#define N INTEGER n +#define INCX INTEGER incx +#define INCY INTEGER incy + +static_assert(sizeof(INT)==32/8 || sizeof(INT)==64/8, "please set MULTI_BLAS_INT to int32_t or int64_t"); + +// indented declarations like in https://www.netlib.org/lapack/lug/node145.html + +#define xROTG(T1, T2) v T1##rotg ##_ ( T1 const*, T1 const*, T2*, T1* ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xROTMG(T) v T ##rotmg##_ ( T*, T*, T* , T const&, T(¶m)[5] ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xROT(TT, T, S) v TT##rot ##_ ( N, T *x, INCX, T *y, INCY, S const&, S const& ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xROTM(T) v T ##rotm ##_ ( N, T *x, INCX, T *y, INCY, T const(&p)[5]) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xSWAP(T) v T ##swap ##_ ( N, T *x, INCX, T *y, INCY ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xSCAL(TT, TA, TX) v TT##scal ##_ ( N, TA const& a, TX *x, INCX ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xCOPY(T) v T ##copy ##_ ( N, T const *x, INCX, T *y, INCY ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xAXPY(T) v T ##axpy ##_ ( N, T const* a, T const *x, INCX, T *y, INCY ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type + +// in MKL and vanilla BLAS, OpenBLAS, real dot always return by stack +#define xDOT(R, TT, T) auto TT##dot ##_ ( N, T const *x, INCX, T const *y, INCY) -> R // NOLINT(readability-identifier-length) conventional BLAS naming + +// PGI/NVC++ compiler uses a blas version that needs -DRETURN_BY_STACK +#ifdef BLAS_DOT_RETURNS_VOID +//#if defined(RETURN_BY_STACK) || (defined(FORTRAN_COMPLEX_FUNCTIONS_RETURN_VOID) && FORTRAN_COMPLEX_FUNCTIONS_RETURN_VOID) +//#define xDOT(R, TT, T) v TT##dot ##_ (R*, N, T const *x, INCX, T const *y, INCY) +#define xDOTU(R, T) v T ##dotu ##_ (R*, N, T const * /*x*/, INCX, T const * /*y*/, INCY) // NOLINT(bugprone-macro-parentheses) : macro arg expands to type +#define xDOTC(R, T) v T ##dotc ##_ (R*, N, T const * /*x*/, INCX, T const * /*y*/, INCY) // NOLINT(bugprone-macro-parentheses) : macro arg expands to type +#else +#define xDOTU(R, T) auto T ##dotu ##_ ( N, T const *x, INCX, T const *y, INCY) -> R // NOLINT(readability-identifier-length) conventional BLAS naming +#define xDOTC(R, T) auto T ##dotc ##_ ( N, T const *x, INCX, T const *y, INCY) -> R // NOLINT(readability-identifier-length) conventional BLAS naming +//#define xxDOT(TT, T) auto TT##dot ##_ ( N, T const& a, T const *x, INCX, T const *y, INCY) -> T +#endif + +#define xNRM2(R, TT, T) auto TT##nrm2##_ ( N, T const *x, INCX) -> R // NOLINT(readability-identifier-length) conventional BLAS naming +#define xASUM(R, TT, T) auto TT##asum##_ ( N, T const *x, INCX) -> R // NOLINT(readability-identifier-length) conventional BLAS naming +#define IxAMAX(T) auto i##T ##amax##_ ( N, T const* x, INCX) -> INT // NOLINT(readability-identifier-length) conventional BLAS naming + +xROTG(s, s) ; xROTG(d,d) ; // MKL has extension for "(c, s)" and "(z, d)" +xROTMG(s) ; xROTMG(d) ; +xROT(s, s, s) ; xROT(d, d, d) ; xROT(cs, c, s); xROT(zd, z, d); +xROTM(s) ; xROTM(d) ; +xSWAP(s) ; xSWAP(d) ; xSWAP(c) ; xSWAP(z); +xSCAL(s, s, s); xSCAL(d, d, d); xSCAL(c, c, c); xSCAL(z, z, z); xSCAL(zd, d, z); xSCAL(cs, s, c); +xCOPY(s) ; xCOPY(d) ; xCOPY(c) ; xCOPY(z) ; +xAXPY(s) ; xAXPY(d) ; xAXPY(c) ; xAXPY(z) ; + +xDOT (s, s, s) ; xDOT(d, d, d) ; xDOT(d, ds, s); +xDOTU(C, c) ; xDOTU(Z, z) ; // MKL has extension for "(c, c)" and "(z, z)"? +xDOTC(C, c) ; xDOTC(Z, z) ; // MKL has extension for "(sds, s)" + +xNRM2(s, s, s); xNRM2(d, d, d); xNRM2(s, sc, c); xNRM2(d, dz, z); +xASUM(s, s, s); xASUM(d, d, d); xASUM(s, sc, c); xASUM(d, dz, z); +IxAMAX(s); IxAMAX(d); IxAMAX(c); IxAMAX(z); + +#define TRANS const char& trans +#define NR INTEGER nr +#define NC INTEGER nc +#define LDA INTEGER lda +#define UPLO const char& uplo +#define DIAG const char& diag + +#define xGEMV(T) void T## gemv ##_ ( TRANS, NR, NC, T const& a, T const* A, LDA, T const* X, INCX, T const& beta, T* Y, INCY ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xGER( T) void T## ger ##_ ( NR, NC, T const& a, T const* X, INCX, T const* Y, INCY, T* A, LDA) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xGERU(T) void T## geru ##_ ( NR, NC, T const& a, T const* X, INCX, T const* Y, INCY, T* A, LDA) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xGERC(T) void T## gerc ##_ ( NR, NC, T const& a, T const* X, INCX, T const* Y, INCY, T* A, LDA) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xTRSV(T) void T## trsv ##_ (UPLO, TRANS, DIAG, N, T const* A, LDA, T* X , INCX ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type + +xGEMV(s); xGEMV(d); xGEMV(c); xGEMV(z); +xGER(s) ; xGER(d) ; +xGERU(c); xGERU(z); +xGERC(c); xGERC(z); +xTRSV(s); xTRSV(d); xTRSV(c); xTRSV(z); + +#define TRANSA const char& transa +#define TRANSB const char& transb +#define NK INTEGER nk +#define LDB INTEGER ldb +#define LDC INTEGER ldc + +#define SIDE const char& side + +#define xGEMM(T) void T ##gemm ##_ ( TRANSA, TRANSB, NR, NC, NK, T const& a, T const* A, LDA, T const* B, LDB, T const& b , T const* CC, LDC) // NOLINT(readability-identifier-length) conventional BLAS naming +#define xSYRK(T) void T ##syrk ##_ ( UPLO, TRANSA, NR, NK, T const& a, T const* A, LDA, T const& b , T* CC, LDC) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xHERK(TT, T) void T ##herk ##_ ( UPLO, TRANSA, NR, NK, TT const& a, T const* A, LDA, TT const& b , T* CC, LDC) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type +#define xTRSM(T) void T ##trsm ##_ (SIDE, UPLO, TRANSA, DIAG, NR, NK, T const& a, T const* A, LDA, T const* B, LDB ) // NOLINT(readability-identifier-length) conventional BLAS naming + +xGEMM(s); xGEMM(d); xGEMM(c) ; xGEMM(z) ; +xSYRK(s); xSYRK(d); xSYRK(c) ; xSYRK(z) ; + xHERK(s, c); xHERK(d, z); +xTRSM(s); xTRSM(d); xTRSM(c) ; xTRSM(z) ; + +#undef TRANS +#undef UPLO +#undef SIDE +#undef DIAG +#undef xROTG +#undef xROTMG +#undef xROT +#undef xROTM +#undef xSCAL +#undef xSWAP +#undef xCOPY +#undef xAXPY + +#undef xDOT +#undef xDOTU +#undef xDOTC + +#undef xNRM2 +#undef xASUM +#undef IxAMAX +#undef xGEMV +#undef xGER +#undef xGERU +#undef xGERC +#undef xGEMM +#undef xHERK +#undef xTRSM + +#undef s +#undef d +#undef c +#undef z +#undef C +#undef Z +#undef v +#undef INTEGER +#undef N +#undef INCX +#undef INCY +#undef TRANSA +#undef TRANSB +#undef LDA +#undef LDB +#undef LDC +} // end of extern "C" +#endif + +namespace boost::multi::blas { + +// Boundary Checked value +#define BC(value) [](auto checked) {assert(checked >= std::numeric_limits::min() && checked < std::numeric_limits::max()); return checked;}(value) /*NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ + +namespace core { + +using s = float; +using d = double; +using c = std::complex; +using z = std::complex; +using v = void; + +using std::enable_if_t; +using std::is_assignable; +using std::is_assignable_v; + +using ::core::ssize_t; + +// xrotg, xrotmg, xrot, xrotm are not implemented, they might be out of scope + +// NOLINTBEGIN(modernize-use-constraints) for C++20 +// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length,modernize-use-constraints) +template{} && is_s{} && is_assignable{},int> =0> void swap(ssize_t n, SX* x, ptrdiff_t incx, SY* y, ptrdiff_t incy) noexcept {BLAS(sswap)(n, reinterpret_cast< float *>(x), incx, reinterpret_cast< float *>(y), incy); } // NOSONAR +template{} && is_d{} && is_assignable{},int> =0> void swap(ssize_t n, DX* x, ptrdiff_t incx, DY* y, ptrdiff_t incy) noexcept {BLAS(dswap)(n, reinterpret_cast< double *>(x), incx, reinterpret_cast< double *>(y), incy); } // NOSONAR +template{} && is_c{} && is_assignable{},int> =0> void swap(ssize_t n, CX* x, ptrdiff_t incx, CY* y, ptrdiff_t incy) noexcept {BLAS(cswap)(n, reinterpret_cast*>(x), incx, reinterpret_cast*>(y), incy); } // NOSONAR +template{} && is_z{} && is_assignable{},int> =0> void swap(ssize_t n, ZX* x, ptrdiff_t incx, ZY* y, ptrdiff_t incy) noexcept {BLAS(zswap)(n, reinterpret_cast*>(x), incx, reinterpret_cast*>(y), incy); } // NOLINT() // NOSONAR + +template{} && is_s{} && is_assignable{},int> =0> void copy(ssize_t n, SX* x, ptrdiff_t incx, SY* y, ptrdiff_t incy) {BLAS(scopy)(n, reinterpret_cast< float const*>(x), incx, reinterpret_cast< float *>(y), incy);} // NOSONAR +template{} && is_d{} && is_assignable{},int> =0> void copy(ssize_t n, DX* x, ptrdiff_t incx, DY* y, ptrdiff_t incy) {BLAS(dcopy)(n, reinterpret_cast< double const*>(x), incx, reinterpret_cast< double *>(y), incy);} // NOSONAR +template{} && is_c{} && is_assignable{},int> =0> void copy(ssize_t n, CX* x, ptrdiff_t incx, CY* y, ptrdiff_t incy) {BLAS(ccopy)(n, reinterpret_cast const*>(x), incx, reinterpret_cast*>(y), incy);} // NOSONAR +template{} && is_z{} && is_assignable{},int> =0> void copy(ssize_t n, ZX* x, ptrdiff_t incx, ZY* y, ptrdiff_t incy) {BLAS(zcopy)(n, reinterpret_cast const*>(x), incx, reinterpret_cast*>(y), incy);} // NOSONAR + +// TODO(correaa) : add mixed-type scal (zdscal, csscal) +template::element_type, class ALPHA = typename std::pointer_traits::element_type, enable_if_t{} && is_s{} && is_assignable{}>* = nullptr> void scal(SSize n, ALPHAP a, SXP xp, SSize incx) {BLAS(sscal)(static_cast(n), *reinterpret_cast< float const*>(a), reinterpret_cast< float *>(xp), static_cast(incx));} // NOSONAR +template::element_type, class ALPHA = typename std::pointer_traits::element_type, enable_if_t{} && is_d{} && is_assignable{}>* = nullptr> void scal(SSize n, ALPHAP a, DXP xp, SSize incx) {BLAS(dscal)(static_cast(n), *reinterpret_cast< double const*>(a), reinterpret_cast< double *>(xp), static_cast(incx));} // NOSONAR +template::element_type, class ALPHA = typename std::pointer_traits::element_type, enable_if_t{} && is_c{} && is_assignable{}>* = nullptr> void scal(SSize n, ALPHAP a, CXP xp, SSize incx) {BLAS(cscal)(static_cast(n), *reinterpret_cast const*>(a), reinterpret_cast*>(xp), static_cast(incx));} // NOSONAR +template::element_type, class ALPHA = typename std::pointer_traits::element_type, enable_if_t{} && is_z{} && is_assignable{}>* = nullptr> void scal(SSize n, ALPHAP a, ZXP xp, SSize incx) {BLAS(zscal)(static_cast(n), *reinterpret_cast const*>(a), reinterpret_cast*>(xp), static_cast(incx));} // NOSONAR +// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length,modernize-use-constraints) +// NOLINTEND(modernize-use-constraints) for C++20 + +using std::pointer_traits; +using std::enable_if_t; +using std::is_convertible_v; + +#define xaxpy(T) \ +template::element_type, class SYP, class SY = typename pointer_traits::element_type, enable_if_t< /* NOLINT(modernize-use-constraints) */ \ + is_##T{} && is_##T{} && is_##T{} && is_assignable{} \ + && is_convertible_v && is_convertible_v \ +, int> =0> \ +void axpy(Size n, ALPHA const* a, SXP x, SSize incx, SYP y, SSize incy) { BLAS(T##axpy)(static_cast(n), reinterpret_cast(a), reinterpret_cast(static_cast(x)), static_cast(incx), reinterpret_cast(static_cast(y)), static_cast(incy));} /*NOLINT(readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast,bugprone-macro-parentheses) conventional BLAS name*/ + +xaxpy(s) xaxpy(d) xaxpy(c) xaxpy(z) +#undef xaxpy +} // end namespace core + +// #undef xrotg +// #undef xrot +// #undef xswap +// #undef xscal +// #undef xcopy +// #undef xaxpy +// #undef xdot + +#ifndef CBLAS_H + +namespace core { + +using std::enable_if_t; +using std::is_assignable; + +// NOLINTBEGIN(modernize-use-constraints) for C++20 +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_s{} && is_assignable{}, int> =0> void dot (ssize_t n, XP* xp, ptrdiff_t incx, YP* yp, ptrdiff_t incy, RP* rp) { + // Apple Accelerate BLAS is known to have bugs in single precission function + // `sdot` and `smrm2`, be careful: + // https://stackoverflow.com/a/77017238/225186, + // https://fortran-lang.discourse.group/t/how-many-blas-libraries-have-this-error/4454/23, + // https://forums.developer.apple.com/forums/thread/717757 +#ifdef MULTI_BLAS_USE_SDOT // disable workararound for Apple Accelerate framework bug + auto const rr = BLAS(sdot )(n, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r)); +#else + BLAS(sgemv)('N', 1, n, 1.0F, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy, 0.0F, reinterpret_cast(static_cast(rp)), 1); // NOLINT(readability-suspicious-call-argument,cppcoreguidelines-pro-type-reinterpret-cast) +#endif +} +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_d{} && is_assignable{}, int> =0> void dot (ssize_t n, XP* xp, ptrdiff_t incx, YP* yp, ptrdiff_t incy, RP* r) {auto const rr = BLAS(ddot )(n, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r));} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) // NOSONAR + +// PGI/NVC++ compiler uses a blas version that needs -DRETURN_BY_STACK +#ifdef BLAS_DOT_RETURNS_VOID +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_c{} && is_assignable{}, int> =0> void dotu(SSize n, XP xp, SSize incx, YP yp, SSize incy, RP rp) { + [[maybe_unused]] static bool const check = []{ + std::array, 3> const v1 = {std::complex{1.0F, 2.0F}, std::complex{3.0F, 4.0F}, std::complex{ 5.0F, 6.0F}}; + std::array, 3> const v2 = {std::complex{7.0F, 8.0F}, std::complex{9.0F, 10.0F}, std::complex{11.0F, 12.0F}}; + Complex_float rr{-1.0F, -2.0F}; + BLAS(cdotu)(&rr, 3, v1.data(), 1, v2.data(), 1); + if( std::abs(rr.real - std::real(v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2])) > 1.0e-8 ) { throw std::logic_error("[real] cdotu should be configured as non-void returing"); } + if( std::abs(rr.imag - std::imag(v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2])) > 1.0e-8 ) { throw std::logic_error("[imag] cdotu should be configured as non-void returing"); } + return true; + }(); + // BLAS(cdotu)(reinterpret_cast(rp), n, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + BLAS(cgemv)('N', 1, static_cast(n), std::complex{1.0F, 0.0F}, reinterpret_cast(static_cast(xp)), static_cast(incx), reinterpret_cast(static_cast(yp)), static_cast(incy), std::complex{0.0F, 0.0F}, reinterpret_cast(static_cast(rp)), 1); // NOLINT(readability-suspicious-call-argument,cppcoreguidelines-pro-type-reinterpret-cast) +} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,google-readability-casting) : adapt types +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_z{} && is_assignable{}, int> =0> void dotu(SSize n, XP xp, SSize incx, YP yp, SSize incy, RP rp) { BLAS(zdotu)(reinterpret_cast(rp), static_cast(n), reinterpret_cast(static_cast(xp)), static_cast(incx), reinterpret_cast(static_cast(yp)), static_cast(incy));} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,google-readability-casting) : adapt types + +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_c{} && is_assignable{}, int> =0> void dotc(SSize n, XP xp, SSize incx, YP yp, SSize incy, RP rp) { + // std::clog << "using cdotc void\n"; + BLAS(cdotc)(reinterpret_cast(rp), static_cast(n), reinterpret_cast(static_cast(xp)), static_cast(incx), reinterpret_cast(static_cast(yp)), static_cast(incy)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + // BLAS(cgemv)('C', n, 1, std::complex{1.0F, 0.0F}, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy, std::complex{0.0F, 0.0F}, reinterpret_cast(static_cast(rp)), 1); // NOLINT(readability-suspicious-call-argument) +} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,google-readability-casting) : adapt types +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_z{} && is_assignable{}, int> =0> void dotc(ssize_t n, XP xp, ptrdiff_t incx, YP yp, ptrdiff_t incy, RP rp) { BLAS(zdotc)(reinterpret_cast(rp), n, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,google-readability-casting) : adapt types +#else +// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) +// TODO(correaa) implement workaround for bug in Apple Accelerate BLAS ? https://stackoverflow.com/a/77017238/225186 +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_c{} && is_assignable{}, int> =0> void dotu(SSize n, XP xp, SSize incx, YP yp, SSize incy, RP rp) { + // [[maybe_unused]] static bool const use_cdotu = []{ + // std::array, 3> const v1 = {std::complex{1.0F, 2.0F}, std::complex{3.0F, 4.0F}, std::complex{ 5.0F, 6.0F}}; + // std::array, 3> const v2 = {std::complex{7.0F, 8.0F}, std::complex{9.0F, 10.0F}, std::complex{11.0F, 12.0F}}; + + // Complex_float rr{-1.0F, -2.0F}; + // rr = BLAS(cdotu)(3, v1.data(), 1, v2.data(), 1); + + // if( !(std::abs(rr.real - std::real(v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2])) < 1.0e-8) + // || !(std::abs(rr.imag - std::imag(v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2])) < 1.0e-8) ) { + // std::clog + // << "multi::blas setup warning: when using cdotu that returns non-void,\n" + // << "cdotu returned (" << rr.real << ", " << rr.imag << ", it should return " << v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] << '\n' + // << "This problem appears with BLAS and OpenBLAS 32bit.\n" + // << "... falling back to cgemv\n"; + // { + // std::complex gemv_rr{-12.345F, -54.321F}; + // BLAS(cgemv)('N', 1, v1.size(), std::complex{1.0F, 0.0F}, v1.data(), 1, v2.data(), 1, std::complex{0.0F, 0.0F}, &gemv_rr, 1); + // std::clog << "cgemv gives " << gemv_rr << ", it should give " << v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] << '\n'; + + // if( !(std::abs(gemv_rr - (v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2])) < 1.0e-8) ) { + // std::clog << "gemv also failed" << '\n'; + // } + // return false; // dot not use cdotu + // } + // } + // return true; // use cdotu + // }(); + // if(use_cdotu) { + // Complex_float const rr = BLAS(cdotu)( n, reinterpret_cast(static_cast(x)), incx, reinterpret_cast(static_cast(y)), incy); std::memcpy(reinterpret_cast*>(static_cast(rp))->data(), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*rp)); + // } else { + BLAS(cgemv)('N', 1, static_cast(n), std::complex{1.0F, 0.0F}, reinterpret_cast(static_cast(xp)), static_cast(incx), reinterpret_cast(static_cast(yp)), static_cast(incy), std::complex{0.0F, 0.0F}, reinterpret_cast(static_cast(rp)), 1); // NOLINT(readability-suspicious-call-argument) + // } +} +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_z{} && is_assignable{}, int> =0> void dotu(SSize n, XP xp, SSize incx, YP yp, SSize incy, RP rp) { + // auto const rr = BLAS(zdotu)( n, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy); std::memcpy(reinterpret_cast*>(static_cast(rp))->data(), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*rp)); + BLAS(zgemv)('N', 1, static_cast(n), std::complex{1.0, 0.0}, reinterpret_cast(static_cast(xp)), static_cast(incx), reinterpret_cast(static_cast(yp)), static_cast(incy), std::complex{0.0, 0.0}, reinterpret_cast(static_cast(rp)), 1); // NOLINT(readability-suspicious-call-argument) +} + +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_c{} && is_assignable{}, int> =0> void dotc(ssize_t n, XP xp, ptrdiff_t incx, YP yp, ptrdiff_t incy, RP rp) { + // std::clog << "using cdotc non void\n"; + // c + auto const rr = BLAS(cdotc)( n, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy); std::memcpy(reinterpret_cast*>(static_cast(rp))->data(), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*rp)); + // BLAS(cgemv)('N', 1, n, std::complex{1.0F, 0.0F}, reinterpret_cast(static_cast(xp)), incx, reinterpret_cast(static_cast(yp)), incy, std::complex{0.0F, 0.0F}, reinterpret_cast(static_cast(rp)), 1); // NOLINT(readability-suspicious-call-argument) +} + +template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_z{} && is_assignable{}, int> =0> void dotc(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) {auto const rr = BLAS(zdotc)( n, reinterpret_cast(static_cast(x)), incx, reinterpret_cast(static_cast(y)), incy); std::memcpy(reinterpret_cast*>(static_cast(r))->data(), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r));} // NOSONAR +// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) +#endif +// NOLINTEND(modernize-use-constraints) for C++20 + +} // end namespace core +#else +#define xdotu(T) template v dotu(S n, add_const_ptr_t x, S incx, add_const_ptr_t y, S incy, add_ptr_t r){ BLAS(T##dotu_sub)(BC(n), x, BC(incx), y, BC(incy), r); } +#define xdotc(T) template v dotc(S n, add_const_ptr_t x, S incx, add_const_ptr_t y, S incy, add_ptr_t r){ BLAS(T##dotc_sub)(BC(n), x, BC(incx), y, BC(incy), r); } + +namespace core { + xdotu(c) xdotu(z) + xdotc(c) xdotc(z) +} + +#undef xdotu +#undef xdotc +#endif + +namespace core { + template auto dot(S n, s const& b, s const* x, S incx, s const* y, S incy) -> s {return BLAS(sdsdot)(BC(n), b, x, BC(incx), y, BC(incy));} // NOLINT(readability-identifier-length) conventional BLAS name +} // end namespace core + +#define xasum(T, TT) template auto asum (S n, T const* x, S incx){return BLAS(TT##asum )(BC(n), x, BC(incx)) ;} // NOLINT(readability-identifier-length) conventional BLAS name +#define ixamax(T) template auto iamax(S n, T const* x, S incx){return BLAS(i##T##amax)(BC(n), x, BC(incx)) - 1;} // NOLINT(readability-identifier-length) conventional BLAS name + +namespace core { + +// NOLINTBEGIN(modernize-use-constraints) for C++20 +template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_s{} && std::is_assignable{} , int> =0> void asum(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(sasum) (n, reinterpret_cast(static_cast(x)), incx); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(s));} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) +template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_d{} && std::is_assignable{} , int> =0> void asum(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(dasum) (n, reinterpret_cast(static_cast(x)), incx); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(d));} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) + +template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_s{} && std::is_assignable{}, int> =0> void asum(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(scasum)(n, reinterpret_cast(static_cast(x)), incx); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(s));} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) +template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_d{} && std::is_assignable{}, int> =0> void asum(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(dzasum)(n, reinterpret_cast(static_cast(x)), incx); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(d));} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) + +// TODO(correaa) implement workaround for bug in Apple Accelerate BLAS ? https://stackoverflow.com/a/77017238/225186 +template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_s{} && std::is_assignable{} , int> =0> void nrm2(SSize n, XP x, SSize incx, RP r) {auto rr = BLAS(snrm2) (static_cast(n), reinterpret_cast(static_cast(x)), static_cast(incx)); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(s));} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) +template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_d{} && std::is_assignable{} , int> =0> void nrm2(SSize n, XP x, SSize incx, RP r) {auto rr = BLAS(dnrm2) (static_cast(n), reinterpret_cast(static_cast(x)), static_cast(incx)); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(d));} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) + +template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_s{} && std::is_assignable{}, int> =0> void nrm2(SSize n, XP x, SSize incx, RP r) {auto rr = BLAS(scnrm2)(static_cast(n), reinterpret_cast(static_cast(x)), static_cast(incx)); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(s));} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) +template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} && is_d{} && std::is_assignable{}, int> =0> void nrm2(SSize n, XP x, SSize incx, RP r) {auto rr = BLAS(dznrm2)(static_cast(n), reinterpret_cast(static_cast(x)), static_cast(incx)); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(d));} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) +// NOLINTEND(modernize-use-constraints) for C++20 + + ixamax(s) ixamax(d) ixamax(c) ixamax(z) +} // end namespace core + +#undef xasum +#undef ixamax + +/////////////////////////////////////////////////////////////////////////////// +// LEVEL2 +//#define xgemv(T) template v gemv(C trans, S m, S n, T const& a, T const* A, S lda, T const* X, S incx, T beta, T* Y, S incy ){BLAS(T##gemv)(trans, BC(m), BC(n), a, A, BC(lda), X, BC(incx), beta, Y, BC(incy) );} +//#define xger(T) template< class S> v ger ( S m, S n, T const& a, T const* X, S incx, T const* Y, S incy, T* A, S lda){BLAS(T##ger )( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} +// template< class S> v ger ( S m, S n, c const& a, c const* X, S incx, c const* Y, S incy, c* A, S lda){BLAS(cgeru )( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} +// template< class S> v ger ( S m, S n, z const& a, z const* X, S incx, z const* Y, S incy, z* A, S lda){BLAS(zgeru )( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} +//#define xgeru(T) template< class S> v geru( S m, S n, T const& a, T const* X, S incx, T const* Y, S incy, T* A, S lda){BLAS(T##geru)( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} +//#define xgerc(T) template< class S> v gerc( S m, S n, T const& a, T const* X, S incx, T const* Y, S incy, T* A, S lda){BLAS(T##gerc)( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} + +namespace core { + +using std::enable_if_t; +using std::is_assignable; + +using size_t = INT; +using ssize_t = std::make_signed_t; + +// NOLINTBEGIN(modernize-use-constraints) for C++20 +template{} && is_s{} && is_s{} && is_assignable{}, int> =0> void gemv(char trans, size_t m, size_t n, A const* a, M* ma, size_t lda, X* x, size_t incx, B const* b, Y* y, size_t incy) {BLAS(sgemv)(trans, m, n, *a, reinterpret_cast(ma), lda, reinterpret_cast(x), incx, *b, reinterpret_cast(y), incy);} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) // NOSONAR wrapped func has 11 params +template{} && is_d{} && is_d{} && is_assignable{}, int> =0> void gemv(char trans, size_t m, size_t n, A const* a, M* ma, size_t lda, X* x, size_t incx, B const* b, Y* y, size_t incy) {BLAS(dgemv)(trans, m, n, *a, reinterpret_cast(ma), lda, reinterpret_cast(x), incx, *b, reinterpret_cast(y), incy);} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) // NOSONAR wrapped func has 11 params +template{} && is_c{} && is_c{} && is_assignable{}, int> =0> void gemv(char trans, size_t m, size_t n, A const* a, M* ma, size_t lda, X* x, size_t incx, B const* b, Y* y, size_t incy) {BLAS(cgemv)(trans, m, n, *a, reinterpret_cast(ma), lda, reinterpret_cast(x), incx, *b, reinterpret_cast(y), incy);} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) // NOSONAR wrapped func has 11 params +template{} && is_z{} && is_z{} && is_assignable()*std::declval()*std::declval())+(std::declval()*std::declval()))>{}, int> =0> void gemv(char trans, size_t m, size_t n, A const* a, M* ma, size_t lda, X* x, size_t incx, B const* b, Y* y, size_t incy) { // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) //NOSONAR wrapped func has 11 params + BLAS(zgemv)(trans, m, n, *a, reinterpret_cast(ma), lda, reinterpret_cast(x), incx, *b, reinterpret_cast(y), incy); // NOLINT(fuchsia-default-arguments-calls,google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) // NOSONAR +} +// NOLINTEND(modernize-use-constraints) for C++20 + +// TODO(correaa) implement get, geru, gerc + +using s = float; +using d = double; +using c = std::complex; +using z = std::complex; +using v = void; + +template +struct blas2 {}; + +template<> struct blas2 {template static v trsv(As... args) {BLAS(strsv)(args...);}}; +template<> struct blas2 {template static v trsv(As... args) {BLAS(dtrsv)(args...);}}; +template<> struct blas2 {template static v trsv(As... args) {BLAS(ctrsv)(args...);}}; +template<> struct blas2 {template static auto trsv(As... args) -> decltype(BLAS(ztrsv)(args...)) {BLAS(ztrsv)(args...);}}; + +} // end namespace core + +/////////////////////////////////////////////////////////////////////////////// +// LEVEL 3 + +namespace core { + +using std::is_convertible_v; +using std::pointer_traits; +using std::enable_if_t; +using std::max; + +#define xsyrk(T) \ +template::element_type, class BETA, class CCP, class CC = typename pointer_traits::element_type, \ +enable_if_t< /* NOLINT(modernize-use-constraints) for C++20 */ \ + is_##T{} && is_##T{} && is_assignable_v && \ + is_convertible_v && is_convertible_v \ +, int> =0> \ +v syrk( UL uplo, C transA, S n, S k, ALPHA const* alpha, AAP aa, S lda, BETA const* beta, CCP cc, S ldc) /*NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length)*/ \ +/*=delete;*/ \ +{ \ + if(transA == 'N' || transA == 'n') { BOOST_MULTI_ASSERT1( lda >= max(S{1}, n) ); } \ + if(transA != 'N' && transA != 'n') { BOOST_MULTI_ASSERT1( lda >= max(S{1}, k) ); } \ + BOOST_MULTI_ASSERT1( ldc >= max(S{1}, n) ); \ + BLAS(T##syrk)( uplo, transA, static_cast(BC(n)), static_cast(BC(k)), *reinterpret_cast(alpha), aa, static_cast(BC(lda)), *reinterpret_cast(beta), cc, static_cast(BC(ldc))); /*NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)*/ \ +} \ + +#define xherk(T) \ +template::element_type, class BETA, class CCP, class CC = typename pointer_traits::element_type, class Real = typename T::value_type \ + , enable_if_t< /* NOLINT(modernize-use-constraints) for C++20 */ \ + is_##T{} && is_##T{} && is_assignable_v && \ + is_convertible_v && is_convertible_v \ + , int> =0 \ +> \ +v herk( UL uplo, C transA, S n, S k, ALPHA const* alpha, AAP aa, S lda, BETA const* beta, CCP cc, S ldc) /*NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length)*/ \ +/*=delete;*/ \ +{ \ + if(transA == 'N' || transA == 'n') { BOOST_MULTI_ASSERT1( lda >= max(S{1}, n) ); } /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ + if(transA != 'N' && transA != 'n') { BOOST_MULTI_ASSERT1( lda >= max(S{1}, k) ); } \ + BOOST_MULTI_ASSERT1( ldc >= max(S{1}, n) ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ + /*BOOST_MULTI_MARK_SCOPE("cpu_herk");*/ \ + BLAS(T##herk)( uplo, transA, static_cast(BC(n)), static_cast(BC(k)), *reinterpret_cast(alpha), reinterpret_cast(aa), static_cast(BC(lda)), *reinterpret_cast(beta), reinterpret_cast(cc), static_cast(BC(ldc))); /*NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-macro-parentheses)*/ \ +} \ + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast" +#endif + +#define xgemm(T) \ +template::element_type, class BBP, class BB = typename pointer_traits::element_type, class BETA, class CCP, class CC = typename pointer_traits::element_type, \ +enable_if_t< /* NOLINT(modernize-use-constraints) for C++20 */ \ + is_##T{} && is_##T{} && is_##T{} && \ + is_convertible_v && is_convertible_v && is_convertible_v \ +, int> =0> \ +v gemm(char transA, char transB, SSize m, SSize n, SSize k, ALPHA const* alpha, AAP aa, SSize lda, BBP bb, SSize ldb, BETA const* beta, CCP cc, SSize ldc) { /*NOLINT(bugprone-easily-swappable-parameters)*/ \ + using std::max; \ + BOOST_MULTI_ASSERT1((transA != 'N') || (lda >= max(SSize{1}, m))); \ + BOOST_MULTI_ASSERT1((transA == 'N') || (lda >= max(SSize{1}, k))); \ + BOOST_MULTI_ASSERT1((transB != 'N') || (ldb >= max(SSize{1}, k))); \ + BOOST_MULTI_ASSERT1((transB == 'N') || (ldb >= max(SSize{1}, n))); \ +\ + BOOST_MULTI_ASSERT1( aa != cc ); \ + BOOST_MULTI_ASSERT1( bb != cc ); \ +\ + if(!( ldc >= max(SSize{1}, m) )) { throw std::logic_error("failed 'ldc >= max(1, m)' with ldc = "+ std::to_string(ldc) +" and m = "+ std::to_string(m)); } \ + if(*beta != 0.0) { BOOST_MULTI_ASSERT1((is_assignable()*std::declval()*std::declval()) + (std::declval()*std::declval()))> {})); } \ + BLAS(T##gemm)(transA, transB, static_cast(BC(m)), static_cast(BC(n)), static_cast(BC(k)), *reinterpret_cast(alpha), reinterpret_cast(static_cast(aa)), static_cast(BC(lda)), reinterpret_cast(static_cast(bb)), static_cast(BC(ldb)), *reinterpret_cast(beta), reinterpret_cast(static_cast(cc)) /*NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-macro-parentheses)*/ /*TODO(correaa) check constness*/, static_cast(BC(ldc))); \ +} \ + +// NOLINTNEXTLINE(readability-identifier-length) conventional BLAS name +xgemm(s) xgemm(d) xgemm(c) xgemm(z) // NOLINT(modernize-use-constraints,readability-function-cognitive-complexity) : 36 of 25 +#undef xgemm + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#define xtrsm(T) \ +template::element_type, class BBP, class BB = typename pointer_traits::element_type, \ +enable_if_t< /* NOLINT(modernize-use-constraints) for C++20 */ \ + is_##T{} && is_##T{} && is_assignable{} && is_assignable{} && \ + is_convertible_v && is_convertible_v \ +,int> =0> \ +v trsm(char side, char uplo, char transA, char diag, SSize m, SSize n, ALPHA alpha, AAP aa, SSize lda, BBP bb, SSize ldb) { /*NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length)*/ \ + assert( side == 'L' || side == 'R' ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ + assert( uplo == 'U' || uplo == 'L' ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ + assert( transA == 'N' || transA == 'T' || transA == 'C' ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ + assert( diag == 'U' || diag == 'N' ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ + BOOST_MULTI_ASSERT1( m >= 0 && n >= 0 ); \ + using std::max; \ + if(side == 'L') {BOOST_MULTI_ASSERT1( lda >= max(SSize{1}, m) );} /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ + if(side == 'R') {BOOST_MULTI_ASSERT1( lda >= max(SSize{1}, n) );} \ + BOOST_MULTI_ASSERT1( ldb >= max(SSize{1}, m) ); \ + BLAS(T##trsm)(side, uplo, transA, diag, static_cast(BC(m)), static_cast(BC(n)), alpha, reinterpret_cast(static_cast(aa)), static_cast(BC(lda)), reinterpret_cast(static_cast(bb)), static_cast(BC(ldb))); /*NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-macro-parentheses)*/ \ +} \ + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundefined-reinterpret-cast" +#endif + +xtrsm(s) xtrsm(d) xtrsm(c) xtrsm(z) // NOLINT(modernize-use-constraints,readability-function-cognitive-complexity) : 29 of 25 +#undef xtrsm + +xsyrk(s) xsyrk(d) xsyrk(c) xsyrk(z) // NOLINT(modernize-use-constraints) for C++20 +#undef xsyrk + xherk(c) xherk(z) // NOLINT(modernize-use-constraints) for C++20 + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // end namespace core + +#undef xherk + +#undef BC + +struct context { // stateless (and thread safe) + template + static auto scal(As... args) + ->decltype(core::scal(args...)) { + return core::scal(args...); } + + template + static auto copy(As... args) noexcept + ->decltype(core::copy(args...)) { + return core::copy(args...); } + + template + static auto swap(As... args) noexcept + ->decltype(core::swap(args...)) { + return core::swap(args...); } + + template + static auto axpy(As... args) + ->decltype(core::axpy(args...)) { + return core::axpy(args...); } + + template + static auto dot(As... args) + ->decltype(core::dot(args...)) { + return core::dot(args...); } + + template + static auto dotc(As... args) + ->decltype(core::dotc(args...)) { + return core::dotc(args...); } + + template + static auto dotu(As... args) + ->decltype(core::dotu(args...)) { + return core::dotu(args...); } + + template + static auto gemm(As&&... args) + ->decltype(core::gemm(std::forward(args)...)) { + return core::gemm(std::forward(args)...); } + + template + static auto gemv(As&&... args) + ->decltype(core::gemv(std::forward(args)...)) { + return core::gemv(std::forward(args)...); } + + template + static auto asum(As... args) + ->decltype(core::asum(args...)) { + return core::asum(args...); } + + template + static auto nrm2(As... args) + -> decltype(auto) + //->decltype(core::nrm2(args...)) { + { return core::nrm2(args...); } + + template + static auto trsm(As&&... args) + ->decltype(core::trsm(std::forward(args)...)) { + return core::trsm(std::forward(args)...); } + + template + static auto herk(As&&... args) + ->decltype(core::herk(std::forward(args)...)) { + return core::herk(std::forward(args)...); } +}; + +template struct is_context : std::false_type {}; + +template<> struct is_context : std::true_type {}; +template<> struct is_context : std::true_type {}; +template<> struct is_context : std::true_type {}; +template<> struct is_context : std::true_type {}; + +template<> struct is_context : std::true_type {}; + +template::element_type*>, int> =0> // NOLINT(modernize-use-constraints) for C++20 +auto default_context_of(TPtr const& /*unused*/) -> blas::context* { + static blas::context dc; + return &dc; +} + +} // end namespace boost::multi::blas + +#endif diff --git a/include/boost/multi/adaptors/blas/cuda/tests/gemm.cpp b/include/boost/multi/adaptors/blas/cuda/tests/gemm.cpp new file mode 100644 index 000000000..b6448cefa --- /dev/null +++ b/include/boost/multi/adaptors/blas/cuda/tests/gemm.cpp @@ -0,0 +1,161 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// #include // TODO(correaa) convert into lightweight test +#include + +#include +#include + +#include +#include + +#include // for abs // IWYU pragma: keep +// IWYU pragma: no_include // for abs +#include // for exception +#include + +namespace multi = boost::multi; + +BOOST_AUTO_TEST_CASE(const multi_adaptors_blas_cuda_gemm_complex_3x2_3x2){ + using complex = std::complex; complex const I{0, 1}; + namespace blas = multi::blas; + multi::array const a = { + {1.0 + 2.0*I, 5.0 + 2.0*I}, + {9.0 - 1.0*I, 9.0 + 1.0*I}, + {1.0 + 1.0*I, 2.0 + 2.0*I} + }; + multi::array const b = { + { 11.0 - 2.0*I, 5.0 + 2.0*I}, + { 7.0 - 3.0*I, 2.0 + 1.0*I}, + { 8.0 - 1.0*I, 1.0 + 1.0*I} + }; + { + { + multi::array c({2, 2}); + c = blas::gemm(1., blas::H(a), b); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE( c[1][0] == 125.0 - 84.0*I ); + } + } + { + multi::cuda::array const a_gpu = a; + multi::cuda::array const b_gpu = b; + { + multi::cuda::array c_gpu({2, 2}); + c_gpu = blas::gemm(1., blas::H(a_gpu), b_gpu); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE( c_gpu[1][0] == 125.0 - 84.0*I ); + } + { + auto c_gpu =+ blas::gemm(1.0, blas::H(a_gpu), b_gpu); + BOOST_REQUIRE( c_gpu[1][0] == 125.0 - 84.0*I ); + } + } + { + multi::cuda::managed::array const a_gpu = a; + multi::cuda::managed::array const b_gpu = b; + { + multi::cuda::managed::array c_gpu({2, 2}); + blas::gemm(1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE( c_gpu[1][0] == 125.0 - 84.0*I ); + } + { + auto c_gpu =+ blas::gemm(1.0, blas::H(a_gpu), b_gpu); + BOOST_REQUIRE( c_gpu[1][0] == 125.0 - 84.0*I ); + } + } +} + +//BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_gemm_complex_3x2_3x2_with_context){ +// using complex = std::complex; complex const I{0, 1}; +// namespace blas = multi::blas; +// multi::array const a = { +// {1. + 2.*I, 5. + 2.*I}, +// {9. - 1.*I, 9. + 1.*I}, +// {1. + 1.*I, 2. + 2.*I} +// }; +// multi::array const b = { +// { 11. - 2.*I, 5. + 2.*I}, +// { 7. - 3.*I, 2. + 1.*I}, +// { 8. - 1.*I, 1. + 1.*I} +// }; +// { +// { +// multi::blas::context ctx; +// multi::array c({2, 2}); +// blas::gemm(ctx, 1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ +// BOOST_REQUIRE( c[1][0] == 125.-84.*I ); +// } +// } +// { +// multi::cublas::context ctx; +// multi::cuda::array const a_gpu = a; +// multi::cuda::array const b_gpu = b; +// { +// multi::cuda::array c_gpu({2, 2}); +// blas::gemm(ctx, 1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆ +// BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); +// } +// { +// auto c_gpu =+ blas::gemm(&ctx, blas::H(a_gpu), b_gpu); +// BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); +// } +// } +// { +// multi::cublas::context ctx; +// multi::cuda::managed::array const a_gpu = a; +// multi::cuda::managed::array const b_gpu = b; +// { +// multi::cuda::managed::array c_gpu({2, 2}); +// blas::gemm(ctx, 1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆ +// BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); +// } +// { +// auto c_gpu =+ blas::gemm(&ctx, blas::H(a_gpu), b_gpu); +// BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); +// } +// } +//} + +#if 0 +BOOST_AUTO_TEST_CASE(const multi_adaptors_blas_cuda_gemm_context_timing){ + using complex = std::complex;//complex const I{0, 1}; + + multi::array A({1000, 1000}); + multi::array B( {1000, 1000}); + multi::array C({size(A), size(~B)}); + A[99][99] = B[11][22] = C[33][44] = 1.0; + std::cerr<< "memory " << (A.num_elements()+ B.num_elements() + C.num_elements())*sizeof(complex)/1e6 <<" MB"<{0., 10.}, g=std::mt19937{}]() mutable{return complex{d(g), d(g)};}; + std::generate(A.elements().begin(), A.elements().end(), rand); + std::generate(B.elements().begin(), B.elements().end(), rand); + } + namespace blas = multi::blas; + { + boost::timer::auto_cpu_timer t; // 2.398206s + for(auto i = 0; i != 10; ++i){ + blas::context ctx; + blas::gemm(ctx, 1.0, A, B, 0.0, C); + } + } + using device_array = multi::cuda::array; + { + device_array A_gpu = A, B_gpu = B, C_gpu({size(A), size(~B)}); + + boost::timer::auto_cpu_timer t; // 0.707426s + for(auto i = 0; i != 10; ++i){ + multi::cublas::context ctx; + blas::gemm(ctx, 1.0, A_gpu, B_gpu, 0.0, C_gpu); + } + } + { + device_array A_gpu = A, B_gpu = B, C_gpu({size(A), size(~B)}); + + boost::timer::auto_cpu_timer t; // 0.613534s + multi::cublas::context ctx; + for(auto i = 0; i != 10; ++i) blas::gemm(ctx, 1.0, A_gpu, B_gpu, 0.0, C_gpu); + } +} +#endif diff --git a/include/multi/adaptors/blas/cuda/tests/gemm.su b/include/boost/multi/adaptors/blas/cuda/tests/gemm.su similarity index 100% rename from include/multi/adaptors/blas/cuda/tests/gemm.su rename to include/boost/multi/adaptors/blas/cuda/tests/gemm.su diff --git a/include/boost/multi/adaptors/blas/cuda/tests/iamax.cpp b/include/boost/multi/adaptors/blas/cuda/tests/iamax.cpp new file mode 100644 index 000000000..dc250e876 --- /dev/null +++ b/include/boost/multi/adaptors/blas/cuda/tests/iamax.cpp @@ -0,0 +1,31 @@ +// © Alfredo A. Correa 2019-2024 + +#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuBLAS iamax" +#define BOOST_TEST_DYN_LINK + +// #include + +#include "../../../../adaptors/blas.hpp" +#include "../../../../adaptors/cuda.hpp" +#include "../../../../adaptors/blas/cuda.hpp" + +namespace multi = boost::multi; + +BOOST_AUTO_TEST_CASE(const multi_adaptors_blas_cuda_iamax){ + using complex = std::complex; complex const I{0.0, 1.0}; + { + multi::array const A = {1.0 + 2.0*I, 2.0, 3.0 + 3.0*I, 4.0}; + using multi::blas::iamax; + BOOST_REQUIRE( iamax(A) == 2 ); + } + { + multi::cuda::array const A = {1.0 + 2.0*I, 2.0, 3.0 + 3.0*I, 4.0}; + using multi::blas::iamax; + BOOST_REQUIRE( iamax(A) == 2 ); + } + { + multi::cuda::managed::array const A = {1.0 + 2.0*I, 2.0, 3.0 + 3.0*I, 4.0}; + using multi::blas::iamax; + BOOST_REQUIRE( iamax(A) == 2 ); + } +} diff --git a/include/multi/adaptors/blas/dot.hpp b/include/boost/multi/adaptors/blas/dot.hpp similarity index 56% rename from include/multi/adaptors/blas/dot.hpp rename to include/boost/multi/adaptors/blas/dot.hpp index 99bec7efe..941540458 100644 --- a/include/multi/adaptors/blas/dot.hpp +++ b/include/boost/multi/adaptors/blas/dot.hpp @@ -1,12 +1,13 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_BLAS_DOT_HPP -#define MULTI_ADAPTORS_BLAS_DOT_HPP +#ifndef BOOST_MULTI_ADAPTORS_BLAS_DOT_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_DOT_HPP -#include "../blas/core.hpp" -#include "../blas/numeric.hpp" // for is_complex -#include "../blas/operations.hpp" // for blas::C +#include +#include // for is_complex +#include // for blas::C namespace boost::multi::blas { @@ -16,20 +17,21 @@ using core::dotc; template auto dot_n(Context&& ctxt, XIt x_first, Size count, YIt y_first, RPtr rp) { - if constexpr(is_complex{}) { - if constexpr(!is_conjugated{} and !is_conjugated{}) {ctxt->dotu(count, base(x_first) , stride(x_first), base(y_first), stride(y_first), rp);} - else if constexpr(!is_conjugated{} and is_conjugated{}) {ctxt->dotc(count, underlying(base(y_first)), stride(y_first), base(x_first), stride(x_first), rp);} - else if constexpr( is_conjugated{} and !is_conjugated{}) {ctxt->dotc(count, underlying(base(x_first)), stride(x_first), base(y_first), stride(y_first), rp);} - else if constexpr( is_conjugated{} and is_conjugated{}) {static_assert(!sizeof(XIt*), "not implemented in blas");} + if constexpr(! is_complex{}) { + std::forward(ctxt)->dot (count, x_first.base() , stride(x_first), y_first.base(), stride(y_first), rp); } else { - ctxt->dot (count, base(x_first) , stride(x_first), base(y_first), stride(y_first), rp); + if constexpr(!is_conjugated{} && !is_conjugated{}) { std::forward(ctxt)->dotu(count, x_first.base() , stride(x_first), y_first.base(), stride(y_first), rp); } + else if constexpr(!is_conjugated{} && is_conjugated{}) { std::forward(ctxt)->dotc(count, underlying(y_first.base()), stride(y_first), x_first.base(), stride(x_first), rp); } + else if constexpr( is_conjugated{} && !is_conjugated{}) { std::forward(ctxt)->dotc(count, underlying(x_first.base()), stride(x_first), y_first.base(), stride(y_first), rp); } + else if constexpr( is_conjugated{} && is_conjugated{}) { static_assert(!sizeof(XIt*), "not implemented in blas"); } } + struct{XIt x_last; YIt y_last;} ret{x_first + count, y_first + count}; return ret; } template -auto dot_n(XIt x_first, Size count, YIt y_first, RPtr rp) {//->decltype(dot_n(blas::context{}, x_first, count, y_first, rp)){ +auto dot_n(XIt x_first, Size count, YIt y_first, RPtr rp) { //->decltype(dot_n(blas::context{}, x_first, count, y_first, rp)){ if constexpr(is_conjugated{}) { auto ctxtp = blas::default_context_of(underlying(x_first.base())); return dot_n(ctxtp, x_first, count, y_first, rp); @@ -39,13 +41,15 @@ auto dot_n(XIt x_first, Size count, YIt y_first, RPtr rp) {//->decltype(dot_n(bl } } -template>::value, int> =0> +template>::value, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 auto dot(Context ctxt, X1D const& x, Y1D const& y, R&& res) -> R&& { // NOLINT(readability-identifier-length) res = \sum_i x_i y_i assert( size(x) == size(y) ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) return blas::dot_n(ctxt, begin(x), size(x), begin(y), &res), std::forward(res); } -template>::value, int> =0> +template>::value, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 auto dot(Context ctxt, X1D const& x, Y1D const& y, R&& res) -> R&& { // NOLINT(readability-identifier-length) res = \sum_i x_i y_i assert( size(x) == size(y) ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) return blas::dot_n(ctxt, begin(x), size(x), begin(y), res.base()), std::forward(res); @@ -76,12 +80,18 @@ class dot_ptr { public: constexpr explicit operator bool() const {return true;} -// dot_ptr(dot_ptr const&) = default; template friend constexpr auto copy_n(dot_ptr first, Size2 count, ItOut d_first) - ->decltype(blas::dot_n(std::declval(), std::declval(), Size{} , std::declval(), d_first), d_first + count) { + ->decltype(blas::dot_n(std::declval(), std::declval(), Size{} , std::declval(), d_first), d_first + count) { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) assert(count == 1); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - return blas::dot_n(first.ctxt_ , first.x_first_ , first.count_, first.y_first_ , d_first), d_first + count; +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + return blas::dot_n(first.ctxt_ , first.x_first_ , first.count_, first.y_first_ , d_first), d_first + count; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif } template @@ -97,26 +107,44 @@ struct dot_ref : private Ptr { assert(( size(x) == size(y) )); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) } - constexpr auto operator&() const& -> Ptr const& {return *this;} // NOLINT(google-runtime-operator) reference type + constexpr auto operator&() const& -> Ptr const& {return *this;} // NOLINT(google-runtime-operator) reference type // NOSONAR auto decay() const -> decay_type {decay_type ret; copy_n(operator&(), 1, &ret); return ret;} // NOLINT(fuchsia-default-arguments-calls) - operator decay_type() const {return decay();} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions,hicpp-explicit-conversion) to allow terse syntax -#if not defined(__CUDACC__) or not defined(__INTEL_COMPILER) + operator decay_type() const {return decay();} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions,hicpp-explicit-conversion) //NOSONAR to allow terse syntax +#if ! defined(__CUDACC__) || ! defined(__INTEL_COMPILER) friend auto operator*(decay_type const& lhs, dot_ref const& self) {return lhs*self.decay();} #endif auto operator+() const -> decay_type {return decay();} - auto operator==(dot_ref const& other) const -> bool {return decay() == other.decay();} - auto operator!=(dot_ref const& other) const -> bool {return decay() != other.decay();} - - template - auto operator==(Other const& other) const - ->decltype(decay()==other) { - return decay()==other; } - template - auto operator!=(Other const& other) const - ->decltype(decay()!=other) { - return decay()!=other; } + // friend auto operator==(dot_ref const& self, dot_ref const& other) -> bool {return self.decay() == other.decay();} + // friend auto operator!=(dot_ref const& self, dot_ref const& other) -> bool {return self.decay() != other.decay();} + + friend auto operator==(dot_ref const& self, dot_ref const& other) -> bool { + return self.decay() == other.decay(); + } + friend auto operator!=(dot_ref const& self, dot_ref const& other) -> bool { + return self.decay() != other.decay(); + } + + template // , class = std::enable_if_t > > + friend auto operator==(dot_ref const& self, Other const& other) -> bool { + return self.decay() == other; + } + template // , class = std::enable_if_t > > + friend auto operator!=(dot_ref const& self, Other const& other) -> bool { + return self.decay() != other; + } + template > > // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + friend auto operator==(Other const& other, dot_ref const& self) -> bool { + return other == self.decay(); + } + template > > // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + friend auto operator!=(Other const& other, dot_ref const& self) -> bool { + return other != self.decay(); + } + }; template [[nodiscard]] @@ -124,14 +152,9 @@ auto dot(Context ctxt, X const& x, Y const& y) { // NOLINT(readability-identifi return dot_ref{ctxt, x, y}; } -#if defined __NVCC__ // in place of global -Xcudafe \"--diag_suppress=implicit_return_from_non_void_function\" - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function - #endif +#ifdef __NVCC__ // in place of global -Xcudafe \"--diag_suppress=implicit_return_from_non_void_function\" + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function #elif defined __NVCOMPILER #pragma diagnostic push #pragma diag_suppress = implicit_return_from_non_void_function @@ -147,14 +170,10 @@ auto dot(X const& x, Y const& y) { // NOLINT(readability-identifier-length) BLA return blas::dot(ctxtp, x, y); } } -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #pragma diagnostic pop - #endif +#ifdef __NVCC__ +#pragma nv_diagnostic pop #elif defined __NVCOMPILER - #pragma diagnostic pop +#pragma diagnostic pop #endif namespace operators { diff --git a/include/multi/adaptors/blas/examples/gemv.cpp b/include/boost/multi/adaptors/blas/examples/gemv.cpp similarity index 80% rename from include/multi/adaptors/blas/examples/gemv.cpp rename to include/boost/multi/adaptors/blas/examples/gemv.cpp index d116fa956..3cc7eb1fc 100644 --- a/include/multi/adaptors/blas/examples/gemv.cpp +++ b/include/boost/multi/adaptors/blas/examples/gemv.cpp @@ -4,13 +4,14 @@ #include int main() { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, hicpp-avoid-c-arrays, modernize-avoid-c-arrays) test legacy types float matA[3][3] = { {1.1, 2.2, 3.3}, {4.4, 5.5, 6.6}, {7.7, 8.8, 9.9}, }; - float vecB[3] = {1.0, 2.0, 3.0}; - float vecC[3] = {0.0, 0.0, 0.0}; + float vecB[3] = {1.0, 2.0, 3.0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types + float vecC[3] = {0.0, 0.0, 0.0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types namespace multi = boost::multi; diff --git a/include/boost/multi/adaptors/blas/filling.hpp b/include/boost/multi/adaptors/blas/filling.hpp new file mode 100644 index 000000000..7c028f15a --- /dev/null +++ b/include/boost/multi/adaptors/blas/filling.hpp @@ -0,0 +1,42 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_FILLING_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_FILLING_HPP + +#include + +#include +#include + +namespace boost::multi::blas { + +enum class filling : char { + lower = 'U', + upper = 'L' +}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wswitch-default" +#endif + +inline auto flip(filling side) -> filling { + switch(side) { // NOLINT(clang-diagnostic-switch-default) + case filling::lower: return filling::upper; + case filling::upper: return filling::lower; + } // __builtin_unreachable(); // LCOV_EXCL_LINE + return {}; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +inline auto operator-(filling side) -> filling {return flip(side);} +inline auto operator+(filling side) -> filling {return side;} + +} // end namespace boost::multi::blas + +#endif diff --git a/include/boost/multi/adaptors/blas/gemm.hpp b/include/boost/multi/adaptors/blas/gemm.hpp new file mode 100644 index 000000000..f80b62bd2 --- /dev/null +++ b/include/boost/multi/adaptors/blas/gemm.hpp @@ -0,0 +1,369 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_GEMM_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_GEMM_HPP + +#include +// #include +#include +// #include + +#include // for base, size, begin + +#include // for assert +#include // for nullptr_t +#include // for exception +#include // for iterator_traits +#include // for logic_error +#include // for to_string, operator""s +#include // for enable_if_t, integr... +#include // for forward, declval + +namespace boost::multi::blas { + +using core::gemm; + +template +auto xbase_aux(It const& it, std::true_type const& /*true */) +->decltype(underlying(base(it))) { + return underlying(base(it)); } + +template +auto xbase_aux(It const& it, std::false_type const& /*false*/) +->decltype(base(it)) { + return base(it); } + +template +auto xbase(It const& it) +->decltype(xbase_aux(it, std::integral_constant{}>{})) { + return xbase_aux(it, std::integral_constant{}>{}); } + +#define CTXT std::forward(ctxt) + +template{} && !is_conjugated{}), int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto gemm_n(Context&& ctxt, typename It2DA::element alpha, It2DA a_first, Size a_count, It2DB b_first, typename It2DA::element beta, It2DC c_first) // NOLINT(readability-function-cognitive-complexity) : 125 +{ + assert( (*b_first).size() == (*c_first).size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( a_first.stride()==1 || (*a_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( b_first.stride()==1 || (*b_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( c_first.stride()==1 || (*c_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + if(a_count == 0) { return c_first; } + + if ((*a_first).stride()==1 && (*b_first).stride()==1 && (*c_first).stride()==1) { + if ( a_count==1 && (*b_first).size()==1 ) {CTXT->gemm('N', 'N', (*b_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), (*b_first).size(), a_first.base(), (*a_first).size(), &beta, c_first.base(), (*c_first).size() );} + else if( a_count==1 ) {CTXT->gemm('N', 'N', (*b_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), b_first. stride(), a_first.base(), (*a_first).size(), &beta, c_first.base(), (*c_first).size() );} + else {CTXT->gemm('N', 'N', (*b_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), b_first. stride(), a_first.base(), a_first. stride(), &beta, c_first.base(), c_first. stride());} + }else if((*a_first).stride()==1 && (*b_first).stride()==1 && c_first. stride()==1) { + if (a_count==1) {CTXT->gemm('T', 'T', a_count, (*b_first).size(), (*a_first).size(), &alpha, a_first.base(), a_first. stride(), b_first.base(), (*b_first).size() , &beta, c_first.base(), (*a_first).size() );} + else {CTXT->gemm('T', 'T', a_count, (*b_first).size(), (*a_first).size(), &alpha, a_first.base(), a_first. stride(), b_first.base(), b_first. stride(), &beta, c_first.base(), (*c_first).stride());} + }else if(a_first. stride()==1 && (*b_first).stride()==1 && (*c_first).stride()==1) { + if (a_count==1) {CTXT->gemm('N', 'T', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), b_first. stride(), a_first.base(), (*a_first).stride(), &beta, c_first.base(), a_count );} + else {CTXT->gemm('N', 'T', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), b_first. stride(), a_first.base(), (*a_first).stride(), &beta, c_first.base(), c_first.stride());} + }else if(a_first. stride()==1 && (*b_first).stride()==1 && c_first. stride()==1) { + if (a_count==1) {CTXT->gemm('N', 'T', a_count, (*b_first).size(), (*a_first).size(), &alpha, a_first.base(), (*a_first).stride(), b_first.base(), (*a_first).size() , &beta, c_first.base(), (*b_first).size() );} + else {CTXT->gemm('N', 'T', a_count, (*b_first).size(), (*a_first).size(), &alpha, a_first.base(), (*a_first).stride(), b_first.base(), b_first. stride(), &beta, c_first.base(), (*c_first).stride());} + }else if((*a_first).stride()==1 && b_first.stride()==1 && c_first. stride()==1) { + if (a_count==1 && (*b_first).size()==1) {CTXT->gemm('N', 'N', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), (*b_first).size() , a_first.base(), (*a_first).size() , &beta, c_first.base(), (*c_first).stride());} + else if(a_count==1) {CTXT->gemm('N', 'T', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), (*b_first).stride(), a_first.base(), (*a_first).size() , &beta, c_first.base(), (*c_first).stride());} + else if((*a_first).size() == 1 && (*b_first).size() == 1) + {CTXT->gemm('N', 'N', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), (*b_first).stride(), a_first.base(), a_first. stride(), &beta, c_first.base(), (*c_first).stride());} + else {CTXT->gemm('N', 'T', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), (*b_first).stride(), a_first.base(), a_first. stride(), &beta, c_first.base(), (*c_first).stride());} + }else if((*a_first).stride()==1 && b_first. stride()==1 && (*c_first).stride()==1) { + if (a_count==1) {CTXT->gemm('T', 'N', a_count, (*c_first).size(), (*a_first).size(), &alpha, b_first.base(), (*b_first).stride(), a_first.base(), (*a_first).size(), &beta, c_first.base(), c_first. stride());} + else {CTXT->gemm('T', 'N', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), (*b_first).stride(), a_first.base(), a_first. stride(), &beta, c_first.base(), c_first. stride());} + }else if(a_first. stride()==1 && b_first.stride( )==1 && c_first. stride()==1) { + if ((*b_first).size()==1) {CTXT->gemm('N', 'N', a_count, (*b_first).size(), (*a_first).size(), &alpha, a_first.base(), (*a_first).stride(), b_first.base(), (*b_first).stride(), &beta, c_first.base(), a_count );} + else {CTXT->gemm('N', 'N', a_count, (*b_first).size(), (*a_first).size(), &alpha, a_first.base(), (*a_first).stride(), b_first.base(), (*b_first).stride(), &beta, c_first.base(), (*c_first).stride());} + }else if(a_first. stride()==1 && b_first.stride( )==1 && (*c_first).stride()==1) { + {CTXT->gemm('T', 'T', (*b_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), (*b_first).stride(), a_first.base(), (*a_first).stride(), &beta, c_first.base(), c_first. stride());} + } else {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + return c_first + a_count; +} + +template{} && is_conjugated{}), int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto gemm_n(Context&& ctxt, typename It2DA::element alpha, It2DA a_first, Size a_count, It2DB b_first, typename It2DA::element beta, It2DC c_first) // NOLINT(readability-function-cognitive-complexity) : 125 +{ + assert( (*b_first).size() == (*c_first).size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( a_first.stride()==1 || (*a_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( b_first.stride()==1 || (*b_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( c_first.stride()==1 || (*c_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + if(a_count == 0) { return c_first; } + + if ((*a_first).stride()==1 && (*b_first).stride()==1 && (*c_first).stride()==1) { + {CTXT->gemm('C', 'N', (*c_first).size(), a_count, (*a_first).size(), &alpha, underlying(b_first.base()), (*b_first).stride(), a_first.base(), (*a_first).size() , &beta, c_first.base(), c_first.stride());} + }else if((*a_first).stride()==1 && b_first. stride()==1 && (*c_first).stride()==1){ + if (a_count==1) {CTXT->gemm('C', 'N', a_count, (*c_first).size(), (*a_first).size(), &alpha, underlying(b_first.base()), (*b_first).stride(), a_first.base(), (*a_first).size() , &beta, c_first.base(), c_first.stride());} + else {CTXT->gemm('C', 'N', (*c_first).size(), a_count, (*a_first).size(), &alpha, underlying(b_first.base()), (*b_first).stride(), a_first.base(), a_first.stride(), &beta, c_first.base(), c_first.stride());} + }else if((*a_first).stride()==1 && b_first. stride()==1 && c_first. stride()==1){ + {CTXT->gemm('C', 'N', (*c_first).size(), a_count, (*a_first).size(), &alpha, underlying(b_first.base()), (*b_first).stride(), a_first.base(), a_first. stride(), &beta, c_first.base(), (*c_first).stride());} + }else if(a_first. stride()==1 && b_first. stride()==1 && c_first. stride()==1){ + {CTXT->gemm('C', 'T', (*c_first).size(), a_count, (*a_first).size(), &alpha, underlying(b_first.base()), (*b_first).stride(), a_first.base(), (*a_first).stride(), &beta, c_first.base(), (*c_first).stride());} + }else if(a_first. stride()==1 && b_first. stride()==1 && (*c_first).stride()==1){ + {CTXT->gemm('C', 'T', a_count, (*c_first).size(), (*a_first).size(), &alpha, underlying(b_first.base()), (*b_first).stride(), a_first.base(), (*a_first).stride(), &beta, c_first.base(), c_first. stride());} + }else{assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + return c_first + a_count; +} + +template{} && !is_conjugated{}), int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto gemm_n(Context&& ctxt, typename It2DA::element alpha, It2DA a_first, Size a_count, It2DB b_first, typename It2DA::element beta, It2DC c_first) // NOLINT(readability-function-cognitive-complexity) : 125 +{ + assert( (*b_first).size() == (*c_first).size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( a_first.stride()==1 || (*a_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( b_first.stride()==1 || (*b_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( c_first.stride()==1 || (*c_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + if(a_count == 0) { return c_first; } + + if (a_first. stride()==1 && (*b_first).stride()==1 && (*c_first).stride()==1){ + if (a_count==1) {CTXT->gemm('N', 'C', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), b_first. stride(), underlying(a_first.base()), (*a_first).stride(), &beta, base(c_first), (*a_first).size()); } + else {CTXT->gemm('N', 'C', (*c_first).size(), a_count, (*a_first).size(), &alpha, b_first.base(), b_first. stride(), underlying(a_first.base()), (*a_first).stride(), &beta, base(c_first), c_first.stride() ); } + } else {throw std::logic_error{"not BLAS-implemented"};} + + return c_first + a_count; +} + +template{} && is_conjugated{}), int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto gemm_n(Context&& ctxt, typename It2DA::element alpha, It2DA a_first, Size a_count, It2DB b_first, typename It2DA::element beta, It2DC c_first) // NOLINT(readability-function-cognitive-complexity) : 125 +{ + assert( (*b_first).size() == (*c_first).size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( a_first.stride()==1 || (*a_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( b_first.stride()==1 || (*b_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( c_first.stride()==1 || (*c_first).stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + if(a_count == 0) { return c_first; } + if (a_first. stride()==1 && b_first. stride()==1 && (*c_first).stride()==1){ + {CTXT->gemm('C', 'C', a_count, (*c_first).size(), (*a_first).size(), &alpha, underlying(base(b_first)), (*b_first).stride(), underlying(base(a_first)), (*a_first).stride(), &beta, base(c_first), c_first. stride());} + } else {throw std::logic_error{"not BLAS-implemented"};} + return c_first + a_count; +} + +#undef CTXT + +template // TODO(correaa) automatic deduction of context +auto gemm_n(typename It2DA::element alpha, It2DA a_first, Size a_count, It2DB b_first, typename It2DA::element beta, It2DC c_first) +->decltype(gemm_n(Context{}, alpha, a_first, a_count, b_first, beta, c_first)) { + return gemm_n(Context{}, alpha, a_first, a_count, b_first, beta, c_first); } + +template +auto gemm(Context&& ctx, typename A::element alpha, A const& a, B const& b, typename A::element beta, C&& c) -> C&& { // NOLINT(readability-identifier-length) BLAS naming + assert( size( a) == size( c) ); + if(! a.is_empty()) {assert( size(~a) == size( b) );} + if constexpr(is_conjugated{}) {blas::gemm (std::forward(ctx), conj(alpha), conj(a), conj(b) , conj(beta), conj(c) );} + else {blas::gemm_n(std::forward(ctx), alpha , begin(a), size(a), begin(b), beta , begin(c));} + return std::forward(c); +} + +template +auto gemm(typename A::element alpha, A const& a, B const& b, typename A::element beta, C&& c) -> C&& { // NOLINT(readability-identifier-length) BLAS naming + if constexpr(is_conjugated{}) { + auto ctxt = blas::default_context_of(underlying(a.base())); + return gemm(ctxt, alpha, a, b, beta, std::forward(c)); + } else { + auto ctxt = blas::default_context_of(a.base()); + return gemm(ctxt, alpha, a, b, beta, std::forward(c)); + } +} + +// template class gemm_range; + +template +class gemm_reference { // TODO(correaa) implement this in terms of gemv_range? + Ext exts_; + + public: + explicit gemm_reference(Ext exts) : exts_{std::move(exts)} {} + auto extensions() const {return exts_;} + friend auto extensions(gemm_reference const& self) {return self.extensions();} +}; + +template +class gemm_iterator { + ContextPtr ctxtp_; + Scalar s_; + ItA a_it_; + ItB b_begin_; + gemm_iterator(ContextPtr ctxtp, Scalar s, ItA a_it, ItB b_begin) : ctxtp_{ctxtp}, s_{s}, a_it_{std::move(a_it)}, b_begin_{std::move(b_begin)} {} // NOLINT(readability-identifier-length) BLAS naming + template + friend class gemm_range; + + public: + gemm_iterator() = default; + gemm_iterator(gemm_iterator const&) = default; + gemm_iterator(gemm_iterator&&) noexcept = default; + ~gemm_iterator() = default; + + // auto operator=(gemm_iterator&&) noexcept -> gemm_iterator&; // = delete; + auto operator=(gemm_iterator const&) -> gemm_iterator&; // = delete; + auto operator=(gemm_iterator&&) noexcept -> gemm_iterator&; + + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = std::nullptr_t; + using reference = gemm_reference; + using iterator_category = std::random_access_iterator_tag; + + auto operator+=(difference_type n) -> gemm_iterator& { a_it_ += n; return *this; } + auto operator-=(difference_type n) -> gemm_iterator& { a_it_ -= n; return *this; } + + auto operator++() -> gemm_iterator& { return operator+=(1); } // required by random access concept requires even if not used explicitly + auto operator--() -> gemm_iterator& { return operator-=(1); } + + auto operator++(int) -> gemm_iterator { gemm_iterator ret{*this}; ++(*this); return ret; } // required by random access concept requires even if not used explicitly + auto operator--(int) -> gemm_iterator { gemm_iterator ret{*this}; --(*this); return ret; } + + friend auto operator+(gemm_iterator ret, difference_type n) { return ret += n; } + + friend auto operator-(gemm_iterator const& a, gemm_iterator const& b) -> difference_type { // NOLINT(readability-identifier-length) BLAS naming + assert(a.b_begin_ == b.b_begin_); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + return a.a_it_ - b.a_it_; + } + friend auto operator==(gemm_iterator const& self, gemm_iterator const& other) -> bool {return self.a_it_ == other.a_it_;} + friend auto operator!=(gemm_iterator const& self, gemm_iterator const& other) -> bool {return self.a_it_ != other.a_it_;} + + template + friend auto copy_n(gemm_iterator const& first, difference_type count, ItOut d_first) + ->decltype(blas::gemm_n(std::declval(), std::declval() , std::declval(), count, std::declval(), 0.0, d_first)) try { // std::complex NOLINT(fuchsia-default-arguments-calls) + return blas::gemm_n(first.ctxtp_ , static_cast(first.s_), first.a_it_ , count, first.b_begin_ , 0.0, d_first); // NOLINT(fuchsia-default-arguments-calls) + } catch(std::exception const& e) { + throw std::logic_error( + "in `copy_n`\nCouldn't decay product of arrays of size "+ std::to_string(count) +"x"+ std::to_string((*first.a_it_).size()) + " and " + // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + std::to_string((*first.a_it_).size())+ "x" +std::to_string((*first.b_begin_).size()) + " into " + std::to_string(count) +"x" + std::to_string((*first.b_begin_).size()) + + "\nbecause\n" + e.what() + ); + } + + template + friend auto copy(gemm_iterator const& first, gemm_iterator const& last, ItOut d_first) {assert(first.s_ == last.s_); + return copy_n(first, last - first, d_first); + } + + template + friend auto uninitialized_copy_n(gemm_iterator const& first, difference_type count, ItOut d_first) { + return copy_n(first, count, d_first); + } + + template + friend auto uninitialized_copy(gemm_iterator const& first, gemm_iterator const& last, ItOut d_first) { + // assert( first.s_ == last.s_ ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + return uninitialized_copy_n(first, last - first, d_first); + } + + auto operator*() const {return reference{(*b_begin_).extensions()};} +}; + +template +class gemm_range { + ContextPtr ctxtp_; + Scalar s_; + ItA a_begin_; + ItA a_end_; + ItB b_begin_; + + public: + gemm_range(gemm_range const&) = delete; + gemm_range(gemm_range&&) = delete; + auto operator=(gemm_range const&) -> gemm_range& = delete; + auto operator=(gemm_range&&) -> gemm_range& = delete; + ~gemm_range() = default; + + gemm_range(ContextPtr ctxtp, Scalar s, ItA a_first, ItA a_last, ItB b_first) // NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length) BLAS naming + : ctxtp_{ctxtp} + , s_{s}, a_begin_{std::move(a_first)}, a_end_{std::move(a_last)} + , b_begin_{std::move(b_first)} + {} + + using iterator = gemm_iterator; + using decay_type = DecayType; + using size_type = typename decay_type::size_type; + + auto begin() const& -> iterator {return {ctxtp_, s_, a_begin_, b_begin_};} + auto end() const& -> iterator {return {ctxtp_, s_, a_end_ , b_begin_};} + friend auto begin(gemm_range const& self) {return self.begin();} + friend auto end (gemm_range const& self) {return self.end ();} + + auto size() const -> size_type {return a_end_ - a_begin_;} + + auto extensions() const -> typename decay_type::extensions_type {return size()*(*b_begin_).extensions();} + friend auto extensions(gemm_range const& self) {return self.extensions();} + + auto operator+() const -> decay_type {return *this;} // TODO(correaa) : investigate why return decay_type{*this} doesn't work + template + friend auto operator+=(Arr&& a, gemm_range const& self) -> Arr&& { // NOLINT(readability-identifier-length) BLAS naming + blas::gemm_n(self.ctxtp_, self.s_, self.a_begin_, self.a_end_ - self.a_begin_, self.b_begin_, 1., a.begin()); + return std::forward(a); + } + friend auto operator*(Scalar factor, gemm_range const& self) { + return gemm_range{self.ctxtp_, factor*self.s_, self.a_begin_, self.a_end_, self.b_begin_}; + } +}; + +template())>{}> > // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto gemm(ContextPtr ctxtp, Scalar s, A2D const& a, B2D const& b) // NOLINT(readability-identifier-length) BLAS naming +->gemm_range +{ + return + gemm_range + (ctxtp, s, a.begin(), a.end(), b.begin()) + ; +} + +#ifdef __NVCC__ // in place of global -Xcudafe \"--diag_suppress=implicit_return_from_non_void_function\" +#pragma nv_diagnostic push +#pragma nv_diag_suppress = implicit_return_from_non_void_function +#endif + +#ifdef __NVCOMPILER +#pragma diagnostic push +#pragma diag_suppress = implicit_return_from_non_void_function +#endif +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type" +#endif + +template +auto gemm(Scalar s, A2D const& a, B2D const& b) { // NOLINT(readability-identifier-length) conventional BLAS naming + if constexpr(is_conjugated{}) { + auto ctxtp = blas::default_context_of(underlying(a.base())); + return blas::gemm(ctxtp, s, a, b); + } else { + auto ctxtp = blas::default_context_of(a.base()); + return blas::gemm(ctxtp, s, a, b); + } +} + +#ifdef __NVCC__ +#pragma nv_diagnostic pop +#elif defined(__NVCOMPILER) +#pragma diagnostic pop +#endif + +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace operators { + template =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + auto operator*(A2D const& A, B2D const& B) // NOLINT(readability-identifier-length) conventional BLAS names + ->decltype(blas::gemm(1.0, A, B)) { + return blas::gemm(1.0, A, B); } +} // end namespace operators + +} // end namespace boost::multi::blas +#endif diff --git a/include/multi/adaptors/blas/gemv.hpp b/include/boost/multi/adaptors/blas/gemv.hpp similarity index 61% rename from include/multi/adaptors/blas/gemv.hpp rename to include/boost/multi/adaptors/blas/gemv.hpp index 379b8294a..310833dd3 100644 --- a/include/multi/adaptors/blas/gemv.hpp +++ b/include/boost/multi/adaptors/blas/gemv.hpp @@ -1,31 +1,36 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_BLAS_GEMV_HPP -#define MULTI_ADAPTORS_BLAS_GEMV_HPP +#ifndef BOOST_MULTI_ADAPTORS_BLAS_GEMV_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_GEMV_HPP -#include "../blas/core.hpp" -#include "../blas/dot.hpp" +#include +#include -#include "./../../detail/../utility.hpp" +#include namespace boost::multi::blas { using core::gemv; +struct gemv_stride_error : std::logic_error { + using std::logic_error::logic_error; +}; + template auto gemv_n(Context ctxt, typename MIt::element a, MIt m_first, Size count, XIt x_first, typename MIt::element b, YIt y_first) { // NOLINT(readability-identifier-length) BLAS naming - assert(m_first->stride()==1 or m_first.stride()==1); // blas doesn't implement this case + assert((*m_first).stride()==1 || m_first.stride()==1); // blas doesn't implement this case assert( x_first.base() != y_first.base() ); + assert( y_first.stride() != 0 ); // BLAS generally doesn't support stride zero - if constexpr(not is_conjugated::value) { - if (m_first .stride()==1) {ctxt->gemv('N', count, m_first->size(), &a, m_first.base() , m_first->stride(), x_first.base(), x_first.stride(), &b, y_first.base(), y_first.stride());} - else if(m_first->stride()==1) {ctxt->gemv('T', m_first->size(), count, &a, m_first.base() , m_first. stride(), x_first.base(), x_first.stride(), &b, y_first.base(), y_first.stride());} - else {throw std::logic_error{"not BLAS-implemented"};} // LCOV_EXCL_LINE + if constexpr(! is_conjugated::value) { + if (m_first .stride()==1) {ctxt->gemv('N', count, (*m_first).size(), &a, m_first.base() , (*m_first).stride(), x_first.base(), x_first.stride(), &b, y_first.base(), y_first.stride());} + else if((*m_first).stride()==1) {ctxt->gemv('T', (*m_first).size(), count, &a, m_first.base() , m_first .stride(), x_first.base(), x_first.stride(), &b, y_first.base(), y_first.stride());} + else {assert(0); /*throw gemv_stride_error{"not BLAS-implemented"};*/} // LCOV_EXCL_LINE } else { - if (m_first->stride()==1) {ctxt->gemv('C', m_first->size(), count, &a, underlying(m_first.base()), m_first. stride(), x_first.base(), x_first.stride(), &b, y_first.base(), y_first.stride());} - // else if(m_first. stride()==1) {assert(0);} // not implemented in blas (use cblas?) - else {throw std::logic_error{"not BLAS-implemented"};} // LCOV_EXCL_LINE + if ((*m_first).stride()==1) {ctxt->gemv('C', (*m_first).size(), count, &a, underlying(m_first.base()), m_first. stride(), x_first.base(), x_first.stride(), &b, y_first.base(), y_first.stride());} + else {assert(0); /*throw gemv_stride_error{"not BLAS-implemented"};*/} // LCOV_EXCL_LINE } struct { @@ -39,39 +44,22 @@ auto gemv_n(Context ctxt, typename MIt::element a, MIt m_first, Size count, XIt template auto gemv_n(A a, MIt m_first, Size count, XIt x_first, B b, YIt y_first) { // NOLINT(readability-identifier-length) BLAS naming blas::context ctxt; - return gemv_n(&ctxt, a, m_first, count, x_first, b, y_first); + return gemv_n(&ctxt, static_cast(a), m_first, count, x_first, static_cast(b), y_first); } -template -auto gemv(Ctxt ctxt, A const& a, M const& m, V const& v, B const& b, W&& w) -> W&& { // NOLINT(readability-identifier-length) BLAS naming +template +auto gemv(Ctxt ctxt, typename M::element const& a, M const& m, V const& v, typename M::element const& b, W&& w) -> W&& { // NOLINT(readability-identifier-length) BLAS naming assert(size( m) == size(w) ); assert(size(~m) == size(v) ); - // gemv_n(a, begin(m), size(m), begin(v), b, begin(w)); - gemv_n(ctxt, a, begin(m), size(m), begin(v), b, begin(w)); // NOLINT(fuchsia-default-arguments-calls) - return std::forward(w); - - // assert(size( m) == size(w) ); - // assert(size(~m) == size(v) ); - // gemv_n(a, begin(m), size(m), begin(v), b, begin(w)); - // return std::forward(w); + gemv_n(ctxt, a, begin(m), size(m), begin(v), b, begin(w)); // NOLINT(fuchsia-default-arguments-calls) - // if constexpr(is_conjugated{}) { - // auto ctxtp = blas::default_context_of(underlying(m.base())); - // return blas::gemv(ctxtp, a, m, v, b, w); - // } else { - // auto ctxtp = blas::default_context_of(m.base()); - // return blas::gemv(ctxtp, a, m, v, b, w); - // } + return std::forward(w); } - -template -auto gemv(A const& a, M const& m, V const& v, B const& b, W&& w) -> W&& { // NOLINT(readability-identifier-length) BLAS naming +template +auto gemv(typename M::element a, M const& m, V const& v, typename M::element b, W&& w) -> W&& { // NOLINT(readability-identifier-length) BLAS naming assert(size( m) == size(w) ); - // assert(size(~m) == size(v) ); - // gemv_n(a, begin(m), size(m), begin(v), b, begin(w)); - // return std::forward(w); if constexpr(is_conjugated{}) { auto ctxtp = blas::default_context_of(underlying(m.base())); @@ -96,23 +84,47 @@ class gemv_iterator { using reference = void; using iterator_category = std::random_access_iterator_tag; + gemv_iterator() = default; + + auto operator++() -> gemv_iterator& { ++m_it_; return *this; } + auto operator++(int) -> gemv_iterator { gemv_iterator ret{*this}; ++(*this); return ret; } + + friend auto operator==(gemv_iterator const& self, gemv_iterator const& other) -> difference_type { + assert(self.v_first_ == other.v_first_); + return self.m_it_ == other.m_it_; + } + + friend auto operator!=(gemv_iterator const& self, gemv_iterator const& other) -> difference_type { + assert(self.v_first_ == other.v_first_); + return self.m_it_ != other.m_it_; + } + friend auto operator-(gemv_iterator const& self, gemv_iterator const& other) -> difference_type { assert(self.v_first_ == other.v_first_); return self.m_it_ - other.m_it_; } + template friend auto copy_n(gemv_iterator first, difference_type count, It1DOut result){ - if constexpr(std::is_same_v) {blas::gemv_n( first.alpha_, first.m_it_, count, first.v_first_, 0., result);} // NOLINT(fuchsia-default-arguments-calls) - else {blas::gemv_n(first.ctxt_, first.alpha_, first.m_it_, count, first.v_first_, 0., result);} // NOLINT(fuchsia-default-arguments-calls) + if constexpr(std::is_same_v) {blas::gemv_n( static_cast(first.alpha_), first.m_it_, count, first.v_first_, Scalar{0.0}, result);} // NOLINT(fuchsia-default-arguments-calls) + else {blas::gemv_n(first.ctxt_, static_cast(first.alpha_), first.m_it_, count, first.v_first_, Scalar{0.0}, result);} // NOLINT(fuchsia-default-arguments-calls) return result + count; } + template friend auto copy(gemv_iterator first, gemv_iterator last, It1DOut result){return copy_n(first, last - first, result);} template friend auto uninitialized_copy(gemv_iterator first, gemv_iterator last, It1DOut result) { - // static_assert(std::is_trivially_default_constructible_v); + #ifdef __cpp_lib_start_lifetime_as + auto count = last - first; + // or use start_lifetime_as_array(std::addressof(*result), count); since this is always called on contiguos iterators + for(; count > 0; ++result, --count) { + std::start_lifetime_as(std::addressof(*result)); + } + #endif return copy(first, last, result); } + gemv_iterator(Scalar alpha, It2D m_it, It1D v_first, Context ctxt) : alpha_{alpha}, m_it_{std::move(m_it)}, v_first_{std::move(v_first)}, ctxt_{ctxt} {} auto operator*() const { return value_type{0.0}; } // could be std::complex NOLINT(fuchsia-default-arguments-calls) @@ -120,11 +132,14 @@ class gemv_iterator { template class gemv_range { - Scalar alpha_ = 1.0; + BOOST_MULTI_NO_UNIQUE_ADDRESS Context ctxt_; + It2D m_begin_; It2D m_end_; + It1D v_first_; - Context ctxt_; + + Scalar alpha_{1.0}; public: gemv_range(gemv_range&&) noexcept = default; @@ -138,10 +153,11 @@ class gemv_range { assert(m_begin_.stride() == m_end_.stride()); } gemv_range(Context ctxt, Scalar alpha, It2D m_first, It2D m_last, It1D v_first) // NOLINT(bugprone-easily-swappable-parameters) - : alpha_{alpha} + : ctxt_{std::move(ctxt)} , m_begin_{std::move(m_first)}, m_end_{std::move(m_last)} , v_first_{std::move(v_first)} - , ctxt_{std::forward(ctxt)} { + , alpha_{alpha} + { assert(m_begin_.stride() == m_end_.stride()); } using iterator = gemv_iterator; @@ -157,9 +173,7 @@ class gemv_range { friend auto operator+(gemv_range const& self) {return self.decay();} template friend auto operator+=(V&& v, gemv_range const& s) -> V&& { // NOLINT(readability-identifier-length) BLAS naming - // if constexpr(std::is_same{}) {blas::gemv_n( s.alpha_, s.m_begin_, s.m_end_ - s.m_begin_, s.v_first_, 1.0, v.begin());} - // else - {blas::gemv_n(s.ctxt_, s.alpha_, s.m_begin_, s.m_end_ - s.m_begin_, s.v_first_, 1., v.begin());} + blas::gemv_n(s.ctxt_, static_cast(s.alpha_), s.m_begin_, s.m_end_ - s.m_begin_, s.v_first_, static_cast(1.0), v.begin()); return std::forward(v); } }; @@ -199,7 +213,8 @@ namespace operators { ->decltype(+blas::gemv(1.0, m, v)) { return +blas::gemv(1.0, m, v); } - template =0> + template =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 auto operator*(typename Matrix::element_type aa, Matrix const& A) { // NOLINT(readability-identifier-length) BLAS naming return scaled_matrix{aa, A}; } diff --git a/include/boost/multi/adaptors/blas/herk.hpp b/include/boost/multi/adaptors/blas/herk.hpp new file mode 100644 index 000000000..bc129b98a --- /dev/null +++ b/include/boost/multi/adaptors/blas/herk.hpp @@ -0,0 +1,210 @@ +// Copyright 2019-2024 Alfredo A. Correa + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_HERK_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_HERK_HPP + +#include +#include +#include +#include +#include +#include // fallback to real case + +// IWYU pragma: no_include "boost/multi/adaptors/blas/traits.hpp" // for blas + +namespace boost::multi::blas { + +template{}, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto base_aux(A&& array) +->decltype((std::forward(array)).base()) { + return (std::forward(array)).base(); } + +template{}, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto base_aux(A&& array) +->decltype(underlying((std::forward(array)).base())) { + return underlying((std::forward(array)).base()); } + +using core::herk; + +template +auto herk_nm(filling c_side, AA alpha, A2DCursor a_home, Size n, Size m, BB beta, C2DCursor c_home) { // NOLINT(readability-function-cognitive-complexity,readability-identifier-length) BLAS naming + auto a_base = base_aux(a_home); // NOLINT(llvm-qualified-auto,readability-qualified-auto) TODO(correaa) + auto c_base = base_aux(c_home); // NOLINT(llvm-qualified-auto,readability-qualified-auto) TODO(correaa) + + if constexpr(is_conjugated::value) { + assert(0); // TODO(correaa) implement + } else { + if (a_home.stride()!=1 && c_home.stride()!=1) { + herk(c_side==filling::upper?'L':'U', 'C', n, m, &alpha, a_base, a_home.template stride<0>(), &beta, c_base, c_home.template stride<0>() ); + } else if(a_home.stride()!=1 && c_home.stride()==1) { + if(n==1) { herk(c_side==filling::upper?'L':'U', 'N', n, m, &alpha, a_base, a_home.template stride<1>(), &beta, c_base, c_home.template stride<1>() ); } + else { assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + } + else if(a_home.stride()==1 && c_home.stride()!=1) { assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + else if(a_home.stride()==1 && c_home.stride()==1) { herk(c_side==filling::upper?'U':'L', 'N', n, m, &alpha, a_base, a_home.template stride<1>(), &beta, c_base, c_home.template stride<1>() );} + } + + return c_home; +} + +template +class herk_range { + // ContextPtr ctxtp_; + multi::blas::filling cs_; + Scalar scale_; + A2DCursor a_home_; + Size a_rows_; + Size a_cols_; + + public: + herk_range(herk_range const&) = delete; + herk_range(herk_range&&) = delete; + auto operator=(herk_range const&) -> herk_range& = delete; + auto operator=(herk_range&&) -> herk_range& = delete; + ~herk_range() = default; + + herk_range(multi::blas::filling cs, Scalar scale, A2DCursor a_home, Size a_rows, Size a_cols) : cs_{cs}, scale_{scale}, a_home_{a_home}, a_rows_{a_rows}, a_cols_{a_cols} {} // NOLINT(bugprone-easily-swappable-parameters) TODO(correaa) + +// herk_range(ContextPtr ctxtp, Scalar s, A2D&& a) // NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length) BLAS naming +// : ctxtp_{ctxtp} +// , s_{s}, a_{std::forward<2D>(a_first)}, a_end_{std::move(a_last)} +// {} + + struct iterator { + herk_range const* self_; // NOLINT(misc-non-private-member-variables-in-classes) TODO(correaa) make private + Size index_; // NOLINT(misc-non-private-member-variables-in-classes) TODO(correaa) make private + + auto operator==(iterator const&) const -> bool; + auto operator!=(iterator const&) const -> bool; + }; + +// // using iterator = herk_iterator; +// using decay_type = DecayType; +// using size_type = typename decay_type::size_type; + + auto begin() const { return iterator{this, 0}; } + auto end() const { return iterator{this, a_rows_}; } + + auto size() const { return a_rows_;} + auto extensions() const { return multi::extensions_t<2>{a_rows_, a_rows_}; } + + // template + // friend auto copy(iterator const& first, iterator const& last, ItOut d_first) { + // auto const& a = multi::ref(first.self_, last); + // blas::herk_nm(first.self_->cs_, a.home(), a.size(), (~a).size(), 0.0, d_first); // NOLINT(fuchsia-default-arguments-calls) + // } + +// // auto operator+() const -> decay_type {return *this;} // TODO(correaa) : investigate why return decay_type{*this} doesn't work +// // template +// // friend auto operator+=(Arr&& a, gemm_range const& self) -> Arr&& { // NOLINT(readability-identifier-length) BLAS naming +// // blas::gemm_n(self.ctxtp_, self.s_, self.a_begin_, self.a_end_ - self.a_begin_, self.b_begin_, 1., a.begin()); +// // return std::forward(a); +// // } +// // friend auto operator*(Scalar factor, gemm_range const& self) { +// // return gemm_range{self.ctxtp_, factor*self.s_, self.a_begin_, self.a_end_, self.b_begin_}; +// // } +}; + +template{}, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto herk(filling c_side, AA alpha, A2D const& a, BB beta, C2D&& c) -> C2D&& { // NOLINT(readability-function-cognitive-complexity,readability-identifier-length) 74, BLAS naming + assert( a.size() == c.size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( c.size() == c.rotated().size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + if(c.is_empty()) {return std::forward(c);} + if constexpr(is_conjugated{}) { + herk(flip(c_side), alpha, a, beta, hermitized(c)); + return std::forward(c); + } else { + + auto base_a = base_aux(a); // NOLINT(llvm-qualified-auto,readability-qualified-auto) TODO(correaa) + auto base_c = base_aux(c); // NOLINT(llvm-qualified-auto,readability-qualified-auto) TODO(correaa) + + // cppcheck-suppress-begin knownConditionTrueFalse ; bug in cppcheck 2.18? + if constexpr(is_conjugated{}) { + // auto& ctxt = *blas::default_context_of(underlying(a.base())); + // if you get an error here might be due to lack of inclusion of a header file with the backend appropriate for your type of iterator + if (stride(a)==1 && c.stride()!=1) { herk(c_side==filling::upper?'L':'U', 'N', c.size(), a.rotated().size(), &alpha, base_a, a.rotated().stride(), &beta, base_c, c.stride());} + else if(stride(a)==1 && c.stride()==1) { + if(size(a)==1) { herk(c_side==filling::upper?'L':'U', 'N', c.size(), a.rotated().size(), &alpha, base_a, a.rotated().stride(), &beta, base_c, c.stride());} + else { assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + } + else if(stride(a)!=1 && c.stride()==1) { herk(c_side==filling::upper?'U':'L', 'C', c.size(), a.rotated().size(), &alpha, base_a, stride( a ), &beta, base_c, c.rotated().stride());} + else if(stride(a)!=1 && c.stride()!=1) { herk(c_side==filling::upper?'L':'U', 'C', c.size(), a.rotated().size(), &alpha, base_a, stride( a ), &beta, base_c, c .stride());} + else { assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + } else { + // auto& ctxt = *blas::default_context_of( a.base() ); + if (stride(a)!=1 && c.stride()!=1) { herk(c_side==filling::upper?'L':'U', 'C', c.size(), a.rotated().size(), &alpha, base_a, stride( a ), &beta, base_c, c.stride());} + else if(stride(a)!=1 && c.stride()==1) { + if(size(a)==1) { herk(c_side==filling::upper?'L':'U', 'N', c.size(), a.rotated().size(), &alpha, base_a, a.rotated().stride(), &beta, base_c, c.rotated().stride());} + else { assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + } + else if(stride(a)==1 && c.stride()!=1) {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + else if(stride(a)==1 && c.stride()==1) {herk(c_side==filling::upper?'U':'L', 'N', c.size(), a.rotated().size(), &alpha, base_a, a.rotated().stride(), &beta, base_c, c.rotated().stride());} + // else {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + } + // cppcheck-suppress-end knownConditionTrueFalse ; bug in cppcheck 2.18? + + return std::forward(c); + } +} + +template{}, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto herk(filling c_side, AA alpha, A2D const& a, BB beta, C2D&& C) // NOLINT(readability-identifier-length) BLAS naming +->decltype(syrk(c_side, alpha, a, beta, std::forward(C))) { + return syrk(c_side, alpha, a, beta, std::forward(C)); } + +template +auto herk(filling c_side, AA alpha, A2D const& a, C2D&& C) // NOLINT(readability-identifier-length) BLAS naming +->decltype(herk(c_side, alpha, a, 0., std::forward(C))) { + return herk(c_side, alpha, a, 0., std::forward(C)); } + +template +auto herk(AA alpha, A2D const& a, C2D&& C) // NOLINT(readability-identifier-length) BLAS naming +->decltype(herk(filling::lower, alpha, a, herk(filling::upper, alpha, a, std::forward(C)))) { + return herk(filling::lower, alpha, a, herk(filling::upper, alpha, a, std::forward(C))); } + +template +auto herk(A2D const& A, C2D&& C) // NOLINT(readability-identifier-length) BLAS naming +->decltype(herk(1.0, A, std::forward(C))) { + return herk(1.0, A, std::forward(C)); } + +template +[[nodiscard]] // ("when argument is read-only") +auto herk(AA alpha, A2D const& a) { // NOLINT(readability-identifier-length) BLAS naming + return herk(alpha, a, Ret({size(a), size(a)}));//Ret({size(a), size(a)}));//, get_allocator(a))); +} + +template struct numeric_limits : std::numeric_limits {}; +template struct numeric_limits> : std::numeric_limits> { + static auto quiet_NaN() -> std::complex {auto nana = numeric_limits::quiet_NaN(); return {nana, nana};} // NOLINT(readability-identifier-naming) conventional std name +}; + +template +[[nodiscard]] // ("because argument is read-only")]] +auto herk(filling cs, AA alpha, A2D const& a) // NOLINT(readability-identifier-length) BLAS naming +{ + return herk_range(cs, alpha, a.home(), std::get<0>(a.sizes()), std::get<1>(a.sizes()) ); +} +// ->std::decay_t< +// decltype( herk(cs, alpha, a, Ret({size(a), size(a)}, 0.0, get_allocator(a))))> { +// return herk(cs, alpha, a, Ret({size(a), size(a)}, +// #ifdef NDEBUG +// numeric_limits::quiet_NaN(), +// #endif +// get_allocator(a) +// )); +// } + +template auto herk(filling f, A2D const& a) // NOLINT(readability-identifier-length) BLAS naming +->decltype(herk(f, 1.0, a)) { + return herk(f, 1.0, a); } + +template auto herk(A2D const& array) { + return herk(1.0, array); +} + +} // end namespace boost::multi::blas +#endif diff --git a/include/multi/adaptors/blas/herk.su b/include/boost/multi/adaptors/blas/herk.su similarity index 100% rename from include/multi/adaptors/blas/herk.su rename to include/boost/multi/adaptors/blas/herk.su diff --git a/include/boost/multi/adaptors/blas/iamax.hpp b/include/boost/multi/adaptors/blas/iamax.hpp new file mode 100644 index 000000000..cf6db746b --- /dev/null +++ b/include/boost/multi/adaptors/blas/iamax.hpp @@ -0,0 +1,38 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_IAMAX_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_IAMAX_HPP + +#include + +namespace boost::multi::blas { + +template +auto iamax_n(It first, Size n) { + using core::iamax; + return iamax(n, base(first), stride(first)); // if you get an error here make sure that you are including (and linking) the appropriate BLAS backend for your memory type +} + +template +auto iamax(It first, It last) + -> decltype(iamax_n(first, std::distance(first, last))) { + return iamax_n(first, std::distance(first, last)); +} + +template +auto iamax(X1D const& x) // NOLINT(readability-identifier-length) x conventional blas name + -> decltype(iamax(begin(x), end(x))) { + assert(! offset(x)); + return iamax(begin(x), end(x)); +} + +template +auto amax(X1D const& x) { // NOLINT(readability-identifier-length) x conventional blas name + return begin(x) + iamax(x); +} + +} // end namespace boost::multi::blas + +#endif diff --git a/include/boost/multi/adaptors/blas/nrm2.hpp b/include/boost/multi/adaptors/blas/nrm2.hpp new file mode 100644 index 000000000..6d5b74ac8 --- /dev/null +++ b/include/boost/multi/adaptors/blas/nrm2.hpp @@ -0,0 +1,136 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_NRM2_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_NRM2_HPP + +#include + +#include + +#include // std::norm + +namespace boost::multi::blas { + +using core::nrm2; + +using multi::base; +using std::norm; // nvcc11 needs using std::FUNCTION and the FUNCTION (and it works in clang, gcc, culang, icc) + +template +auto nrm2_n(Context&& ctxt, XIt x_first, Size count, RPtr rp) { + std::forward(ctxt)->nrm2(count, x_first.base(), x_first.stride(), rp); +} + +template +auto nrm2_n(It const& x, Size n, A0D res) // NOLINT(readability-identifier-length) conventional BLAS naming +//->decltype(blas::default_context_of(x.base())->nrm2(n, x.base(), x.stride(), res), std::next(res)) { // NOLINT(fuchsia-default-arguments-calls) +{ + // static_assert(!std::is_same_v); + return blas::default_context_of(x.base())->nrm2(n, x.base(), x.stride(), res), std::next(res); } // NOLINT(fuchsia-default-arguments-calls) + +template>::value, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto nrm2(Context ctxt, X1D const& x, R&& res) -> R&& { // NOLINT(readability-identifier-length) res = \sum_i x_i y_i + return blas::nrm2_n(ctxt, x.begin(), size(x), &res), std::forward(res); +} + +template>::value, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto nrm2(Context ctxt, X1D const& x, R&& res) -> R&& { // NOLINT(readability-identifier-length) res = \sum_i x_i y_i + return blas::nrm2_n(ctxt, begin(x), size(x), res.base()), std::forward(res); +} + +template +auto nrm2(X1D const& x, R&& res) -> R&& { // NOLINT(readability-identifier-length) BLAS naming + auto ctxtp = blas::default_context_of(x.base()); + return blas::nrm2(ctxtp, x, std::forward(res)); +} + +template +class nrm2_ptr { + ItX x_first_; + Size count_; + + protected: + nrm2_ptr(ItX x_first, Size count) : x_first_{x_first}, count_{count} {} + + public: + explicit operator bool() const {return true;} + + template + friend constexpr auto copy_n(nrm2_ptr first, Size2 count, ItOut d_first) { +// ->decltype(blas::nrm2_n(std::declval(), Size2{} , d_first), d_first + count) { + assert(count == 1); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + return blas::nrm2_n(first.x_first_ , first.count_, d_first), d_first + count; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + } + + template + friend constexpr auto uninitialized_copy_n(nrm2_ptr first, Size2 count, ItOut d_first) + ->decltype(blas::nrm2_n(std::declval(), Size2{} , d_first), d_first + count) {assert(count == 1); + return blas::nrm2_n(first.x_first_ , first.count_, d_first), d_first + count; } + + template + static constexpr auto uninitialized_copy_n(nrm2_ptr first, Size2 count, ItOut d_first) + ->decltype(blas::nrm2_n(std::declval(), Size2{} , d_first), d_first + static_cast::difference_type>(count)) {assert(count == 1); + return blas::nrm2_n(first.x_first_ , first.count_, d_first), d_first + static_cast::difference_type>(count); } +}; + +template> +struct nrm2_ref : private Ptr { + using decay_type = decltype(norm(std::declval())); + explicit nrm2_ref(X const& x) : Ptr{begin(x), size(x)} {} // NOLINT(readability-identifier-length) BLAS naming + + constexpr auto operator&() const& -> Ptr const& {return *this;} // NOLINT(google-runtime-operator) reference type //NOSONAR + + auto decay() const -> decay_type {decay_type ret; copy_n(operator&(), 1, &ret); return ret;} // NOLINT(fuchsia-default-arguments-calls) complex + operator decay_type() const { return decay(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions,hicpp-explicit-conversion) //NOSONAR to allow terse syntax +// #if ! defined(__CUDACC__) || ! defined(__INTEL_COMPILER) +// friend auto operator*(decay_type const& lhs, dot_ref const& self) {return lhs*self.decay();} +// #endif + auto operator+() const -> decay_type { return decay(); } + + // auto operator==(nrm2_ref const& other) const { return decay() == other.decay(); } + // auto operator!=(nrm2_ref const& other) const { return decay() != other.decay(); } + + friend auto operator==(nrm2_ref const& self, nrm2_ref const& other) { return self.decay() == other.decay(); } + friend auto operator!=(nrm2_ref const& self, nrm2_ref const& other) { return self.decay() != other.decay(); } +}; + +template +[[nodiscard]] +auto nrm2(X const& x) { // NOLINT(readability-identifier-length) BLAS naming + return nrm2_ref{x}; +} + +namespace operators { + using std::norm; + template()))>//decltype(norm(std::declval()))> + [[nodiscard]] auto operator^(A1D const& array, int n) + ->decltype(std::pow(Real{blas::nrm2(array)}, n)) { + return std::pow(Real{blas::nrm2(array)}, n); } + + template + [[nodiscard]] auto abs(A1D const& array) { + return blas::nrm2(array); + } + + template + [[nodiscard]] auto norm(A1D const& array) { + auto const sqrt = +blas::nrm2(array); + return sqrt*sqrt; + } + +} // end namespace operators + +} // end namespace boost::multi::blas + +#endif diff --git a/include/boost/multi/adaptors/blas/numeric.hpp b/include/boost/multi/adaptors/blas/numeric.hpp new file mode 100644 index 000000000..3c0feeec6 --- /dev/null +++ b/include/boost/multi/adaptors/blas/numeric.hpp @@ -0,0 +1,365 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_NUMERIC_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_NUMERIC_HPP + +// #include + +#include + +#include + +#include + +// #include + +#include + +// #include + +#include // for BOOST_MULTI_NO_UNIQUE_ADDRESS + +#include // for complex +// #include // for nullptr_t +#include // for negate +#include // for iterator... +#include // for pointer_... +#include // for decay_t +#include // for declval +// IWYU pragma: no_include // for nullptr_t + +#ifdef __NVCC__ +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif + +namespace boost { +namespace multi::blas { + +template struct complex_dummy { + T real; + T imag; +}; + +template< + class A, typename Complex = typename std::decay_t::element, typename T = typename multi::blas::complex_traits::real_type, + class = std::enable_if_t::value>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto real(A&& array) + -> decltype(std::forward(array).template reinterpret_array_cast>().template member_cast(&complex_dummy::real)) { + return std::forward(array).template reinterpret_array_cast>().template member_cast(&complex_dummy::real); +} + +template< + class A, class Complex = typename std::decay_t::element_type, typename T = typename complex_traits::real_type, + class = std::enable_if_t::value>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto imag(A&& array) + -> decltype(std::forward(array).template reinterpret_array_cast>().template member_cast(&complex_dummy::imag)) { + return std::forward(array).template reinterpret_array_cast>().template member_cast(&complex_dummy::imag); +} + +template::element, typename RealElem = typename ComplexElem::value_type, + class = std::enable_if_t::value>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto real_doubled(ComplexArr&& array) { // produces a real view of complex array with the last dimension duplicated and with interleaved real imaginary parts + return std::forward(array).template reinterpret_array_cast(2).rotated().flatted().unrotated(); +} + +template class involuted; + +template::reference, F>> class involuter; // IWYU pragma: keep // bug in iwyu 0.22/18.1.8? + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif +template +class involuted { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // 7 bytes padding added after f_ +#endif + BOOST_MULTI_NO_UNIQUE_ADDRESS Involution f_; + Ref r_; // [[no_unique_address]] // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + public: + using decay_type = std::decay_t()(std::declval()))>; + + constexpr explicit involuted(Ref& ref, Involution fun) : f_{fun}, r_{ref} {} // r_{std::forward(ref)}, f_{fun} {} + constexpr explicit involuted(Ref& ref) : f_{}, r_{ref} {} + + ~involuted() = default; + + involuted(involuted const&) = delete; + involuted(involuted&&) noexcept = default; + + auto operator=(involuted const& other) -> involuted& = delete; + auto operator=(involuted&& other) noexcept -> involuted& = delete; // default + + constexpr auto decay() const& -> decay_type { return f_(r_); } + + constexpr explicit operator decay_type() & { return f_(r_); } + constexpr explicit operator decay_type() const& { return f_(r_); } + constexpr /*plct*/ operator decay_type() && { return f_(r_); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) //NOSONAR to allow terse syntax + + // constexpr auto operator*(decay_type const& other) const { return f_(r_) * other; } + constexpr friend auto operator*(involuted const& self, decay_type const& other) { return self.f_(self.r_) * other; } + + template() = (std::declval())(std::declval()))> + constexpr auto operator=(DecayType&& other) & -> involuted& { + r_ = f_(std::forward(other)); + return *this; + } + + template() = (std::declval())(std::declval()))> + constexpr auto operator=(DecayType&& other) && -> involuted& { + r_ = f_(std::forward(other)); + return *this; + } + + template + friend constexpr auto operator==(involuted const& self, DecayType const& other) + -> decltype(std::declval() == other) { + return self.operator decay_type() == other; + } + template + friend constexpr auto operator!=(involuted const& self, DecayType const& other) + -> decltype(std::declval() != other) { + return self.operator decay_type() != other; + } + + friend constexpr auto operator==(decay_type const& other, involuted const& self) -> bool { + return other == self.operator decay_type(); + } + + friend constexpr auto operator!=(decay_type const& other, involuted const& self) -> bool { + return other != self.operator decay_type(); + } + + template, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + friend constexpr auto operator==(DecayType const& other, involuted const& self) { + return other == self.operator decay_type(); + } + template, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + friend constexpr auto operator!=(DecayType const& other, involuted const& self) { + return other != self.operator decay_type(); + } + + template friend constexpr auto operator<<(Sink&& sink, involuted const& self) -> Sink& { + return std::forward(sink) << self.operator decay_type(); + } + + constexpr auto conj() const& { return ::boost::multi::adl_conj(this->operator decay_type()); } + + template + friend constexpr auto imag(involuted const& self) { + //->decltype(imag(std::declval())) { + return self.operator decay_type().imag(); + } +}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __cpp_deduction_guides +template involuted(T&&, F) -> involuted; +#endif + +template +auto default_allocator_of(involuter const& iv) { + return default_allocator_of(iv.it_); +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif +template +class involuter { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // 7 bytes padding added after f_ +#endif + BOOST_MULTI_NO_UNIQUE_ADDRESS F f_; + It it_; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template friend class involuter; + + public: + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = involuter; // svoid; // typename std::iterator_traits::pointer + using reference = Reference; + using iterator_category = typename std::iterator_traits::iterator_category; + using element_type = typename std::pointer_traits::element_type; + template using rebind = involuter::template rebind, F>; + + involuter() = default; + + BOOST_MULTI_HD constexpr explicit involuter(It it) : f_{}, it_{std::move(it)} {} + BOOST_MULTI_HD constexpr explicit involuter(It it, F fun) : f_{std::move(fun)}, it_{std::move(it)} {} + + template(typename Other::underlying_type{}))* = nullptr> + // cppcheck-suppress noExplicitConstructor + BOOST_MULTI_HD constexpr /*implct*/ involuter(Other const& other) : f_{other.f_}, it_{other.it_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR inherit implicit conversion of underlying type + template(typename Other::underlying_type{}))* = nullptr> + BOOST_MULTI_HD constexpr explicit involuter(Other const& other) : f_{other.f_}, it_{other.it_} {} + + constexpr auto operator*() const { return reference{*it_, f_}; } + + // auto operator==(involuter const& other) const -> bool { return it_ == other.it_; } + // auto operator!=(involuter const& other) const -> bool { return it_ != other.it_; } + friend auto operator==(involuter const& slf, involuter const& thr) { return slf.it_ == thr.it_; } + friend auto operator!=(involuter const& slf, involuter const& thr) { return slf.it_ != thr.it_; } + + friend auto operator==(involuter const& slf, std::nullptr_t const& nil) { return slf.it_ == nil; } + friend auto operator!=(involuter const& slf, std::nullptr_t const& nil) { return slf.it_ != nil; } + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + constexpr auto operator+=(difference_type n) -> involuter& { + it_ += n; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + constexpr auto operator-=(difference_type n) -> involuter& { + it_ -= n; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + constexpr auto operator[](difference_type n) const { return reference{*(it_ + n), f_}; } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + template // workaround for nvcc + friend BOOST_MULTI_HD constexpr auto operator+(involuter lhs, difference_type n) { return lhs += n; } + template // workaround for nvcc + friend BOOST_MULTI_HD constexpr auto operator-(involuter lhs, difference_type n) { return lhs -= n; } + + template // workaround for nvcc + friend BOOST_MULTI_HD constexpr auto operator+(difference_type n, involuter lhs) { return lhs + n; } + + BOOST_MULTI_HD constexpr auto operator-(involuter const& other) const { return it_ - other.it_; } + + explicit operator bool() const { return it_; } + using underlying_type = It; + friend /*constexpr*/ auto underlying(involuter const& self) -> underlying_type { return self.it_; } + constexpr explicit operator It() const { return underlying(*this); } + + friend auto default_allocator_of(involuter const& inv) { + using multi::default_allocator_of; + return default_allocator_of(inv.it_); + } + + using default_allocator_type = typename multi::pointer_traits::default_allocator_type; + + friend auto get_allocator(involuter const& inv) { + using boost::multi::get_allocator; + return get_allocator(inv.it_); + } +}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template using negated = involuted>; +template using negater = involuter>; + +struct conjugate { + template + constexpr auto operator()(Complex const& zee) const { + // using std::conj; // for doubles? + return conj(zee); + } + +#ifdef __CUDACC__ + template + constexpr auto operator()(::thrust::tagged_reference zee) const { + return conj(static_cast(zee)); + } +#endif +#ifdef __HIPCC__ + template + constexpr auto operator()(::thrust::tagged_reference zee) const { + return conj(static_cast(zee)); + } +#endif +}; + +template using conjugated = involuted; + +template using conjugater = involuter; + +template auto make_conjugater(It it) { return conjugater{it}; } +template auto make_conjugater(conjugater it) -> It { return underlying(it); } + +template auto imag(involuted const& inv) { return inv.decay().imag(); } +template auto real(involuted const& inv) { return inv.decay().real(); } + +template auto has_imag_fun_aux(T const& value) -> decltype((void)imag(value), std::true_type{}); +inline auto has_imag_fun_aux(...) -> decltype(std::false_type{}); +template struct has_imag_fun : decltype(has_imag_fun_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + +template auto has_imag_mem_aux(T const& value) -> decltype((void)value.imag(), std::true_type{}); +inline auto has_imag_mem_aux(...) -> decltype(std::false_type{}); +template struct has_imag_mem : decltype(has_imag_mem_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + +template struct has_imag : std::integral_constant{} || has_imag_mem{})> {}; + +template +struct is_complex_array : has_imag().base())>>::element_type>> {}; + +template struct is_complex : has_imag {}; + +template +auto is_conjugated_aux(conjugater const& /*self*/) -> std::true_type; +inline auto is_conjugated_aux(...) -> std::false_type; + +template struct is_conjugated : decltype(is_conjugated_aux((std::declval()).base())) { // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + template constexpr auto operator()(AA&& /*unused*/) { return is_conjugated_aux((std::declval()).base()); } // NOLINT(cppcoreguidelines-missing-std-forward) +}; + +template, typename Elem = typename D::element_type, typename Ptr = typename D::element_ptr, + std::enable_if_t{}, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto conj(A&& array) -> A&& { + return std::forward(array); +} + +template< + class A, class D = std::decay_t, typename Elem = typename D::element_type, + typename Ptr = std::decay_t().base())>, + std::enable_if_t{} && is_complex_array{}, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto conj(A&& array) -> decltype(auto) { + return std::forward(array).template static_array_cast>(); +} + +template, typename Elem = typename D::element_type, + typename Ptr = typename decltype(std::declval().base())::underlying_type, + std::enable_if_t{}, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto conj(A&& array) + -> decltype(std::forward(array).template static_array_cast()) { + return std::forward(array).template static_array_cast(); +} + +} // end namespace multi::blas + +template +auto default_allocator_of(multi::blas::involuter it) { + return multi::default_allocator_of(it.underlying()); +} + +} // end namespace boost + +#undef BOOST_MULTI_HD + +#endif diff --git a/include/multi/adaptors/blas/numeric/is_complex.hpp b/include/boost/multi/adaptors/blas/numeric/is_complex.hpp similarity index 84% rename from include/multi/adaptors/blas/numeric/is_complex.hpp rename to include/boost/multi/adaptors/blas/numeric/is_complex.hpp index 9c52ef32e..38d3e86e6 100644 --- a/include/multi/adaptors/blas/numeric/is_complex.hpp +++ b/include/boost/multi/adaptors/blas/numeric/is_complex.hpp @@ -1,8 +1,9 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2022 Alfredo A. Correa +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_BLAS_NUMERIC_IS_COMPLEX_HPP -#define MULTI_ADAPTORS_BLAS_NUMERIC_IS_COMPLEX_HPP +#ifndef BOOST_MULTI_ADAPTORS_BLAS_NUMERIC_IS_COMPLEX_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_NUMERIC_IS_COMPLEX_HPP #include #include @@ -33,7 +34,7 @@ template struct has_imag : decltype(has_imag_aux(std::declval())){}; template constexpr bool has_imag_v = has_imag::value; template struct is_complex : std::integral_constant or has_real_fun_v) and (has_imag_v or has_imag_fun_v) + (has_real_v || has_real_fun_v) && (has_imag_v || has_imag_fun_v) >{}; template auto real_is_aux(T const& value) -> typename std::is_same; @@ -44,7 +45,7 @@ template auto imag_is_aux(T const& value) -> typename std::is_ template auto imag_is_aux(... ) -> false_type; template struct imag_is : decltype(imag_is_aux(std::declval())){}; -template struct is_complex_of : std::integral_constant::value and imag_is::value>{}; +template struct is_complex_of : std::integral_constant::value && imag_is::value>{}; } // end namespace boost::multi::blas::numeric diff --git a/include/boost/multi/adaptors/blas/operations.hpp b/include/boost/multi/adaptors/blas/operations.hpp new file mode 100644 index 000000000..a63abbc6c --- /dev/null +++ b/include/boost/multi/adaptors/blas/operations.hpp @@ -0,0 +1,100 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_OPERATIONS_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_OPERATIONS_HPP + +#include + +namespace boost::multi::blas { + +// template auto transposed(M&& arr) -> decltype(auto) {return rotated(std::forward(arr));} + +template +[[deprecated("use blas::T to avoid conflict with multi::transposed")]] +auto transposed(A&& arr) -> decltype(auto) {return std::forward(arr).rotated();} + +template auto identity(A&& array) -> decltype(auto) {return std::forward(array);} + +template auto T(A&& arr) -> decltype(auto) {return std::forward(arr).rotated();} // NOLINT(readability-identifier-naming) : conventional one-letter operation BLAS +template auto N(A&& arr) -> decltype(auto) {return std::forward(arr) ;} // NOLINT(readability-identifier-naming) : conventional one-letter operation BLAS + +template, typename E=typename D::element_type> +auto conjugated_transposed(A&& arr) -> decltype(auto) { + return blas::T(blas::conj(std::forward(arr))); +} + +// template, typename E=typename D::element_type> +// auto conjugated(A&& array) -> decltype(auto) { +// return blas::conj(std::forward(array)); +// } + +template +auto hermitized(A&& array, std::true_type /*true */) -> decltype(auto) { + return conjugated_transposed(std::forward(array)); +} + +template +auto hermitized(A&& array, std::false_type /*false*/) -> decltype(auto) { + return transposed(std::forward(array)); +} + +template +auto hermitized(A&& array) -> decltype(auto) {return conjugated_transposed(std::forward(array));} + +namespace operators { + +struct H_t { // NOLINT(readability-identifier-naming) blas naming + + template::rank::value == 2, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + [[nodiscard]] auto operator()(A&& array) const -> decltype(auto) { return hermitized(std::forward(array)); } + + template::rank::value == 1, int> =0> // NOLINT(modernize-use-constraints) for C++20 + [[deprecated("use blas::C instead of blas::H for conjugated vectors to avoid confusions")]] + [[nodiscard]] auto operator()(A&& array) const -> decltype(auto) { return blas::conj(std::forward(array)); } + +}; + +inline constexpr H_t H; // NOLINT(readability-identifier-length) conventional name in BLAS + +template +auto operator^(A&& array, Op op) +->decltype(op(std::forward(array))) { + return op(std::forward(array)); } + +} // end namespace operators + +using operators::H; + +template::rank::value == 1, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto C(A&& array) -> decltype(auto) { return blas::conj(std::forward(array)); } // NOLINT(readability-identifier-naming,readability-identifier-length) : conventional one-letter operation BLAS + +template::rank::value == 2, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +[[deprecated("use blas::H instead of blas::C for conjugated transposed matrices to avoid confusion, use blas::J for only-conjugation of matrices")]] +auto C(A&& array) -> decltype(auto) { return hermitized(std::forward(array)); } // NOLINT(readability-identifier-naming,readability-identifier-length) : conventional one-letter operation BLAS + +template::rank::value == 2, int> =0> // NOLINT(modernize-use-constraints) for C++20 +auto J(A&& array) -> decltype(auto) { return blas::conj(std::forward(array)); } // NOLINT(readability-identifier-naming,readability-identifier-length) : conventional one-letter operation BLAS + +namespace operators { + + template + auto operator*(A&& array) + ->decltype(blas::conj(std::forward(array))) { + return blas::conj(std::forward(array)); } + + template + auto operator~(A&& array) + ->decltype(blas::T(std::forward(array))) { + return blas::T(std::forward(array)); } + +} // end namespace operators + +} // end namespace boost::multi::blas + +#endif diff --git a/include/multi/adaptors/blas/scal.hpp b/include/boost/multi/adaptors/blas/scal.hpp similarity index 66% rename from include/multi/adaptors/blas/scal.hpp rename to include/boost/multi/adaptors/blas/scal.hpp index c7c377a7a..e666251ab 100644 --- a/include/multi/adaptors/blas/scal.hpp +++ b/include/boost/multi/adaptors/blas/scal.hpp @@ -1,21 +1,24 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_BLAS_SCAL_HPP -#define MULTI_ADAPTORS_BLAS_SCAL_HPP +#ifndef BOOST_MULTI_ADAPTORS_BLAS_SCAL_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_SCAL_HPP -#include "../blas/core.hpp" +#include +// IWYU pragma: no_include "boost/multi/adaptors/blas/traits.hpp" // for blas, multi + +// IWYU pragma: no_include // for declval // needed by iwyu-clang-linux +#include // for forward, declval namespace boost::multi::blas { using core::scal; template -auto scal_n(typename It::element a, It first, Size count) // NOLINT(readability-identifier-length) conventional BLAS naming -//->decltype(core::scal(count, &a, first.base(), first.stride()), void()) { -{ +auto scal_n(typename It::element a, It first, Size count) { // NOLINT(readability-identifier-length) conventional BLAS naming auto ctxt = blas::default_context_of(first.base()); - ctxt->scal(count, &a, first.base(), first.stride()); + ctxt->scal(static_cast(count), &a, first.base(), static_cast(first.stride())); } template @@ -37,8 +40,8 @@ class scal_range { explicit scal_range(A const& alpha) : alpha_{alpha} {} template friend auto operator*=(X1D&& x, scal_range const& self) // NOLINT(readability-identifier-length) conventional BLAS naming - ->decltype(std::forward(scal(std::declval(), x))) { - return std::forward(scal(self.alpha_, x));} + ->decltype(std::forward(scal(std::declval(), std::forward(x)))) { + return std::forward(scal(self.alpha_, std::forward(x)));} }; template auto scal(A const& array) {return scal_range{array};} @@ -51,5 +54,4 @@ namespace operators { } // end namespace operators } // end namespace boost::multi::blas - #endif diff --git a/include/boost/multi/adaptors/blas/side.hpp b/include/boost/multi/adaptors/blas/side.hpp new file mode 100644 index 000000000..77ac3c949 --- /dev/null +++ b/include/boost/multi/adaptors/blas/side.hpp @@ -0,0 +1,34 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_SIDE_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_SIDE_HPP + +namespace boost::multi::blas { + +enum class side : char { + left = 'L', + right = 'R' +}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wswitch-default" +#endif + +inline auto swap(side sid) noexcept -> side { + switch(sid) { // NOLINT(clang-diagnostic-switch-default) + case side::left : return side::right; + case side::right: return side::left ; + } // __builtin_unreachable(); // LCOV_EXCL_LINE + return {}; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +} // end namespace boost::multi::blas +#endif diff --git a/include/boost/multi/adaptors/blas/swap.hpp b/include/boost/multi/adaptors/blas/swap.hpp new file mode 100644 index 000000000..bb3e766c8 --- /dev/null +++ b/include/boost/multi/adaptors/blas/swap.hpp @@ -0,0 +1,48 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_SWAP_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_SWAP_HPP +#pragma once + +#include + +namespace boost::multi::blas { + +using core::swap; + +template +auto swap_n(It1 first, Size count, It2 first2) -> It2 { + blas::default_context_of(base(first))->swap(count, base(first), stride(first), base(first2), stride(first2)); + return first2 + count; +} + +template +auto swap(It1 first, It2 last, It2 first2) noexcept -> It2 { + assert(stride(first) == stride(last)); + return swap_n(first, last - first, first2); +} + +template +auto swap(X1D&& x, Y1D&& y) noexcept(false) -> Y1D&& { // NOLINT(readability-identifier-length) x, y conventional blas names, // NOSONAR(cpp:S5018) this swap can "fail" if sizes do not match + assert( size(x) == size(y) ); + swap( std::begin(x), std::end(std::forward(x)), std::begin(y) ); + return std::forward(y); +} + +template +auto swap(X1D const&, Y1D const&) noexcept(false) = delete; // NOSONAR(cpp:S5018) this swap can "fail" if sizes do not match + +template +auto operator^(X1D&& x, Y1D&& y) { // NOLINT(readability-identifier-length) BLAS naming + blas::swap(x, y); + return std::tie(std::forward(x), std::forward(y)); // or use std::forward_as_tuple ? +} + +namespace operators { + using blas::operator^; +} // end namespace operators + +} // end namespace boost::multi::blas +#endif diff --git a/include/boost/multi/adaptors/blas/syrk.hpp b/include/boost/multi/adaptors/blas/syrk.hpp new file mode 100644 index 000000000..840ef94da --- /dev/null +++ b/include/boost/multi/adaptors/blas/syrk.hpp @@ -0,0 +1,65 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_SYRK_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_SYRK_HPP + +#include +#include +#include + +namespace boost::multi::blas { + +using core::syrk; + +template +auto syrk(filling c_side, typename A2D::element alpha, A2D const& a, typename A2D::element beta, C2D&& cc) { // NOLINT(readability-identifier-length) BLAS naming + //->decltype(syrk('\0', '\0', size(c), size(a), alpha, base(a), stride(rotated(a)), beta, base(c), stride(c)), std::forward(c)){ + using std::get; + assert( cc.size() == get<1>(cc.sizes()) ); + // cppcheck-suppress knownConditionTrueFalse ; bug in cppcheck 2.18? + if(stride(a) == 1) { + if(cc.stride() == 1) { + syrk(flip(c_side) == filling::upper ? 'L' : 'U', 'N', cc.size(), size(a), &alpha, a.base(), a.rotated().stride(), &beta, cc.base(), cc.rotated().size()); + } else { + syrk(c_side == filling::upper ? 'L' : 'U', 'N', cc.size(), a.rotated().size(), &alpha, a.base(), a.rotated().stride(), &beta, cc.base(), cc.stride()); + } + } else { + if(cc.stride() == 1) { + syrk(flip(c_side) == filling::upper ? 'L' : 'U', 'T', cc.size(), a.rotated().size(), &alpha, a.base(), stride(a), &beta, cc.base(), cc.rotated().stride()); + } else { + syrk(c_side == filling::upper ? 'L' : 'U', 'T', cc.size(), a.rotated().size(), &alpha, a.base(), a.stride(), &beta, cc.base(), cc.stride()); + } + } + return std::forward(cc); +} + +template +auto syrk(filling c_side, AA alpha, A2D const& a, C2D&& cc) // NOLINT(readability-identifier-length) BLAS naming + -> decltype(syrk(c_side, alpha, a, 0.0, std::forward(cc))) { + return syrk(c_side, alpha, a, 0.0, std::forward(cc)); +} + +// template +// auto syrk(AA alpha, A2D const& a, C2D&& c) // NOLINT(readability-identifier-length) BLAS naming +// -> decltype(syrk(filling::upper, alpha, a, syrk(filling::lower, alpha, a, std::forward(c)))) { +// return syrk(filling::upper, alpha, a, syrk(filling::lower, alpha, a, std::forward(c))); +// } + +// template +// [[nodiscard]] // ("because input argument is const") +// // this decay in the return type is important +// auto // NOLINTNEXTLINE(readability-identifier-length) BLAS naming +// syrk(AA alpha, A2D const& a) -> std::decay_t { +// return syrk(alpha, a, Ret({size(a), size(a)}, get_allocator(a))); +// } + +// template +// [[nodiscard]] auto syrk(A2D const& A) // NOLINT(readability-identifier-length) BLAS naming +// -> decltype(syrk(1.0, A)) { +// return syrk(1.0, A); +// } + +} // end namespace boost::multi::blas +#endif diff --git a/include/multi/adaptors/blas/test/CMakeLists.txt b/include/boost/multi/adaptors/blas/test/CMakeLists.txt similarity index 56% rename from include/multi/adaptors/blas/test/CMakeLists.txt rename to include/boost/multi/adaptors/blas/test/CMakeLists.txt index 7f77cb8ce..9933d5345 100644 --- a/include/multi/adaptors/blas/test/CMakeLists.txt +++ b/include/boost/multi/adaptors/blas/test/CMakeLists.txt @@ -1,13 +1,4 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- -#[=[Multi Test suite can be run like this: - mkdir -p build - cd build - cmake .. [-DENABLE_CUDA=1] - make -j - ctest -j --output-on-error [-T memcheck] - exit -#]=] -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.18) # required by BLAS::BLAS if((NOT CMAKE_CXX_COMPILER_ID @@ -23,15 +14,15 @@ if((NOT DART_COMPILER_NAME STREQUAL "nvcc" - ) + ) AND (NOT DART_COMPILER_NAME STREQUAL "icpc" ) ) - find_package(Boost REQUIRED COMPONENTS unit_test_framework) - link_libraries("-lboost_unit_test_framework") + find_package(Boost REQUIRED NO_MODULE) # COMPONENTS boost) # headers unit_test_framework) +# link_libraries("-lboost_unit_test_framework") find_package(BLAS REQUIRED) find_path( @@ -45,55 +36,32 @@ if((NOT link_libraries(${BLAS_LIBRARIES}) else() find_package(Boost REQUIRED) # cmake cannot detect this component with pgi compiler - link_libraries("-lboost_unit_test_framework") + # link_libraries("-lboost_unit_test_framework") link_libraries("-lblas") # cmake cannot detect BLAS with pgi/nvc++ but it ships with its own version endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "PGI") - add_definitions(-DRETURN_BY_STACK) +# add_definitions(-DRETURN_BY_STACK) + add_definitions(-DBLAS_DOT_RETURNS_VOID) endif() -if(CMAKE_CXX_COMPILER_ID STREQUAL "NVHPC") - add_definitions(-DRETURN_BY_STACK) +if(CMAKE_CXX_COMPILER_ID STREQUAL "NVHPC") # nvhpc will find its own blas version +# add_definitions(-DRETURN_BY_STACK) + add_definitions(-DBLAS_DOT_RETURNS_VOID) endif() - if(ENABLE_CUDA OR DEFINED CXXCUDA) enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") -# find_package(CUDA QUIET) -# if(CUDA_FOUND) -# message("CUDA found") -# include_directories(${CUDA_INCLUDE_DIRS}) -# else() -# message("CUDA not found") -# endif() - -# include(FindCUDA/select_compute_arch) -# cuda_detect_installed_gpus(INSTALLED_GPU_CCS_1) -# string(STRIP "${INSTALLED_GPU_CCS_1}" INSTALLED_GPU_CCS_2) -# string( -# REPLACE " " -# ";" -# INSTALLED_GPU_CCS_3 -# "${INSTALLED_GPU_CCS_2}" -# ) -# string( -# REPLACE "." -# "" -# CUDA_ARCH_LIST -# "${INSTALLED_GPU_CCS_3}" -# ) -# set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCH_LIST}) - + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) + endif() endif() enable_testing() -list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # needs cmake 3.17 include(CTest) -configure_file("config.hpp.in" ${CMAKE_BINARY_DIR}/config.hpp) +# configure_file("config.hpp.in" ${CMAKE_BINARY_DIR}/config.hpp) include_directories(${CMAKE_BINARY_DIR}) @@ -108,32 +76,56 @@ set(TEST_SRCS numeric.cpp nrm2.cpp scal.cpp + syrk.cpp traits.cpp trsm.cpp ) +find_package(TBB) + +add_library(multi-blas INTERFACE) +target_link_libraries(multi-blas INTERFACE multi) +target_link_libraries(multi-blas INTERFACE BLAS::BLAS) + foreach(TEST_FILE ${TEST_SRCS}) set(TEST_EXE "${TEST_FILE}.x") add_executable(${TEST_EXE} ${TEST_FILE}) if(ENABLE_CUDA OR DEFINED CXXCUDA) set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) #set_property(TARGET ${TEST_EXE} PROPERTY "${CUDA_ARCH_LIST}") - target_compile_options(${TEST_EXE} PRIVATE -std=c++17) + set_property(TARGET ${TEST_EXE} PROPERTY CUDA_STANDARD 17) endif() - # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") - target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) - target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_TEST_DYN_LINK=1") + # target_link_libraries (${TEST_EXE} PRIVATE Boost::boost) # headers) # unit_test_framework ) + target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS} ) + + if(TBB_FOUND) + target_link_libraries(${TEST_EXE} PRIVATE TBB::tbb) + endif() + + target_link_libraries(${TEST_EXE} PRIVATE ${BLAS_LIBRARIES}) # there is no BLAS_INCLUDE_DIRS - target_link_libraries(${TEST_EXE} PRIVATE multi) - target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) + target_link_libraries(${TEST_EXE} PUBLIC multi) + target_link_libraries(${TEST_EXE} PRIVATE BLAS::BLAS) - target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) if(NOT ENABLE_CIRCLE) + if(ENABLE_CUDA) + target_compile_options( + ${TEST_EXE} + PRIVATE + $<$,$>: + -Wno-unknown-warning-option + #-Wextra + #-Wpedantic + #-Wmove + -Wno-enum-constexpr-conversion # for Boost.Test + #-Wno-error=\#warnings + > + ) + endif() + if(NOT ENABLE_CUDA AND (NOT DART_COMPILER_NAME @@ -143,18 +135,32 @@ foreach(TEST_FILE ${TEST_SRCS}) ) target_compile_options( ${TEST_EXE} - PRIVATE -Werror - -Wall + PRIVATE $<$: + -Werror + -Wall -Wextra -Wpedantic -Wformat-truncation - -Wno-unknown-pragmas> # -Wconversion + -Wno-unknown-pragmas + -Wparentheses + > # -Wconversion $<$,$>: + -Werror + -Wall + # -Weverything -Wextra -Wpedantic -Wmove - -Wno-error=\#warnings> + -Wno-padded + -Wno-pass-failed + -Wno-error=\#warnings + -Wno-c++98-compat -Wno-c++98-compat-bind-to-temporary-copy + > # -Wno-c++98-compat # use of constexpr + $<$: + -Wno-unknown-warning-option # for -Wno-enum-constexpr-conversion in older versions + -Wno-enum-constexpr-conversion # for Boost.Test + > $<$: -Wextra -wd161 @@ -163,6 +169,7 @@ foreach(TEST_FILE ${TEST_SRCS}) -Wchar-subscripts -Wcomment -Wenum-compare + -Wno-enum-constexpr-conversion # for Boost.Test -Wformat -Wuninitialized -Wmaybe-uninitialized @@ -185,9 +192,10 @@ foreach(TEST_FILE ${TEST_SRCS}) --display_error_number --diag_error=incompatible_assignment_operands,returning_ptr_to_local_variable,subscript_out_of_range,used_before_set,undefined_preproc_id,implicit_func_decl,implicit_return_from_non_void_function,missing_type_specifier > $<$: - /W4> + #/W4 + > ) endif() endif() - add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) + add_test(NAME ${TEST_EXE} COMMAND $) endforeach() diff --git a/include/multi/adaptors/blas/test/asum.cpp b/include/boost/multi/adaptors/blas/test/asum.cpp similarity index 67% rename from include/multi/adaptors/blas/test/asum.cpp rename to include/boost/multi/adaptors/blas/test/asum.cpp index e4c33238c..6e7611a40 100644 --- a/include/multi/adaptors/blas/test/asum.cpp +++ b/include/boost/multi/adaptors/blas/test/asum.cpp @@ -1,8 +1,8 @@ // -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2024 Alfredo A. Correa #define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS asum" -#include +// #include #include "../../../adaptors/cuda.hpp" #include "../../../array.hpp" @@ -16,17 +16,17 @@ namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_blas_asum_double) { +BOOST_AUTO_TEST_CASE(const multi_blas_asum_double) { multi::array const A = { {1.0, 2.0, 3.0, 4.0}, {5.0, 6.0, 7.0, 8.0}, {9.0, 10.0, 11.0, 12.0}, }; using multi::blas::asum; - BOOST_REQUIRE(asum(A[1]) == std::accumulate(begin(A[1]), end(A[1]), 0., [](auto&& a, auto&& b) { return a + std::abs(b); })); + BOOST_REQUIRE(asum(A[1]) == std::accumulate(begin(A[1]), end(A[1]), 0.0, [](auto&& a, auto&& b) { return a + std::abs(b); })); } -BOOST_AUTO_TEST_CASE(multi_blas_asum_complex) { +BOOST_AUTO_TEST_CASE(const multi_blas_asum_complex) { using Z = multi::complex; // std::complex; auto const I = Z{0.0, 1.0}; @@ -46,7 +46,7 @@ BOOST_AUTO_TEST_CASE(multi_blas_asum_complex) { ); } -BOOST_AUTO_TEST_CASE(multi_blas_asum_double_cuda) { +BOOST_AUTO_TEST_CASE(const multi_blas_asum_double_cuda) { multi::cuda::array const A = { {1.0, 2.0, 3.0, 4.0}, {5.0, 6.0, 7.0, 8.0}, @@ -57,30 +57,30 @@ BOOST_AUTO_TEST_CASE(multi_blas_asum_double_cuda) { } using complex = multi::complex; -constexpr auto I = complex{0, 1}; +constexpr auto I = complex{0.0, 1.0}; -BOOST_AUTO_TEST_CASE(multi_blas_asum_complex_cuda) { +BOOST_AUTO_TEST_CASE(const multi_blas_asum_complex_cuda) { namespace blas = multi::blas; multi::cuda::array const A = { - {1.0 + 2.0 * I, 2.0, 3.0, 4.0}, - { 5.0, 6.0 + 3.0 * I, 7.0, 8.0}, - { 9.0, 10.0, 11.0 + 4.0 * I, 12.0}, + {1.0 + 2.0 * I, 2.0 , 3.0 , 4.0}, + {5.0 , 6.0 + 3.0 * I, 7.0 , 8.0}, + {9.0 , 10.0 , 11.0 + 4.0 * I, 12.0}, }; BOOST_REQUIRE( blas::asum(A[1] ) == 29.0 ); BOOST_REQUIRE( blas::asum(A[1]({0, 4})) == 29.0 ); } -BOOST_AUTO_TEST_CASE(multi_blas_asum_complex_cuda_mutable) { +BOOST_AUTO_TEST_CASE(const multi_blas_asum_complex_cuda_mutable) { using Z = multi::complex; - auto const I = Z{0, 1}; + auto const I = Z{0.0, 1.0}; multi::cuda::array const A = { - {1.0 + 2.0 * I, 2.0, 3.0, 4.0}, - { 5.0, 6.0 + 3.0 * I, 7.0, 8.0}, - { 9.0, 10.0, 11.0 + 4.0 * I, 12.0}, + {1.0 + 2.0 * I, 2.0 , 3.0 , 4.0}, + {5.0 , 6.0 + 3.0 * I, 7.0 , 8.0}, + {9.0 , 10.0 , 11.0 + 4.0 * I, 12.0}, }; using multi::blas::asum; diff --git a/include/boost/multi/adaptors/blas/test/axpy.cpp b/include/boost/multi/adaptors/blas/test/axpy.cpp new file mode 100644 index 000000000..4c165acaa --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/axpy.cpp @@ -0,0 +1,247 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifdef _MSC_VER +#pragma warning(disable : 4244) // conversion deep in xmemory header +#endif + +#include // for axpy, operator- +#include // for context +#include // for imag, real +// #include // for complex, operator* +#include // for array, implicit_cast + +#include + +#include +#include // for complex, operator+ + +namespace multi = boost::multi; +namespace blas = multi::blas; + +// using complex = multi::complex; // test internal implementation of complex (partially formed complex) +using complex = std::complex; // test internal implementation of complex (partially formed complex) + +namespace { +void zero_stride() { + multi::array y = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) blas naming + + // regular arrays, stride != 0 + multi::array const x = {1.0, 1.0, 1.0}; // NOLINT(readability-identifier-length) blas naming + + multi::blas::axpy(1.0, x.begin(), y); + + BOOST_TEST(y[0] == 2.0); + BOOST_TEST(y[1] == 3.0); + BOOST_TEST(y[2] == 4.0); + + // broadcasted array of 0D into 1D, stride == 0 + multi::array const x0(1.0); + + // x0.broadcasted() is {..., 1.0, 1.0, ...} and has zero stride + + multi::blas::axpy(1.0, x0.broadcasted().begin(), y); + + BOOST_TEST(y[0] == 3.0); + BOOST_TEST(y[1] == 4.0); + BOOST_TEST(y[2] == 5.0); +} +} // end unnamed namespace + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_blas_axpy_real) + { + multi::array arr = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0, 12.0}, + }; + + auto const AC = arr; + + multi::array const b = arr[2]; // NOLINT(readability-identifier-length) BLAS naming + + blas::axpy(2.0, b, arr[1]); // daxpy + BOOST_TEST( std::abs( arr[1][2] - (2.0*b[2] + AC[1][2]) ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(blas_axpy_repeat) + { + multi::array a1D = multi::iextension(3); + BOOST_TEST( a1D[0] == 0.0 ); + BOOST_TEST( a1D[1] == 1.0 ); + BOOST_TEST( a1D[2] == 2.0 ); + + multi::array const b1D = {3.0, 3.0, 3.0}; + + blas::axpy(1.0, b1D, a1D); + BOOST_TEST( a1D[0] == 3.0 ); + BOOST_TEST( a1D[1] == 4.0 ); + BOOST_TEST( a1D[2] == 5.0 ); + + // BOOST_TEST(( multi::array(3.0).broadcasted().size() != 0 )); + + blas::axpy_n(1.0, multi::array(3.0).broadcasted().begin(), 3, a1D.begin()); + BOOST_TEST( a1D[0] == 6.0 ); + BOOST_TEST( a1D[1] == 7.0 ); + BOOST_TEST( a1D[2] == 8.0 ); + + // blas::axpy(1.0, multi::array(3.0).broadcasted(), a1D); + // BOOST_TEST( a1D[0] == 6.0 ); + // BOOST_TEST( a1D[1] == 7.0 ); + // BOOST_TEST( a1D[2] == 8.0 ); + + // blas::axpy(2.0, b, arr[1]); // daxpy + // BOOST_TEST( arr[1][2] == 2.0*b[2] + AC[1][2] ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_axpy_double) + { + multi::array const const_arr = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0, 12.0}, + }; + multi::array arr = const_arr; + multi::array const b{const_arr[2]}; // NOLINT(readability-identifier-length) conventional name in BLAS + + blas::axpy(2.0, b, arr[1]); // A[1] = 2*b + A[1], A[1]+= a*A[1] + BOOST_TEST( std::abs( arr[1][2] - (2.0*b[2] + const_arr[1][2])) < 1e-10 ); + + auto const I = complex{0, 1}; // NOLINT(readability-identifier-length) imaginary unit + + multi::array AC = {1.0 + 2.0 * I, 3.0 + 4.0 * I, 4.0 - 8.0 * I}; + multi::array BC(extensions(AC), complex{0.0, 0.0}); + + blas::axpy(+1.0, blas::real(AC), blas::real(BC)); + blas::axpy(-1.0, blas::imag(AC), blas::imag(BC)); + + // BOOST_TEST( BC[2] == std::conj(AC[2]) ); + BOOST_TEST( BC[2] == conj(AC[2]) ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_axpy_complex) + { + multi::array arr = { + {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0}}, + {{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}}, + {{9.0, 0.0}, {10.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}}, + }; + auto const const_arr = arr; + + multi::array const x = arr[2]; // NOLINT(readability-identifier-length) BLAS naming + blas::axpy(complex{2.0, 0.0}, x, arr[1]); // zaxpy (2. is promoted to 2+I*0 internally and automatically) + BOOST_TEST( arr[1][2] == 2.0*x[2] + const_arr[1][2] ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_axpy_complex_as_operator_plus_equal) + { + multi::array arr = { + {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0}}, + {{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}}, + {{9.0, 0.0}, {10.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}}, + }; + + auto const carr = arr; + + multi::array const y = arr[2]; // NOLINT(readability-identifier-length) BLAS naming + + arr[1] += blas::axpy(2.0, y); // zaxpy (2. is promoted to 2+I*0 internally and automatically) + BOOST_TEST( arr[1][2] == 2.0*y[2] + carr[1][2] ); + } + + // BOOST_AUTO_TEST_CASE(axpy_assignment) + { + multi::array const xx = {1.0, 1.0, 1.0}; + multi::array yy = {2.0, 2.0, 2.0}; + + yy += blas::axpy(3.0, xx); + + BOOST_TEST( yy[0] == 5.0 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_axpy_complex_as_operator_minus_equal) + { + multi::array arr = { + {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0}}, + {{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}}, + {{9.0, 0.0}, {10.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}}, + }; + auto const AC = arr; + multi::array const x = arr[2]; // NOLINT(readability-identifier-length) BLAS naming + arr[1] -= blas::axpy(complex{2.0, 0.0}, x); // zaxpy (2. is promoted to 2+I*0 internally and automatically) + BOOST_TEST( arr[1][2] == -2.0*x[2] + AC[1][2] ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_axpy_complex_context) + { + multi::array arr = { + {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0}}, + {{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}}, + {{9.0, 0.0}, {10.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}}, + }; + auto const arr_copy = arr; + multi::array const arr2 = arr[2]; + blas::context ctxt{}; + blas::axpy(&ctxt, complex{2.0, 0.0}, arr2, arr[1]); // zaxpy (2. is promoted to 2+I*0 internally and automatically) + BOOST_TEST( arr[1][2] == 2.0*arr2[2] + arr_copy[1][2] ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_axpy_operator_minus) + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array x = { + {10.0, 0.0}, + {11.0, 0.0}, + {12.0, 0.0}, + {13.0, 0.0}, + }; + multi::array const y = x; // NOLINT(readability-identifier-length) BLAS naming + + using blas::operators::operator-; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + + BOOST_TEST( (x - y)[0] == complex(0.0, 0.0) ); + BOOST_TEST( (y - x)[0] == complex(0.0, 0.0) ); + + using blas::operators::operator+; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + + BOOST_TEST( (x - (y+y))[0] == -x[0] ); + BOOST_TEST( ((x+x) - y)[0] == +x[0] ); + + multi::array arr = { + {{1.0, 0.0}, {2.0, 0.0}}, + {{3.0, 0.0}, {4.0, 0.0}}, + }; + multi::array const arr2 = { + {1.0, 0.0}, + {2.0, 0.0}, + }; + BOOST_TEST( (arr[0] - arr2)[0] == complex(0.0, 0.0) ); + BOOST_TEST( (arr[0] - arr2)[1] == complex(0.0, 0.0) ); + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array X = { + {10.0, 0.0}, + {11.0, 0.0}, + {12.0, 0.0}, + {13.0, 0.0}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const Y = { + {10.0, 0.0}, + {11.0, 0.0}, + {12.0, 0.0}, + {13.0, 0.0}, + }; + + using blas::operators::operator-=; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + X -= Y; + BOOST_TEST( X[0] == complex(0.0, 0.0) ); + } + + zero_stride(); + + return boost::report_errors(); +} diff --git a/include/multi/adaptors/blas/test/config.hpp.in b/include/boost/multi/adaptors/blas/test/config.hpp.in similarity index 100% rename from include/multi/adaptors/blas/test/config.hpp.in rename to include/boost/multi/adaptors/blas/test/config.hpp.in diff --git a/include/boost/multi/adaptors/blas/test/copy.cpp b/include/boost/multi/adaptors/blas/test/copy.cpp new file mode 100644 index 000000000..670035057 --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/copy.cpp @@ -0,0 +1,208 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for copy, copy_n +#include // for array, layout_t, subarray + +#include + +#if defined(NDEBUG) && !defined(RUNNING_ON_VALGRIND) // && !defined(__NVCC__) && !(defined(__clang__) && defined(__CUDA__)) +#include // for transform +#include // NOLINT(build/c++11) for duration, high_resolution... +#if __has_include() && !defined(__NVCC__) && !defined(__NVCOMPILER) +#if !((defined(__clang__) && !defined(__apple_build_version__)) && defined(__CUDA__)) +#if(!defined(__INTEL_LLVM_COMPILER) || (__INTEL_LLVM_COMPILER > 20240000)) +#include // NOLINT(misc-include-cleaner) for execution_policy +#endif +#endif +#endif +#include // for invoke // IWYU pragma: keep +#include // for basic_ostream, endl, cout +#endif + +#include +#include // for operator*, operator+ +#include // for size +#include // for forward // IWYU pragma: keep // NOLINT(misc-include-cleaner) + +namespace multi = boost::multi; +namespace blas = multi::blas; + +#define BOOST_AUTO_TEST_CASE(CasenamE) + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_AUTO_TEST_CASE(multi_blas_copy_n) { + multi::array const x = {1.0, 2.0, 3.0, 4.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array y = {5.0, 6.0, 7.0, 8.0}; // NOLINT(readability-identifier-length) BLAS naming + blas::copy_n(x.begin(), x.size(), y.begin()); + BOOST_TEST( y == x ); + } + + BOOST_AUTO_TEST_CASE(multi_blas_copy) { + multi::array const x = {1.0, 2.0, 3.0, 4.0}; // NOLINT(readability-identifier-length) BLAS naming + { + multi::array y = {5.0, 6.0, 7.0, 8.0}; // NOLINT(readability-identifier-length) BLAS naming + blas::copy(x, y); // segmentation fault in clang-11 + BOOST_TEST( y == x ); + } + { + multi::array y = {5.0, 6.0, 7.0, 8.0}; // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( size(y) == size(x) ); + y() = blas::copy(x); + BOOST_TEST( y == x ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_copy_real) { + namespace blas = multi::blas; + + multi::array arr = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0, 12.0}, + }; + + BOOST_TEST( arr[0][2] == 3.0 ); + BOOST_TEST( arr[2][2] == 11.0 ); + + blas::copy(arr[0], arr[2]); + BOOST_TEST( arr[0][2] == 3.0 ); + BOOST_TEST( arr[2][2] == 3.0 ); + + blas::copy(arr[1]({0, size(arr[1])}), arr[2]({0, size(arr[1])})); + BOOST_TEST( arr[1][3] == 8.0 ); + BOOST_TEST( arr[2][3] == 8.0 ); + + multi::array AR3 = blas::copy(arr.rotated()[3]); // dcopy + BOOST_TEST( std::abs( AR3[1] - arr[1][3] ) < 1e-10 ); + } + + BOOST_AUTO_TEST_CASE(multi_blas_copy_row) { + multi::array const arr = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + {7.0, 8.0, 9.0}, + }; + multi::array y(multi::extensions_t<1>{multi::iextension{3}}); // NOLINT(readability-identifier-length) BLAS naming + blas::copy(arr.rotated()[0], y); + BOOST_TEST( y == arr.rotated()[0] ); + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_copy_complex) { + using complex = std::complex; + + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array arr = { + {1.0 + 3.0 * I, 2.0 + 4.0 * I, 3.0 + 5.0 * I, 4.0 + 6.0 * I}, + {5.0 + 0.0 * I, 6.0 + 0.0 * I, 7.0 + 0.0 * I, 8.0 + 0.0 * I}, + {9.0 + 0.0 * I, 10.0 + 0.0 * I, 11.0 + 0.0 * I, 12.0 + 0.0 * I}, + }; + blas::copy(arr[0], arr[2]); + BOOST_TEST( arr[0][2] == 3.0 + 5.0*I ); + } + +#if defined(NDEBUG) && !defined(RUNNING_ON_VALGRIND) + /* transform copy */ { + multi::array A2D({10000, 10000}, 55.5); + auto&& A2D_block = A2D({1000, 9000}, {1000, 5000}); + + multi::array B2D({10000, 10000}, 66.6); + auto&& B2D_block = ~(~B2D({1000, 9000}, {1000, 9000})).strided(2); + + using std::chrono::duration; + using std::chrono::high_resolution_clock; + + std::cout + << "MULTI assignment\n" + << std::invoke([&, start_time = high_resolution_clock::now()] () noexcept { + B2D_block = A2D_block; + return duration{high_resolution_clock::now() - start_time}; + }).count() + << '\n'; + + BOOST_TEST( A2D_block == B2D_block ); + + std::cout + << "std::transform BLAS\n" + << std::invoke( + [&, start_time = high_resolution_clock::now()] () noexcept { + // NOLINTNEXTLINE(modernize-use-ranges) for C++20 + std::transform(A2D_block.begin(), A2D_block.end(), B2D_block.begin(), [](auto const& row) { return multi::blas::copy(row); }); + return duration{high_resolution_clock::now() - start_time}; + } + ).count() + << '\n'; + + BOOST_TEST( A2D_block == B2D_block ); + +#if defined(NDEBUG) && !defined(RUNNING_ON_VALGRIND) && __has_include() && !defined(__NVCC__) && !defined(__NVCOMPILER) +#if !((defined(__clang__)) && defined(__CUDA__)) && (!defined(__INTEL_LLVM_COMPILER) || (__INTEL_LLVM_COMPILER > 20240000)) +#if(__cplusplus >= 202002L) +#if !defined(__apple_build_version__) + std::cout << "std::transform par BLAS\n" + << std::invoke([&, start_time = high_resolution_clock::now()] { + std::transform(std::execution::par, A2D_block.begin(), A2D_block.end(), B2D_block.begin(), [](auto& row) { return multi::blas::copy(row); }); + return duration{high_resolution_clock::now() - start_time}; + }).count() + << '\n'; + + BOOST_TEST( A2D_block == B2D_block ); + std::cout << "std::copy par\n" + << std::invoke([&, start_time = high_resolution_clock::now()] { + std::copy(std::execution::par, A2D_block.begin(), A2D_block.end(), B2D_block.begin()); + return duration{high_resolution_clock::now() - start_time}; + }).count() + << '\n'; + + std::cout << "std::copy par 2\n" + << std::invoke([&, start_time = high_resolution_clock::now()] { + std::transform( + std::execution::par, A2D_block.begin(), A2D_block.end(), B2D_block.begin(), B2D_block.begin(), + [](auto const& row_a, auto&& row_b) -> auto&& { + std::copy(std::execution::par_unseq, row_a.begin(), row_a.end(), row_b.begin()); + return std::forward(row_b); + } + ); + return duration{high_resolution_clock::now() - start_time}; + }).count() + << '\n'; + + BOOST_TEST( A2D_block == B2D_block ); + + std::cout << "std::copy elements par\n" + << std::invoke([&, start_time = high_resolution_clock::now()] { + std::copy(std::execution::par_unseq, A2D_block.elements().begin(), A2D_block.elements().end(), B2D_block.elements().begin()); + return duration{high_resolution_clock::now() - start_time}; + }).count() + << '\n'; +#endif + + BOOST_TEST( A2D_block == B2D_block ); +#endif +#endif +#endif + + std::cout << "std::copy\n" + << std::invoke([&, start_time = high_resolution_clock::now()] () noexcept { + std::copy(A2D_block.begin(), A2D_block.end(), B2D_block.begin()); // NOLINT(modernize-use-ranges) for C++20 + return duration{high_resolution_clock::now() - start_time}; + }).count() + << '\n'; + + BOOST_TEST( A2D_block == B2D_block ); + + std::cout << "Multi element assignment\n" + << std::invoke([&, start_time = high_resolution_clock::now()] () noexcept { + B2D_block.elements() = A2D_block.elements(); + return duration{high_resolution_clock::now() - start_time}; + }).count() + << '\n'; + + BOOST_TEST( A2D_block == B2D_block ); + } +#endif + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/blas/test/dot.cpp b/include/boost/multi/adaptors/blas/test/dot.cpp new file mode 100644 index 000000000..4d6de9529 --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/dot.cpp @@ -0,0 +1,587 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include // for context, dot +#include // for dot_ref, dot, dot_n +#include // for involuter, conj +#include // for C, hermitized +#include // for array, static_array + + + +#include // for for_each, transform +#include // for abs // IWYU pragma: keep +// IWYU pragma: no_include +#include // for complex, operator* +#include // for plus // IWYU pragma: keep +#include // for begin, end +#include // for numeric_limits +#include // for allocator +#include // for inner_product +// IWYU pragma: no_include // for tuple_element<>:... +// IWYU pragma: no_include +#include // for is_same +#include // for forward + +namespace multi = boost::multi; +namespace blas = multi::blas; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(blas_dot_context_double) + { + multi::array const x = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + + blas::context const ctxt; + + auto res1 = +blas::dot(&ctxt, x, y); + BOOST_TEST( std::abs( res1 - std::inner_product(begin(x), end(x), begin(y), 0.0)) < 1e-10 ); + + auto const res2 = +blas::dot(&ctxt, x, y); + BOOST_TEST( std::abs( res2 - std::inner_product(begin(x), end(x), begin(y), 0.0)) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_no_context_double) + { + multi::array const x = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + + auto res = +blas::dot(x, y); + + BOOST_TEST( std::abs( res - std::inner_product(begin(x), end(x), begin(y), 0.0)) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_double) + { + multi::array const x = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + + auto res = std::numeric_limits::quiet_NaN(); + + blas::dot(x, y, multi::array_ref(res)); + BOOST_TEST( std::abs( res - std::inner_product(begin(x), end(x), begin(y), 0.0)) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_context_double) + { + using T = double; + multi::array const x = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + + blas::context const ctxt; + + auto const res1 = +blas::dot(&ctxt, x, y); + BOOST_TEST( std::abs( res1 - std::inner_product(begin(x), end(x), begin(y), 0.0)) < 1e-8 ); + + auto const res2 = +blas::dot(&ctxt, x, y); + BOOST_TEST( std::abs( res2 - std::inner_product(begin(x), end(x), begin(y), 0.0) ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_context_float) + { + using T = float; + multi::array const x = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming + + blas::context const ctxt; + + auto const res1 = +blas::dot(x, y); + BOOST_TEST( std::abs(res1 - std::inner_product(begin(x), end(x), begin(y), 0.F)) < 1.0e-8F ); + + auto const res2 = +blas::dot(&ctxt, x, y); + BOOST_TEST( std::abs( res2 - std::inner_product(begin(x), end(x), begin(y), 0.F)) < 1e-10F ); + } + + // Apple Accelerate BLAS has bugs with dot for floats, https://fortran-lang.discourse.group/t/how-many-blas-libraries-have-this-error/4454/23, https://forums.developer.apple.com/forums/thread/717757 + + // BOOST_AUTO_TEST_CASE(blas_dot_no_context_float) + { + multi::array const x = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming + + auto res = +blas::dot(x, y); + + BOOST_TEST( std::abs( res - std::inner_product(begin(x), end(x), begin(y), 0.0F)) < 1e-10F ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_float) + { + multi::array const x = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming + + float res = std::numeric_limits::quiet_NaN(); + + blas::dot(x, y, multi::array_ref(res)); + BOOST_TEST( std::abs( res - std::inner_product(begin(x), end(x), begin(y), 0.F) ) < 1e-10F ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_complex_double) + { // if you get a segfaut here, your system may require -DRETURN_BY_STACK + using complex = std::complex; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const x = { + {1.0, 0.0}, + {2.0, 0.0}, + {3.0, 0.0}, + }; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const y = { + {1.0, 0.0}, + {2.0, 0.0}, + {3.0, 0.0}, + }; // NOLINT(readability-identifier-length) BLAS naming + complex res{0.0, 0.0}; + blas::dot(x, y, res); + + auto hermitian_product = [](auto alpha, auto omega) { + return alpha * std::conj(omega); + }; + + // an isolated error here might mean that the dot and nrm2 interface for the BLAS library is not detected properly + BOOST_TEST_EQ( + real(res), + real(std::inner_product(begin(x), end(x), begin(y), complex{}, std::plus<>{}, hermitian_product)) // NOLINT(fuchsia-default-arguments-calls) + ); + BOOST_TEST_EQ( + imag(res), + imag(std::inner_product(begin(x), end(x), begin(y), complex{}, std::plus<>{}, hermitian_product)) // NOLINT(fuchsia-default-arguments-calls) + ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_complex_float) + { // if you get a segfaut here, your system may require -DRETURN_BY_STACK + using complex = std::complex; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const x = { + {1.0F, 0.0F}, + {2.0F, 0.0F}, + {3.0F, 0.0F}, + }; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const y = { + {1.0F, 0.0F}, + {2.0F, 0.0F}, + {3.0F, 0.0F}, + }; // NOLINT(readability-identifier-length) BLAS naming + complex res{0.0F, 0.0F}; + blas::dot(x, y, res); + + BOOST_TEST( std::abs(real(res)) > 1e-6F ); + + // // an isolated error here might mean that the dot and nrm2 interface for the BLAS library is not detected properly + BOOST_TEST_EQ( + real(res), + real(std::inner_product( + begin(x), end(x), begin(y), complex{}, std::plus<>{}, // NOLINT(fuchsia-default-arguments-calls) + [](auto alpha, auto omega) { return alpha * std::conj(omega); } + )) + ); + BOOST_TEST_EQ( + imag(res), + imag( + std::inner_product(begin(x), end(x), begin(y), complex{}, std::plus<>{}, // NOLINT(fuchsia-default-arguments-calls) + [](auto alpha, auto omega) { return alpha * std::conj(omega); }) + ) + ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_complex_C) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array const x = {1.0 + 0.0 * I, 2.0 + 0.0 * I, 3.0 + 0.0 * I}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0 + 0.0 * I, 2.0 + 2.0 * I, 3.0 + 0.0 * I}; // NOLINT(readability-identifier-length) BLAS naming + + complex res{-999.0, 666.0}; + blas::dot(blas::C(x), y, res); + BOOST_TEST( + res == std::inner_product(begin(x), end(x), begin(y), complex{}, std::plus<>{}, // NOLINT(fuchsia-default-arguments-calls) + [](auto alpha, auto omega) { return conj(alpha) * omega;} + ) + ); + } + + // BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_complex_C_float) + { + using complex = std::complex; + auto const I = complex{0.0F, 1.0F}; // NOLINT(readability-identifier-length) imag unit + + multi::array const x = {1.0F + 0.0F * I, 2.0F + 0.0F * I, 3.0F + 0.0F * I}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0F + 0.0F * I, 2.0F + 2.0F * I, 3.0F + 0.0F * I}; // NOLINT(readability-identifier-length) BLAS naming + + complex res{0.0F, 0.0F}; + blas::dot(blas::C(x), y, res); + + + BOOST_TEST( + res == std::inner_product( + begin(x), end(x), begin(y), complex{}, std::plus<>{}, // NOLINT(fuchsia-default-arguments-calls) + [](auto const& alpha, auto const& omega) { + return conj(alpha) * omega; + } + ) + ); + } + +// #if defined(CUDA_FOUND) and CUDA_FOUND +// #include +// BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_complex_C_thrust) { +// using complex = thrust::complex; +// auto const I = complex{0.0, 1.0}; + +// multi::array const A = {1.0 + 0. * I, 2.0 + 0.0 * I, 3.0 + 0.0 * I}; +// multi::array const B = {1.0 + 0. * I, 2.0 + 2.0 * I, 3.0 + 0.0 * I}; + +// complex C; +// blas::dot(blas::C(A), B, C); +// BOOST_TEST( C == std::inner_product(begin(A), end(A), begin(B), complex{0.0, 0.0}, std::plus<>{}, [](auto& a, auto& b){ +// return conj(a) * b;}) ); +// } +// #endif + + // BOOST_AUTO_TEST_CASE(multi_blas_dot_strided_double) + { + multi::array const CA = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0, 12.0}, + }; + double res = std::numeric_limits::quiet_NaN(); + blas::dot_n(begin(CA[1]), size(CA[1]), begin(CA[2]), &res); + BOOST_TEST( std::abs( res - std::inner_product(begin(CA[1]), begin(CA[2]), end(CA[1]), 0.0) ) < 1e-10 ); + + double const res2 = blas::dot(CA[1], CA[2]); + BOOST_TEST( std::abs( res - res2 ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_dot_strided_float) + { + multi::array const CA = { + {1.0F, 2.0F, 3.0F, 4.0F}, + {5.0F, 6.0F, 7.0F, 8.0F}, + {9.0F, 10.0F, 11.0F, 12.0F}, + }; + auto res = std::numeric_limits::quiet_NaN(); + blas::dot_n(begin(CA[1]), size(CA[1]), begin(CA[2]), &res); + BOOST_TEST( std::abs( res - std::inner_product(begin(CA[1]), begin(CA[2]), end(CA[1]), 0.0F)) < 1e-10F ); + + auto const res2 = static_cast(blas::dot(CA[1], CA[2])); + BOOST_TEST( std::abs( res - static_cast(res2) ) < 1e-10F ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_dot_strided_context) + { + multi::array const CA = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0, 12.0}, + }; + + double res = std::numeric_limits::quiet_NaN(); + + blas::context ctxt; + blas::dot_n(&ctxt, begin(CA[1]), size(CA[1]), begin(CA[2]), &res); + BOOST_TEST( std::abs( res - std::inner_product(begin(CA[1]), begin(CA[2]), end(CA[1]), 0.0)) < 1e-10 ); + + double const res2 = blas::dot(CA[1], CA[2]); + BOOST_TEST( std::abs( res - res2 ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_dot_1d_real_double) + { + multi::array const x = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + + using blas::dot; + + BOOST_TEST( std::abs( dot(x, y) - 14.0 ) < 1e-10 ); + BOOST_TEST( std::abs( 14.0 - dot(x, y) ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_dot_1d_real_double_equal) + { + multi::array const x = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + + using blas::dot; + BOOST_TEST(std::abs( dot(x, y) - dot(x, y) ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_dot_impl_real_double) + { + multi::array const cA = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0, 12.0}, + }; + + double const res1 = blas::dot(cA[1], cA[2]); + BOOST_TEST( std::abs( res1 - std::inner_product(begin(cA[1]), begin(cA[2]), end(cA[1]), 0.0) ) < 1e-10 ); + + auto res2 = std::numeric_limits::quiet_NaN(); + blas::dot(cA[1], cA[2], res2); + BOOST_TEST( std::abs( res2 - std::inner_product(begin(cA[1]), begin(cA[2]), end(cA[1]), 0.0)) < 1e-10 ); + + auto res_nan = std::numeric_limits::quiet_NaN(); + double const res3 = blas::dot(cA[1], cA[2], res_nan); + BOOST_TEST( std::abs( res3 - res2 ) < 1e-10 ); + + double const res4 = blas::dot(cA[1], cA[2]); + BOOST_TEST( std::abs( res4 - std::inner_product(begin(cA[1]), begin(cA[2]), end(cA[1]), 0.0) ) < 1e-10 ); + BOOST_TEST( std::abs( blas::dot(cA[1], cA[2]) - blas::dot(cA[2], cA[1]) ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(inq_case) + { + multi::array const x(multi::extensions_t<1>{multi::iextension{10}}, +1.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array const y(multi::extensions_t<1>{multi::iextension{10}}, -1.0); // NOLINT(readability-identifier-length) BLAS naming + + using blas::conj; + using blas::dot; + using blas::hermitized; + + auto res = dot(x, y); + auto res2 = dot(hermitized(x), y); + + BOOST_TEST( std::abs( +res - +res2 ) < 1e-10 ); + + auto res3 = dot(blas::conj(x), y); // conjugation doesn't do anything for real array + BOOST_TEST( std::abs( res3 - res ) < 1e-10 ); + + auto d_arr = dot(blas::C(x), y); + BOOST_TEST( std::abs( d_arr - res ) < 1e-10 ); + + static_assert(!std::is_same_v); + + using blas::C; + double const d_doub = dot(C(x), y); + + BOOST_TEST( std::abs( d_doub - d_arr ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_dot_impl_complex_double) + { + namespace blas = multi::blas; + + using complex = std::complex; + + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + { 1.0 + I, 2.0 + 3.0 * I, 3.0 + 2.0 * I, 4.0 - 9.0 * I}, + {5.0 + 2.0 * I, 6.0 + 6.0 * I, 7.0 + 2.0 * I, 8.0 - 3.0 * I}, + {9.0 + 1.0 * I, 10.0 + 9.0 * I, 11.0 + 1.0 * I, 12.0 + 2.0 * I}, + }; + + auto c1 = complex{0.0, 0.0}; + blas::dot(A[1], A[2], c1); + BOOST_TEST( c1 == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}) ); + + auto const c2 = +blas::dot(A[1], A[2]); + BOOST_TEST( c2 == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}) ); + + complex const c3 = blas::dot(A[1], A[2]); + BOOST_TEST( c3 == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}) ); + + complex const c4 = blas::dot(A[1], blas::C(A[2])); + BOOST_TEST( + c4 == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}, std::plus<>{}, [](auto alpha, auto omega) { + return alpha * conj(omega);}) + ); + + complex const c5 = blas::dot(blas::C(A[1]), A[2]); + BOOST_TEST( + c5 == inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}, std::plus<>{}, [](auto alpha, auto omega) { + return conj(alpha) * omega;}) + ); + + complex const c6 = blas::dot(blas::conj(A[1]), A[2]); + BOOST_TEST( + c6 + == inner_product( + begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}, std::plus<>{}, + [](auto alpha, auto omega) { return conj(alpha) * omega; } + ) + ); + + complex const c7 = blas::dot(blas::C(A[1]), A[2]); + BOOST_TEST( + c7 + == std::inner_product( + begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}, std::plus<>{}, + [](auto alpha, auto omega) { return conj(alpha) * omega; } + ) + ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_dot_impl_complex_float) + { + namespace blas = multi::blas; + + using complex = std::complex; + + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0F + 1.0F * I, 2.0F + 3.0F * I, 3.0F + 2.0F * I, 4.0F - 9.0F * I}, + {5.0F + 2.0F * I, 6.0F + 6.0F * I, 7.0F + 2.0F * I, 8.0F - 3.0F * I}, + {9.0F + 1.0F * I, 10.0F + 9.0F * I, 11.0F + 1.0F * I, 12.0F + 2.0F * I}, + }; + + auto c1 = complex{0.0F, 0.0F}; + blas::dot(A[1], A[2], c1); + BOOST_TEST( c1 == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0F, 0.0F}) ); + + auto const c2 = +blas::dot(A[1], A[2]); + BOOST_TEST( c2 == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0F, 0.0F}) ); + + complex const c3 = blas::dot(A[1], A[2]); + BOOST_TEST( c3 == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0F, 0.0F}) ); + + complex const c4 = blas::dot(A[1], blas::C(A[2])); + + BOOST_TEST( + c4 + == std::inner_product(A[1].begin(), A[1].end(), A[2].begin(), complex{0.0F, 0.0F}, std::plus<>{}, [](auto al, auto om) { return al * conj(om); }) + ); + + complex const c5 = blas::dot(blas::C(A[1]), A[2]); + BOOST_TEST( + c5 + == std::inner_product(A[1].begin(), A[1].end(), A[2].begin(), complex{0.0F, 0.0F}, std::plus<>{}, [](auto al, auto om) { return conj(al) * om; }) + ); + + complex const c6 = blas::dot(blas::conj(A[1]), A[2]); + BOOST_TEST( + c6 + == std::inner_product(A[1].begin(), A[1].end(), A[2].begin(), complex{0.0F, 0.0F}, std::plus<>{}, [](auto al, auto om) { return conj(al) * om; }) + ); + + complex const c7 = blas::dot(blas::C(A[1]), A[2]); + BOOST_TEST( + c7 + == std::inner_product(A[1].begin(), A[1].end(), A[2].begin(), complex{0.0F, 0.0F}, std::plus<>{}, [](auto al, auto om) { return conj(al) * om; }) + ); + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_second_double) + { + namespace blas = multi::blas; + + using complex = std::complex; + using Alloc = std::allocator; // thrust::cuda::allocator; + + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + + auto CC = C; + + auto const [is, js] = C.extensions(); + std::for_each(is.begin(), is.end(), [&, Js = js](auto ii) { // NOLINT(modernize-use-ranges) for C++20 + std::for_each(Js.begin(), Js.end(), [&](auto jj) { // NOLINT(modernize-use-ranges) for C++20 + C[ii][jj] *= 0.0; + std::for_each(B.extension().begin(), B.extension().end(), [&](auto kk) { + C[ii][jj] += A[ii][kk] * conj(B[kk][jj]); + }); + }); + }); + + // TODO(correaa) MKL gives an error here + // unknown location(0): fatal error: in "cublas_one_gemv_complex_conjtrans_zero": memory access violation at address: 0x00000007: no mapping at fault address + + std::transform(begin(A), end(A), begin(CC), begin(CC), [BT = B.transposed()](auto const& Ar, auto&& Cr) { // NOLINT(modernize-use-ranges) for C++20 + return std::transform( + begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto const& Ce) { + return (std::complex{1.0, 0.0} * blas::dot(Ar, blas::C(Bc))) + (0.0 * Ce); + } + ), + std::forward(Cr); + }); + + BOOST_TEST( std::abs( static_cast(CC[1][0]).real() - static_cast(C[1][0]).real() ) < 1e-10 ); + BOOST_TEST( std::abs( static_cast(CC[1][0]).imag() - static_cast(C[1][0]).imag() ) < 1e-10 ); + + BOOST_TEST( std::abs( static_cast(CC[0][1]).real() - static_cast(C[0][1]).real() ) < 1e-10 ); + BOOST_TEST( std::abs( static_cast(CC[0][1]).imag() - static_cast(C[0][1]).imag() ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_second_float) { + // namespace blas = multi::blas; + + // using complex = std::complex; + // using Alloc = std::allocator; // thrust::cuda::allocator; + + // auto const I = complex{0.0F, 1.0F}; // NOLINT(readability-identifier-length) + + // // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + // multi::array const A = { + // {1.0F - 2.0F * I, 9.0F - 1.0F * I}, + // {2.0F + 3.0F * I, 1.0F - 2.0F * I}, + // }; + + // // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + // multi::array const B = { + // {3.0F - 4.0F * I, 19.0F - 1.0F * I}, + // {1.0F + 5.0F * I, 8.0F - 8.0F * I}, + // }; + + // multi::array C({2, 2}, {3.0F, 0.0F}); // NOLINT(readability-identifier-length) conventional BLAS naming + + // auto CC = C; + + // auto const [is, js] = C.extensions(); + // std::for_each(is.begin(), is.end(), [&, js = js](auto ii) { + // std::for_each(js.begin(), js.end(), [&](auto jj) { + // C[ii][jj] *= 0.0F; + // std::for_each(B.extension().begin(), B.extension().end(), [&](auto kk) { + // C[ii][jj] += A[ii][kk] * conj(B[kk][jj]); + // }); + // }); + // }); + + // // TODO(correaa) MKL gives an error here + // // unknown location(0): fatal error: in "cublas_one_gemv_complex_conjtrans_zero": + // // memory access violation at address: 0x00000007: no mapping at fault address + + // std::transform(begin(A), end(A), begin(CC), begin(CC), [BT = B.transposed()](auto const& Ar, auto&& Cr) { + // std::transform( + // begin(BT), end(BT), begin(Cr), begin(Cr), + // [&Ar](auto const& Bc, auto const& Ce) { return complex{1.0F, 0.0F} * blas::dot(Ar, blas::C(Bc)) + 0.0F * Ce; } + // ); + // return std::forward(Cr); + // }); + + // std::cerr << "CC[1][0] = " << static_cast(CC[1][0]) << '\n'; + // std::cerr << "C [1][0] = " << static_cast(C [1][0]) << '\n'; + + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // std::cerr << "CC[0][1] = " << static_cast(CC[0][1]) << '\n'; + // std::cerr << "C [0][1] = " << static_cast(C [0][1]) << '\n'; + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/blas/test/gemm.cpp b/include/boost/multi/adaptors/blas/test/gemm.cpp new file mode 100644 index 000000000..c8dc08000 --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/gemm.cpp @@ -0,0 +1,1959 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for context +#include // for gemm, gemm_range + +#include +// IWYU pragma: no_include "boost/multi/adaptors/blas/numeric.hpp" // for involuter, conju... +#include // for H, T, (anonymous) +#include // for layout_t, array + +#include // for abs // IWYU pragma: keep +#include // for complex, operator* +#include // for begin, size +// IWYU pragma: no_include +// IWYU pragma: no_include // for forward +// IWYU pragma: no_include // for abs +// IWYU pragma: no_include // for exception + +namespace multi = boost::multi; +namespace blas = multi::blas; + +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ +#define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) +// #define BOOST_REQUIRE_SMALL(X, ToL) BOOST_TEST( std::abs( X ) < (ToL) ) + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_T_sub) { + namespace blas = multi::blas; + + multi::array A({100, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array B({4, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm(1.0, A({0, 100}, {1, 2}), blas::T(B)({0, 1}, {0, 1}), 0.0, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST(C[99][0] == 1.0); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_H_sub) { + multi::array A({100, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array B({4, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm(1., A({0, 100}, {1, 2}), blas::H(B)({0, 1}, {0, 1}), 0.0, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST(C[99][0] == 1.0); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_H_sub_6) { + multi::array A({100, 4}, 2.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array B({4, 4}, 3.0); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm(1., A({0, 100}, {1, 2}), blas::H(B)({0, 1}, {0, 1}), 0.0, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST(C[99][0] == 6.0); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_H_copy) { + multi::array A({100, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array B({4, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + + auto C = +blas::gemm(1., A({0, 100}, {1, 2}), blas::H(B)({2, 3}, {2, 3})); // c=ab, c⸆=b⸆a⸆ // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST(C[99][0] == 1.0); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_complex_100x1_1x1) { + using complex = std::complex; + multi::array const A({100, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({1, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, {0.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm({1.0, 0.0}, A, B, {0.0, 0.0}, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST(C[99][0] == 1.0); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_complex_100x1_1x1_T) { + using complex = std::complex; + multi::array const A({100, 1}, complex{1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({1, 1}, complex{1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, complex{0.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm(complex{1.0, 0.0}, A, blas::T(B), complex{0.0, 0.0}, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( C[99][0] == 1.0 ); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_complex_100x1_1x1_H) { + using complex = std::complex; // complex const I{0, 1}; + multi::array const A({100, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({1, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, {0.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm({1.0, 0.0}, A, blas::H(B), {0.0, 0.0}, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( C[99][0] == 1.0 ); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1) { + using complex = std::complex; + multi::array const A({100, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({1, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, {0.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm({1.0, 0.0}, A, B, {0.0, 0.0}, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST(C[99][0] == 1.0); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_T) { + multi::array const A({100, 1}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({1, 1}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm(1.0, A, blas::T(B), 0.0, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST(C[99][0] == 1.0); + } + + BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_H) { + multi::array const A({100, 1}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({1, 1}, 1.0); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming + + blas::gemm(1.0, A, blas::H(B), 0.0, C); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST(C[99][0] == 1.0); + } + + BOOST_AUTO_TEST_CASE(multi_blas_gemm_square_real) { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + {1.0, 3.0, 4.0}, + {9.0, 7.0, 1.0}, + {1.0, 2.0, 3.0}, + }; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const b = { + {11.0, 12.0, 4.0}, + { 7.0, 19.0, 1.0}, + {11.0, 12.0, 4.0}, + }; + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, a, b, 0.0, c); + BOOST_TEST( c[2][1] == 86.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + BOOST_TEST( size( a) == size( c) ); + BOOST_TEST( size(~b) == size(~c) ); + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); + BOOST_TEST( c[2][1] == 86.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, a, blas::T(b), 0.0, c); + BOOST_TEST( c[2][1] == 48.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1., a.begin(), a.size(), blas::T(b).begin(), 0.0, c.begin()); + BOOST_TEST( c[2][1] == 48.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, blas::T(a), b, 0.0, c); + BOOST_TEST( c[2][1] == 103.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1.0, begin(blas::T(a)), size(blas::T(a)), begin(b), 0.0, begin(c)); + BOOST_TEST( c[2][1] == 103.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, blas::T(a), blas::T(b), 0.0, c); + BOOST_TEST( c[2][1] == 50.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1.0, begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0.0, begin(c)); + BOOST_TEST( c[2][1] == 50.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, a, blas::T(b), 0.0, c); + BOOST_TEST( c[2][1] == 48.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1.0, begin(a), size(a), begin(blas::T(b)), 0.0, begin(c)); + BOOST_TEST( c[2][1] == 48.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, blas::T(a), b, 0.0, c); + BOOST_TEST( c[2][1] == 103.0 ); + } + { + multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1.0, begin(blas::T(a)), size(blas::T(a)), begin(b), 0.0, begin(c)); + BOOST_TEST( c[2][1] == 103.0 ); + } + { + multi::array c({a.size(), b.rotated().size()}, 9999.0); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm(2.0, blas::H(a), blas::H(b), 0.0, c); + BOOST_TEST( c[2][1] == 100.0 ); + } + { + multi::array c = blas::gemm(2.0, blas::H(a), blas::H(b)); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( c[2][1] == 100.0 ); + } + { + multi::array const c = blas::gemm(2.0, blas::H(a), blas::H(b)); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( c[2][1] == 100.0 ); + } + { + multi::array c({a.size(), b.rotated().size()}, 9999.0); // NOLINT(readability-identifier-length) BLAS naming + c = blas::gemm(2.0, blas::H(a), blas::H(b)); + BOOST_TEST( c[2][1] == 100.0 ); + } + { + multi::array c; // NOLINT(readability-identifier-length) BLAS naming + c = blas::gemm(2.0, blas::H(a), blas::H(b)); + BOOST_TEST( c[2][1] == 100.0 ); + } + { + multi::array c({a.size(), b.rotated().size()}, 9999.0); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm_n(2.0, begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0.0, begin(c)); + BOOST_TEST( c[2][1] == 100.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_square) { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + {1.0, 3.0}, + {9.0, 7.0}, + }; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const b = { + {11.0, 12.0}, + { 7.0, 19.0}, + }; + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 148.0 ); + } + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + + blas::context const ctxt; + blas::gemm_n(&ctxt, 1.0, begin(a), size(a), begin(b), 0.0, begin(c)); + BOOST_TEST( c[1][0] == 148.0 ); + } + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, ~a, b, 0.0, c); // c=a⸆b, c⸆=b⸆a + BOOST_TEST(( c[1][1] == 169.0 && c[1][0] == 82.0 )); + } + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + + blas::context const ctxt; + blas::gemm_n(&ctxt, 1.0, begin(~a), size(~a), begin(b), 0.0, begin(c)); + BOOST_TEST( std::abs( c[1][1] - 169.0 ) < 1e-10 ); + BOOST_TEST( std::abs( c[1][0] - 82.0 ) < 1e-10 ); + } + { + multi::array const c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + + blas::context const ctxt; + blas::gemm_n(&ctxt, 1.0, begin(~a), size(~a), begin(b), 0.0, begin(~c)); + BOOST_TEST( std::abs( (~c)[1][1] - 169 ) < 1e-10 ); + BOOST_TEST( std::abs( (~c)[1][0] - 82 ) < 1e-10 ); + } + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, a, ~b, 0.0, c); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( c[1][0] == 183.0 ); + } + { + // TODO(correaa) fix sfinae of const c + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + + blas::context const ctxt; + blas::gemm_n(&ctxt, 1.0, begin(a), size(a), begin(~b), 0.0, begin(c)); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( c[1][0] == 183.0 ); + } + { + // NOLINTNEXTLINE(misc-const-correctness) TODO(correaa) fix sfinae of const c + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, a, ~b, 0.0, ~c); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( (~c)[1][0] == 183.0 ); + } + { + // NOLINTNEXTLINE(misc-const-correctness) TODO(correaa) fix sfinae of const c + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1.0, begin(a), size(a), begin(~b), 0.0, begin(~c)); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( (~c)[1][0] == 183.0 ); + } + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, ~a, ~b, 0.0, c); // c=a⸆b⸆, c⸆=ba + BOOST_TEST( c[1][0] == 117.0 ); + } + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1.0, begin(~a), size(~a), begin(~b), 0.0, begin(c)); // c=a⸆b⸆, c⸆=ba + BOOST_TEST( c[1][0] == 117.0 ); + } + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, ~a, ~b, 0.0, ~c); // c⸆=a⸆b⸆, c=ba + BOOST_TEST( c[0][1] == 117.0 ); + } + { + multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1.0, begin(~a), size(~a), begin(~b), 0.0, begin(~c)); // c⸆=a⸆b⸆, c=ba + BOOST_TEST( c[0][1] == 117.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare) { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + {1.0, 3.0, 1.0}, + {9.0, 7.0, 1.0}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const b = { + {11.0, 12.0, 1.0}, + { 7.0, 19.0, 1.0}, + { 1.0, 1.0, 1.0}, + }; + { + multi::array c({2, 3}); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( std::abs( c[1][2] - 17 ) < 1e-10 ); + } + { + multi::array c({2, 3}); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( std::abs( c[1][2] - 17.0 ) < 1e-10 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare_automatic) { + namespace blas = multi::blas; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + {1.0, 3.0, 1.0}, + {9.0, 7.0, 1.0}, + }; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const b = { + {11.0, 12.0, 4.0, 8.0}, + { 7.0, 19.0, 2.0, 7.0}, + { 5.0, 3.0, 3.0, 1.0}, + }; + { + multi::array c({size(a), size(~b)}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 53.0 ); + } + { + multi::array c({size(a), size(~b)}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 53.0 ); + } + { + multi::array c({2, 4}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm(0.1, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); + } + { + multi::array c({2, 4}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n(0.1, begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); + } + { + auto c = +blas::gemm(0.1, a, b); // c=ab, c⸆=b⸆a⸆ // NOLINT(readability-identifier-length) conventional BLAS naming + BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); + } + { + multi::array c = blas::gemm(0.1, a, b); // NOLINT(readability-identifier-length) conventional BLAS naming + BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_gemm_nh) { + using complex = std::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + { + auto c = +blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c = blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( c[1][0] == 7.-10.*I ); + BOOST_TEST( c[0][1] == 7.+10.*I ); + } + { + multi::array c = blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† // NOLINT(readability-identifier-length) conventional BLAS naming + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + c = blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + c() = blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.*I ); + } + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm({1.0, 0.0}, a, blas::H(a), {0.0, 0.0}, c); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm_n({1.0, 0.0}, begin(a), size(a), begin(blas::H(a)), {0.0, 0.0}, begin(c)); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7. - 10.*I ); + BOOST_TEST( c[0][1] == 7. + 10.*I ); + } + } + +#ifdef CUDA_FOUND +#include + BOOST_AUTO_TEST_CASE(multi_blas_gemm_nh_thrust) { + using complex = thrust::complex; + complex const I{0.0, 1.0}; + multi::array const a = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I} + }; + { + auto c = +blas::gemm(1.0, a, blas::hermitized(a)); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c = blas::gemm(1.0, a, blas::hermitized(a)); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c = blas::gemm(1.0, a, blas::hermitized(a)); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c({2, 2}); + c = blas::gemm(1.0, a, blas::hermitized(a)); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., a, blas::hermitized(a), 0.0, c); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1.0, begin(a), size(a), begin(blas::H(a)), 0.0, begin(c)); // c=aa†, c†=aa† + BOOST_TEST( c[1][0] == 7.0 - 10.0*I ); + BOOST_TEST( c[0][1] == 7.0 + 10.0*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_gemm_elongated) { + using complex = std::complex; + complex const I{0.0, 1.0}; + multi::array const a = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I} + }; + { + multi::array c({1, 1}); + blas::gemm(1.0, a, blas::H(a), 0.0, c); // c=aa†, c†=aa† + BOOST_TEST( c[0][0] == 87.0 + 0.0*I ); + } + { + multi::array c({1, 1}); + blas::gemm_n(1.0, begin(a), size(a), begin(blas::H(a)), 0.0, begin(c)); // c=aa†, c†=aa† + BOOST_TEST( c[0][0] == 87.0 + 0.0*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1_bisbis) { + using complex = std::complex; + complex const I{0.0, 1.0}; + multi::array const a = { + {1.0 + 2.0 * I}, + {9.0 - 1.0 * I}, + {1.0 + 1.0 * I} + }; + multi::array const b = { + {11.0 - 2.0 * I, 7.0 - 3.0 * I, 8.0 - 1.0 * I} + }; + { + multi::array c({1, 1}); + + BOOST_TEST( size(blas::H(a)) == 1 ); + BOOST_TEST( size(blas::H(b)[0]) == 1 ); + + blas::gemm(1.0, blas::H(a), blas::H(b), 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 84.0 + 7.0*I ); + } + { + multi::array c({1, 1}); + blas::gemm_n(1.0, begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 84.0 + 7.0*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_empty) { + multi::array const a({0, 5}); + BOOST_TEST( size( a) == 0 ); + BOOST_TEST( size(~a) == 5 ); + BOOST_TEST( a.is_empty() ); + + multi::array const b({5, 0}); + BOOST_TEST( size( b) == 0 ); + BOOST_TEST( size(~b) == 0 ); + BOOST_TEST( b.is_empty() ); + { + multi::array c; + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + } + { + multi::array c; + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare2) { + multi::array const a = { + {1.0, 3.0}, + {9.0, 7.0}, + {1.0, 1.0}, + }; + multi::array const b = { + {11.0, 12.0}, + { 7.0, 19.0}, + }; + { + multi::array c({size(a), size(~b)}); + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[2][1] == 31.0 ); + } + { + multi::array c({size(a), size(~b)}); + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[2][1] == 31.0 ); + } + { + multi::array c({size(~b), size(a)}); + blas::gemm(1.0, a, b, 0.0, ~c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 31.0 ); + } + { + multi::array c({size(~b), size(a)}); + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(~c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 31.0 ); + } + { + auto ar = +~a; + multi::array c({3, 2}); + blas::gemm(1.0, ~ar, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[2][1] == 31.0 ); + } + { + auto ar = +~a; + multi::array c({3, 2}); + blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[2][1] == 31.0 ); + } + { + auto ar = +~a; + multi::array c({2, 3}); + blas::gemm(1.0, ~ar, b, 0.0, ~c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 31.0 ); + } + { + auto ar = +~a; + multi::array c({2, 3}); + blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(~c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 31.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x2_2x2) { + multi::array const a = { + {1.0, 3.0}, + {9.0, 4.0}, + }; + multi::array const b = { + {11.0, 12.0}, + { 7.0, 19.0}, + }; + { + multi::array c({2, 2}); + blas::gemm(1.0, ~a, b, 0.0, c); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[1][0] == 61.0 ); + + blas::gemm(1.0, ~a, b, 0.0, ~c); // c⸆=a⸆b, c=b⸆a + BOOST_TEST( c[0][1] == 61.0 ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1.0, begin(~a), size(~a), begin(b), 0.0, begin(c)); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[1][0] == 61.0 ); + + blas::gemm_n(1.0, begin(~a), size(~a), begin(b), 0.0, begin(~c)); // c⸆=a⸆b, c=b⸆a + BOOST_TEST( c[0][1] == 61.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x2) { + multi::array const a = { + {1.0, 3.0}, + {9.0, 4.0}, + {1.0, 5.0}, + }; + multi::array const b = { + {11.0, 12.0}, + { 7.0, 19.0}, + { 8.0, 1.0}, + }; + { + multi::array c({2, 2}); + blas::gemm(1.0, ~a, b, 0.0, c); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[1][0] == 101.0 ); + + blas::gemm(1., ~a, b, 0., ~c); // c⸆=a⸆b, c=b⸆a + BOOST_TEST( c[0][1] == 101 ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1.0, begin(~a), size(~a), begin(b), 0.0, begin(c)); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[1][0] == 101.0 ); + + blas::gemm_n(1.0, begin(~a), size(~a), begin(b), 0.0, begin(~c)); // c⸆=a⸆b, c=b⸆a + BOOST_TEST( c[0][1] == 101.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_3x2) { + multi::array const a = { + {1.0, 9.0, 1.0} + }; + BOOST_TEST( stride(~a) == 1 ); + BOOST_TEST( stride( a) == 3 ); + multi::array const b = { + {11.0, 12.0}, + { 7.0, 19.0}, + { 8.0, 1.0}, + }; + { + multi::array c({size(a), size(~b)}); + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 184.0 ); + } + { + multi::array c({size(a), size(~b)}); + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 184.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar)}); + blas::gemm(1.0, ~ar, b, 0.0, ~c); // c⸆=a⸆b, c=b⸆a + BOOST_TEST( c[1][0] == 184.0 ); + } + { + auto ar = +~a; + BOOST_TEST( size(~ar) == 1 ); + BOOST_TEST( begin(~ar).stride() == 1 ); + BOOST_TEST( begin(~ar)->stride() == 1 ); + BOOST_TEST( begin( ar)->stride() == 1 ); + + multi::array c({size(~b), size(~ar)}); + BOOST_TEST( begin( c).stride() == 1 ); + BOOST_TEST( begin(~c).stride() == 1 ); + BOOST_TEST( begin(c)->stride() == 1 ); + + BOOST_TEST( begin(b) ); + blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(~c)); // c⸆=a⸆b, c=b⸆a + BOOST_TEST( c[1][0] == 184.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complexreal_1x3_3x2) { + using complex = std::complex; + multi::array const a = { + {1.0, 9.0, 1.0} + }; + BOOST_TEST( stride(~a) == 1 ); + BOOST_TEST( stride( a) == 3 ); + multi::array const b = { + {11.0, 12.0}, + { 7.0, 19.0}, + { 8.0, 1.0}, + }; + { + multi::array c({size(a), size(~b)}); + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 184.0 ); + } + { + multi::array c({size(a), size(~b)}); + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 184.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar)}); + blas::gemm(1.0, ~ar, b, 0.0, ~c); // c⸆=a⸆b, c=b⸆a + BOOST_TEST( c[1][0] == 184.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar)}); + blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(~c)); // c⸆=a⸆b, c=b⸆a + BOOST_TEST( c[1][0] == 184.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_part_3x2) { + multi::array const a = { + {1.0, 9.0, 1.0}, + {3.0, 3.0, 3.0}, + }; + BOOST_TEST( stride(~a) == 1 ); + BOOST_TEST( stride( a) == 3 ); + multi::array const b = { + {11.0, 12.0}, + { 7.0, 19.0}, + { 8.0, 1.0}, + }; + { + multi::array c({size(a({0, 1})), size(~b)}); + blas::gemm(1.0, a({0, 1}), b, 0.0, c); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[0][1] == 184.0 ); + } + { + multi::array c({size(a({0, 1})), size(~b)}); + blas::gemm_n(1.0, begin(a({0, 1})), size(a({0, 1})), begin(b), 0.0, begin(c)); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[0][1] == 184.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); + blas::gemm(1.0, ~(ar(extension(ar), {0, 1})), b, 0.0, ~c); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[1][0] == 184.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); + blas::gemm_n(1.0, begin(~(ar(extension(ar), {0, 1}))), size(~(ar(extension(ar), {0, 1}))), begin(b), 0., begin(~c)); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[1][0] == 184.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complexreal_1x3_part_3x2) { + using complex = std::complex; + multi::array const a = { + {1.0, 9.0, 1.0}, + {3.0, 3.0, 3.0}, + }; + BOOST_TEST( stride(~a) == 1 ); + BOOST_TEST( stride( a) == 3 ); + multi::array const b = { + {11.0, 12.0}, + { 7.0, 19.0}, + { 8.0, 1.0} + }; + { + multi::array c({size(a({0, 1})), size(~b)}); + blas::gemm(1.0, a({0, 1}), b, 0.0, c); + BOOST_TEST( c[0][1] == 184.0 ); + } + { + multi::array c({size(a({0, 1})), size(~b)}); + blas::gemm_n(1.0, begin(a({0, 1})), size(a({0, 1})), begin(b), 0.0, begin(c)); + BOOST_TEST( c[0][1] == 184.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); + blas::gemm(1.0, ~(ar(extension(ar), {0, 1})), b, 0.0, ~c); + BOOST_TEST( c[1][0] == 184.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); + blas::gemm_n(1.0, begin(~(ar(extension(ar), {0, 1}))), size(~(ar(extension(ar), {0, 1}))), begin(b), 0.0, begin(~c)); + BOOST_TEST( c[1][0] == 184.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x1) { + multi::array const a = { + {1.0, 9.0, 1.0}, + {3.0, 3.0, 3.0}, + }; + BOOST_TEST( stride(~a) == 1 ); + BOOST_TEST( stride( a) == 3 ); + multi::array const b = { + {11.0}, + {7.0}, + {8.0} + }; + { + multi::array c({size(a), size(~b)}); + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 82.0 ); + BOOST_TEST( c[1][0] == 78.0 ); + } + { + multi::array c({size(a), size(~b)}); + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[0][0] == 82.0 ); + BOOST_TEST( c[1][0] == 78.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); + blas::gemm(1.0, ~(ar(extension(ar), {0, 1})), b, 0.0, ~c); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[0][0] == 82.0 ); + } + { + auto ar = +~a; + multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); + blas::gemm_n(1., begin(~(ar(extension(ar), {0, 1}))), size(~(ar(extension(ar), {0, 1}))), begin(b), 0., begin(~c)); // c=a⸆b, c⸆=b⸆a + BOOST_TEST( c[0][0] == 82.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x1_bis) { + multi::array const a = { + {1.0, 9.0, 1.0}, + {3.0, 4.0, 5.0}, + }; + multi::array const b = { + {11.0}, + {7.0}, + {8.0} + }; + + { + multi::array c({1, 2}); + blas::gemm(1.0, a, b, 0.0, ~c); // c⸆=ab, c=b⸆a⸆ + BOOST_TEST( (~c)[0][0] == 82.0 ); + BOOST_TEST( (~c)[1][0] == 101.0 ); + } + { + multi::array c({1, 2}); + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(~c)); // c⸆=ab, c=b⸆a⸆ + BOOST_TEST( (~c)[0][0] == 82.0 ); + BOOST_TEST( (~c)[1][0] == 101.0 ); + } + { + multi::array c({2, 1}); + blas::gemm(1.0, a, b, 0.0, c); // c⸆=ab, c=b⸆a⸆ + BOOST_TEST( (~c)[0][1] == 101.0 ); + BOOST_TEST( c[1][0] == 101.0 ); + } + { + multi::array c({2, 1}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c⸆=ab, c=b⸆a⸆ + BOOST_TEST( (~c)[0][1] == 101.0 ); + BOOST_TEST( c[1][0] == 101.0 ); + } + { + multi::array c({1, 2}); + auto ar = +~a; + blas::gemm(1., ~ar, b, 0., ~c); // c⸆=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 101.0 ); + } + { + multi::array c({1, 2}); + auto ar = +~a; + blas::gemm_n(1., begin(~ar), size(~ar), begin(b), 0., begin(~c)); // c⸆=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 101.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_3x1) { + multi::array const a = { + {1.0, 9.0, 1.0}, + }; + multi::array const b = { + {11.0}, + {7.0}, + {8.0} + }; + { + multi::array c({1, 1}); + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 82.0 ); + } + { + multi::array c({1, 1}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); + BOOST_TEST( c[0][0] == 82.0 ); + } + { + multi::array c({1, 1}); + auto ar = +~a; + blas::gemm(1.0, ~ar, b, 0.0, c); + BOOST_TEST( c[0][0] == 82.0 ); + } + { + multi::array c({1, 1}); + auto ar = +~a; + blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(c)); + BOOST_TEST( c[0][0] == 82.0 ); + } + { + multi::array c({1, 1}); + auto br = +~b; + blas::gemm(1.0, a, ~br, 0.0, c); + BOOST_TEST( c[0][0] == 82.0 ); + } + { + multi::array c({1, 1}); + BOOST_TEST( begin(c). stride() == 1 ); + BOOST_TEST( begin(c)->stride() == 1 ); + + auto br = +~b; + // BOOST_TEST( begin(br). stride() == 1 ); + BOOST_TEST( begin( br)->stride() == 1 ); + + BOOST_TEST(begin(a)->stride() == 1); + BOOST_TEST( begin(~br). stride() == 1 ); + // BOOST_TEST( begin(~br)->stride() == 1 ); + BOOST_TEST(begin(c)->stride() == 1); + BOOST_TEST(begin(c).stride() == 1); + BOOST_TEST(size(a) == 1); + + blas::gemm_n(1.0, begin(a), size(a), begin(~br), 0.0, begin(c)); + BOOST_TEST( c[0][0] == 82.0 ); + } + { + multi::array c({1, 1}); + auto br = +~b; + blas::gemm(1.0, a, blas::H(br), 0.0, c); + BOOST_TEST( c[0][0] == 82.0 ); + } + { + multi::array c({1, 1}); + auto br = +~b; + blas::gemm_n(1.0, begin(a), size(a), begin(blas::H(br)), 0.0, begin(c)); + BOOST_TEST( c[0][0] == 82.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square) { + using complex = std::complex; + constexpr complex I{0.0, 1.0}; + multi::array const a = { + {1.0 + 3.0 * I, 3.0 + 2.0 * I}, + {9.0 + 1.0 * I, 7.0 + 1.0 * I}, + }; + multi::array const b = { + {11.0 + 2.0 * I, 12.0 + 4.0 * I}, + { 7.0 + 1.0 * I, 19.0 - 9.0 * I}, + }; + { + multi::array c({2, 2}); + blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 145.0 + 43.0*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 145. + 43.*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., ~a, b, 0., c); // c=a⸆b, c⸆=b⸆a + BOOST_TEST(( c[1][1] == 170.-8.*I && c[1][0] == 77.+42.*I )); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(~a), size(~a), begin(b), 0., begin(c)); // c=a⸆b, c⸆=b⸆a + BOOST_TEST(( c[1][1] == 170.-8.*I && c[1][0] == 77.+42.*I )); + } + { + multi::array c({2, 2}); + blas::gemm(1., a, ~b, 0., c); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( c[1][0] == 177.+69.*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(a), size(a), begin(~b), 0., begin(c)); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( c[1][0] == 177.+69.*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=a⸆b⸆, c⸆=ba + BOOST_TEST( c[1][0] == 109. + 68.*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0., begin(c)); // c=a⸆b⸆, c⸆=ba + BOOST_TEST( c[1][0] == 109. + 68.*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::T(a), blas::T(b), 0., blas::T(c)); // c⸆=a⸆b⸆, c=ba + BOOST_TEST( c[0][1] == 109.+68.*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0., begin(blas::T(c))); // c⸆=a⸆b⸆, c=ba + BOOST_TEST( c[0][1] == 109.+68.*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_1x3_3x1) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a = { + {1. + 2. * I, 9. - 1. * I, 1. + 1. * I}, + }; + multi::array const b = { + {11. - 2. * I}, + {7. - 3. * I}, + {8. - 1. * I} + }; + { + multi::array c({1, 1}); + blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 84.-7.*I ); + } + { + multi::array c({1, 1}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 84.-7.*I ); + } + { + multi::array c({1, 1}); + auto ar = +~a; + blas::gemm(1., ~ar, b, 0., c); // c=ab, c⸆=ba + BOOST_TEST( c[0][0] == 84.-7.*I ); + } + { + multi::array c({1, 1}); + auto ar = +~a; + blas::gemm_n(1., begin(~ar), size(~ar), begin(b), 0., begin(c)); // c=ab, c⸆=ba + BOOST_TEST( c[0][0] == 84.-7.*I ); + } + { + multi::array c({1, 1}); + auto br = +~b; + blas::gemm(1., a, ~br, 0., c); + BOOST_TEST( c[0][0] == 84.-7.*I ); + } + { + multi::array c({1, 1}); + auto br = +~b; + blas::context ctxt; + blas::gemm_n(ctxt, 1., begin(a), size(a), begin(~br), 0., begin(c)); + BOOST_TEST( c[0][0] == 84.-7.*I ); + } + { + multi::array c({1, 1}); + auto br = +~b; + blas::gemm(1., a, blas::H(br), 0., ~c); + BOOST_TEST( c[0][0] == 80. + 53.*I ); + } + { + multi::array c({1, 1}); + auto br = +~b; + blas::gemm_n(1., begin(a), size(a), begin(blas::H(br)), 0., begin(~c)); + BOOST_TEST( c[0][0] == 80. + 53.*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_hermitized_square) { + using complex = std::complex; + constexpr complex I{0, 1}; + multi::array const a = { + {1. + 3. * I, 3. + 2. * I}, + {9. + 1. * I, 7. + 1. * I}, + }; + multi::array const b = { + {11. + 2. * I, 12. + 4. * I}, + { 7. + 1. * I, 19. - 9. * I}, + }; + { + multi::array c({2, 2}); + blas::gemm(1., a, b, 0., c); // c=ab, c†=b†a† + BOOST_TEST( c[1][0] == 145. + 43.*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c†=b†a† + BOOST_TEST( c[1][0] == 145. + 43.*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::H(a), blas::H(b), 0., c); // c=a†b†, c†=ba + BOOST_TEST( c[1][0] == 109.0 - 68.0*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0., begin(c)); // c=a†b†, c†=ba + BOOST_TEST( c[1][0] == 109.0 - 68.0*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::H(a), blas::H(b), 0., blas::H(c)); // c†=a†b†, c=ba + BOOST_TEST( c[1][0] == 184.0 - 40.*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::H(a), b, 0., c); // c=a†b, c†=b†a + BOOST_TEST( c[1][0] == 87.0 - 16.0*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=a†b, c†=b†a + BOOST_TEST( c[1][0] == 87.0 - 16.*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., a, blas::H(b), 0., c); // c=ab†, c†=ba† + BOOST_TEST( c[1][0] == 189.0 - 23.0*I ); + } + { + multi::array c({2, 2}); + c = blas::gemm(1., a, blas::H(b)); // c=ab†, c†=ba† + BOOST_TEST( c[1][0] == 189.0 - 23.0*I ); + } + { + multi::array c = blas::gemm(1., a, blas::H(b)); // c=ab†, c†=ba† + BOOST_TEST( size(c) == 2 ); + BOOST_TEST( c[1][0] == 189.0 - 23.0*I ); + } + { + auto c = multi::array(blas::gemm(1., a, blas::H(b))); // c=ab†, c†=ba† + BOOST_TEST( size(c) == 2 ); + BOOST_TEST( c[1][0] == 189.0 - 23.0*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab†, c†=ba† + BOOST_TEST( c[1][0] == 189. - 23.*I ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::H(a), blas::H(b), 0., c); // c=a†b†, c†=ba + BOOST_TEST( c[1][0] == 109. - 68.*I); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0., begin(c)); // c=a†b†, c†=ba + BOOST_TEST( c[1][0] == 109. - 68.*I); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a = { + {1. + 2. * I}, + {9. - 1. * I}, + {1. + 1. * I} + }; + multi::array const b = { + {11. - 2. * I}, + {7. - 3. * I}, + {8. - 1. * I} + }; + { + multi::array c({1, 1}); + blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 80.-53.*I ); + } + { + multi::array c({1, 1}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 80.-53.*I ); + } + { + multi::array c({1, 1}); + blas::gemm(1., blas::H(a), b, 0., c); // c=a†b, c†=b†a + BOOST_TEST( c[0][0] == 80.-53.*I ); + } + { + multi::array c({1, 1}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=a†b, c†=b†a + BOOST_TEST( c[0][0] == 80.-53.*I ); + } + { + multi::array c({1, 1}); + auto ha = +blas::hermitized(a); + blas::gemm(1., ha, b, 0., c); + BOOST_TEST( c[0][0] == 80.-53.*I ); + + blas::gemm(1., blas::H(b), a, 0., c); + BOOST_TEST( c[0][0] == 80.+53.*I ); + } + { + multi::array c({1, 1}); + auto ha = +blas::hermitized(a); + blas::gemm_n(1., begin(ha), size(ha), begin(b), 0., begin(c)); + BOOST_TEST( c[0][0] == 80.-53.*I ); + + blas::gemm_n(1., begin(blas::H(b)), size(blas::H(b)), begin(a), 0., begin(c)); + BOOST_TEST( c[0][0] == 80.+53.*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_1x3_3x2) { + using complex = std::complex; + constexpr complex I{0, 1}; + multi::array const a = { + {1. + 2. * I, 9. - 1. * I, 1. + 1. * I} + }; + multi::array const b = { + {11. - 2. * I, 5. + 2. * I}, + { 7. - 3. * I, 2. + 1. * I}, + { 8. - 1. * I, 1. + 1. * I} + }; + { + multi::array c({1, 2}); + blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 20.+21.*I ); + } + { + multi::array c({1, 2}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 20.+21.*I ); + } + { + auto ar = +~a; + multi::array c({1, 2}); + blas::gemm(1., blas::H(ar), b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 28.+3.*I ); + } + { + auto ar = +~a; + multi::array c({1, 2}); + blas::gemm_n(1., begin(blas::H(ar)), size(blas::H(ar)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 28.+3.*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x2) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a = { + {1. + 2. * I}, + {9. - 1. * I}, + {1. + 1. * I} + }; + multi::array const b = { + {11. - 2. * I, 5. + 2. * I}, + { 7. - 3. * I, 2. + 1. * I}, + { 8. - 1. * I, 1. + 1. * I} + }; + { + multi::array c({1, 2}); + blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 28.+3.*I ); + } + { + multi::array c({1, 2}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][1] == 28.+3.*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x2_3x2) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a = { + {1. + 2. * I, 5. + 2. * I}, + {9. - 1. * I, 9. + 1. * I}, + {1. + 1. * I, 2. + 2. * I} + }; + multi::array const b = { + {11. - 2. * I, 5. + 2. * I}, + { 7. - 3. * I, 2. + 1. * I}, + { 8. - 1. * I, 1. + 1. * I} + }; + { + multi::array c({2, 2}); + blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 125.-84.*I ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 125.-84.*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x2_3x1) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a = { + {1. + 2. * I, 5. + 2. * I}, + {9. - 1. * I, 9. + 1. * I}, + {1. + 1. * I, 2. + 2. * I} + }; + multi::array const b = { + {11. - 2. * I}, + {7. - 3. * I}, + {8. - 1. * I} + }; + { + multi::array c({2, 1}); + blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 125.-84.*I ); + } + { + multi::array c({2, 1}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 125.-84.*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1_bis) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a = { + {1. + 2. * I}, + {9. - 1. * I}, + {1. + 1. * I} + }; + multi::array const b = { + {11. - 2. * I}, + {7. - 3. * I}, + {8. - 1. * I} + }; + { + multi::array c({1, 1}); + blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 80. - 53.*I ); + } + { + multi::array c({1, 1}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[0][0] == 80. - 53.*I ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_square_automatic) { + multi::array const a = { + {1., 3.}, + {9., 7.}, + }; + multi::array const b = { + {11., 12.}, + { 7., 19.}, + }; + { + multi::array c({2, 2}); + blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 148 && c[1][1] == 241 ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == 148 && c[1][1] == 241 ); + } + { + multi::array c({2, 2}); + blas::gemm(1., a, blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][1] == 196. ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::T(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][1] == 169. ); + BOOST_TEST( c[1][0] == 82. ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][1] == 154. ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square_automatic) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a = { + {1. + 2. * I, 3. - 3. * I}, + {9. + 1. * I, 7. + 4. * I}, + }; + multi::array const b = { + {11. + 1. * I, 12. + 1. * I}, + { 7. + 8. * I, 19. - 2. * I}, + }; + namespace blas = multi::blas; + { + multi::array c({2, 2}); + blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == complex(115, 104) ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == complex(115, 104) ); + } + { + multi::array c({2, 2}); + blas::gemm(1., a, blas::T(b), 0., c); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( c[1][0] == complex(178, 75) ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(a), size(a), begin(blas::T(b)), 0., begin(c)); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( c[1][0] == complex(178.0, 75.0) ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square_automatic_part2) { + using complex = std::complex; + complex const I{0.0, 1.0}; + + multi::array const a = { + {1.0 + 2.0 * I, 3.0 - 3.0 * I}, + {9.0 + 1.0 * I, 7.0 + 4.0 * I}, + }; + multi::array const b = { + {11.0 + 1.0 * I, 12.0 + 1.0 * I}, + { 7.0 + 8.0 * I, 19.0 - 2.0 * I}, + }; + namespace blas = multi::blas; + { + multi::array c({2, 2}); + blas::gemm(1., blas::T(a), b, 0., c); // c=a⸆b, c⸆=b⸆a + BOOST_TEST(( c[1][1] == complex(180, 29) && c[1][0] == complex(53, 54) )); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(b), 0., begin(c)); // c=a⸆b, c⸆=b⸆a + BOOST_TEST(( c[1][1] == complex(180, 29) && c[1][0] == complex(53, 54) )); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][1] == complex(186.0, 65.0) ); + BOOST_TEST( c[1][0] == complex(116, 25) ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST(( c[1][1] == complex(186, 65) && c[1][0] == complex(116.0, 25.0) )); + } + { + multi::array c({2, 2}); + blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == complex(115, 104) ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][0] == complex(115, 104) ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::H(a), b, 0., c); // c=a†b, c†=b†a + BOOST_TEST( c[1][0] == complex(111, 64) && c[1][1] == complex(158.0, -51.0) ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square_automatic_part3) { + using complex = std::complex; + complex const I{0.0, 1.0}; + + multi::array const a = { + {1.0 + 2.0 * I, 3.0 - 3.0 * I}, + {9.0 + 1.0 * I, 7.0 + 4.0 * I}, + }; + multi::array const b = { + {11.0 + 1.0 * I, 12.0 + 1.0 * I}, + { 7.0 + 8.0 * I, 19.0 - 2.0 * I}, + }; + namespace blas = multi::blas; + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=a†b, c†=b†a + BOOST_TEST( c[1][0] == complex(111, 64) && c[1][1] == complex(158, -51) ); + } + { + multi::array c({2, 2}); + blas::gemm(1., a, blas::H(b), 0.0, c); // c=ab†, c†=ba† + BOOST_TEST( c[1][0] == complex(188, 43) && c[1][1] == complex(196, 25) ); + auto c2 = +blas::gemm(1., a, blas::H(b)); + BOOST_TEST( c2 == c ); + } + { + multi::array c({2, 2}); + blas::gemm(0.1, a, blas::H(b), 0.0, c); // c=ab†, c†=ba† + auto const c2 = +blas::gemm(0.1, a, blas::H(b)); + BOOST_TEST( c2 == c ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::H(a), blas::H(b), 0.0, c); // c=a†b†, c†=ba + BOOST_TEST( c[1][0] == complex(116, -25) && c[1][1] == complex(186, -65) ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0., begin(c)); // c=a†b†, c†=ba + BOOST_TEST( c[1][0] == complex(116, -25) && c[1][1] == complex(186, -65) ); + } + { + multi::array c({2, 2}); + blas::gemm(1., blas::T(a), blas::H(b), 0.0, c); // c=a⸆b†, c†=ba⸆† + BOOST_TEST( c[1][0] == complex(118, 5) && c[1][1] == complex(122, 45) ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::H(b)), 0., begin(c)); // c=a⸆b†, c†=ba⸆† + BOOST_TEST( c[1][0] == complex(118, 5) && c[1][1] == complex(122, 45) ); + } + { + multi::array c({2, 2}); + blas::gemm(1.0, blas::T(a), blas::T(b), 0.0, c); // c=a⸆b⸆, c⸆=ba + BOOST_TEST( c[1][0] == complex(116, 25) && c[1][1] == complex(186, 65) ); + } + { + multi::array c({2, 2}); + blas::gemm_n(1.0, begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0., begin(c)); // c=a⸆b⸆, c⸆=ba + BOOST_TEST( c[1][0] == complex(116, 25) && c[1][1] == complex(186, 65) ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_nonsquare_automatic) { + using complex = std::complex; + complex const I{0.0, 1.0}; + + multi::array const a = { + {1.0 + 2.0 * I, 3.0 - 3.0 * I, 1.0 - 9.0 * I}, + {9.0 + 1.0 * I, 7.0 + 4.0 * I, 1.0 - 8.0 * I}, + }; + multi::array const b = { + {11.0 + 1.0 * I, 12.0 + 1.0 * I, 4.0 + 1.0 * I, 8.0 - 2.0 * I}, + { 7.0 + 8.0 * I, 19.0 - 2.0 * I, 2.0 + 1.0 * I, 7.0 + 1.0 * I}, + { 5.0 + 1.0 * I, 3.0 - 1.0 * I, 3.0 + 8.0 * I, 1.0 + 1.0 * I} + }; + { + multi::array c({2, 4}); + blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == complex(112, 12) ); + } + { + multi::array c({2, 4}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == complex(112, 12) ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_realcomplex_complex_nonsquare_automatic) { + using complex = std::complex; + + complex const I{0.0, 1.0}; + + multi::array const a = { + {1.0, 3.0, 1.0}, + {9.0, 7.0, 1.0}, + }; + multi::array const b = { + {11. + 1. * I, 12. + 1. * I, 4. + 1. * I, 8. - 2. * I}, + { 7. + 8. * I, 19. - 2. * I, 2. + 1. * I, 7. + 1. * I}, + { 5. + 1. * I, 3. - 1. * I, 3. + 8. * I, 1. + 1. * I} + }; + { + multi::array c = blas::gemm(1., a, b); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == complex(53, 24) ); + } + { + multi::array c({2, 4}); + c = blas::gemm(1., a, b); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == complex(53, 24) ); + } + { + multi::array c({2, 4}); + blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == complex(53, 24) ); + } + { + multi::array c({2, 4}); + blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == complex(53, 24) ); + } + { + multi::array const a_real = { + {1., 3., 1.}, + {9., 7., 1.}, + }; + multi::array c({2, 4}); + blas::real_doubled(c) = blas::gemm(1., a_real, blas::real_doubled(b)); + + BOOST_TEST( c[1][2] == complex(53, 24) ); + } + } + + BOOST_AUTO_TEST_CASE(submatrix_result_issue_97) { + using complex = std::complex; + + constexpr complex I{0.0, 1.0}; + + multi::array M = { + {2.0 + 3.0 * I, 2.0 + 1.0 * I, 1.0 + 2.0 * I}, + {4.0 + 2.0 * I, 2.0 + 4.0 * I, 3.0 + 1.0 * I}, + {7.0 + 1.0 * I, 1.0 + 5.0 * I, 0.0 + 3.0 * I}, + }; + auto M2 = +M({0, 3}, {0, 1}); + BOOST_TEST( M2 == M({0, 3}, {0, 1}) ); + } + + BOOST_AUTO_TEST_CASE(blas_context_gemm) { + using complex = std::complex; + + static constexpr complex I{0, 1}; + + auto rand = [d = std::normal_distribution<>{}, g = std::mt19937{}]() mutable { // NOLINT(cert-msc32-c, cert-msc51-cpp) for test purposes + return d(g) + d(g) * I; + }; + + multi::array A({30, 40}); + multi::array B({40, 50}); + + std::generate(A.elements().begin(), A.elements().end(), rand); + std::generate(B.elements().begin(), B.elements().end(), rand); + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare_hermitized_second_gemm_range) { + multi::array const a({2, 3}, 0.); + multi::array const b({4, 3}, 0.); + { + multi::array c({2, 4}); + c() = blas::gemm(0.1, a, blas::H(b)); + BOOST_REQUIRE_CLOSE(c[1][2], 0., 0.00001); + } + { + multi::array c = blas::gemm(0.1, a, blas::H(b)); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( c[1][2] == 0.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_nonsquare_hermitized_second_gemm_range) { + using complex = std::complex; + + multi::array const a({2, 3}, 0.); + multi::array const b({4, 3}, 0.); + { + multi::array c({2, 4}, 999.); + blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); + BOOST_TEST( c[1][2] != 999. ); + } + { + multi::array c = blas::gemm(1., a, blas::H(b)); // c=ab⸆, c⸆=ba⸆ + BOOST_TEST( c[1][2] == 0. ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare_hermitized_second) { + namespace blas = multi::blas; + + multi::array const a = { + {1, 3, 1}, + {9, 7, 1}, + }; + multi::array const b = { + {11, 7, 5}, + {12, 19, 3}, + { 4, 2, 3}, + { 8, 7, 1} + }; + { + multi::array c({2, 4}); + blas::gemm(1., a, blas::H(b), 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 53. ); + } + { + multi::array c({2, 4}); + blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 53. ); + } + { + multi::array c({2, 4}); + blas::gemm(0.1, a, blas::H(b), 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); + } + { + multi::array c({2, 4}); + blas::gemm_n(0.1, begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); + } + { + multi::array c({2, 4}); + c() = blas::gemm(0.1, a, blas::H(b)); + } + { + multi::array c = blas::gemm(0.1, a, blas::H(b)); // c=ab⸆, c⸆=ba⸆ + BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_real_nonsquare_hermitized_second) { + namespace blas = multi::blas; + using complex = std::complex; + + multi::array const a = { + {1., 3., 1.}, + {9., 7., 1.}, + }; + multi::array const b = { + {11., 7., 5.}, + {12., 19., 3.}, + { 4., 2., 3.}, + { 8., 7., 1.} + }; + { + multi::array c({2, 4}); + blas::gemm(1., a, blas::H(b), 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_TEST( c[1][2] == 53. ); + } + { + multi::array c({2, 4}); + blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE_CLOSE(c[1][2], 53.0, 1E-6); + } + { + multi::array c({2, 4}); + blas::gemm(0.1, a, blas::H(b), 0., c); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE_CLOSE(real(c[1][2]), 5.3, 0.00001); + } + { + multi::array c({2, 4}); + blas::gemm_n(0.1, begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ + BOOST_REQUIRE_CLOSE(real(c[1][2]), 5.3, 0.00001); + } + { + multi::array c({2, 4}); + c() = blas::gemm(0.1, a, blas::H(b)); + } + { + multi::array c = blas::gemm(0.1, a, blas::H(b)); // c=ab⸆, c⸆=ba⸆ + BOOST_REQUIRE_CLOSE(real(c[1][2]), 5.3, 0.00001); + } + } + + BOOST_AUTO_TEST_CASE(blas_gemm_1xn_complex) { + using complex = std::complex; + multi::array const a({1, 100}, 1.); + multi::array const b({1, 100}, 1.); + + multi::array c({1, 1}, 999.); + blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); + BOOST_TEST( c[0][0] == 100. ); + } + + BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_complex_inq_hydrogen_case) { + using complex = std::complex; + complex const I{0, 1}; + + multi::array const a({3, 1}, 2. + 1. * I); + multi::array const b({1, 1}, 3. + 4. * I); + + multi::array c({3, 1}, 999.); + blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); + BOOST_TEST_REQUIRE( c[0][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); + BOOST_TEST_REQUIRE( c[1][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); + BOOST_TEST_REQUIRE( c[2][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); + } + + BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_complex_inq_hydrogen_case_no_n_interface) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a({3, 1}, 2. + 1. * I); + multi::array const b({10, 1}, 3. + 4. * I); + + multi::array c({3, 10}, 999.); + blas::gemm(1., a, blas::H(b), 0., c); + BOOST_TEST_REQUIRE( c[0][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); + BOOST_TEST_REQUIRE( c[1][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); + BOOST_TEST_REQUIRE( c[0][1] == (2. + 1.*I)*std::conj(3. + 4.*I) ); + } + + BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_1x1_complex_inq_hydrogen_case_complex_value_hermitized) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a({1, 1}, 2. + 1. * I); + multi::array const b({1, 1}, 3. + 4. * I); + + multi::array c({1, 1}, 999.); + c = blas::gemm(1., a, blas::H(b)); + BOOST_TEST( c[0][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); + } + + BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_1x1_complex_inq_hydrogen_case_complex_value) { + using complex = std::complex; + complex const I{0, 1}; + multi::array const a({1, 1}, 2. + 1. * I); + multi::array const b({1, 1}, 3. + 4. * I); + + multi::array c({1, 1}, 999.); + c = blas::gemm(1., a, b); + BOOST_TEST( c[0][0] == (2. + 1.*I)*(3. + 4.*I) ); + } + + BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_1x1_complex_inq_hydrogen_case) { + using complex = std::complex; + multi::array const a({1, 1}, 2.); + multi::array const b({1, 1}, 3.); + + multi::array c({1, 1}, 999.); + c = blas::gemm(1., a, b); + BOOST_TEST( c[0][0] == 6. ); + } + + BOOST_AUTO_TEST_CASE(blas_gemm_inq_case) { // https://gitlab.com/correaa/boost-multi/-/issues/97 + using complex = std::complex; + complex const I{0, 1}; + + multi::array mat({10, 2}, 1.0 + 3. * I); + multi::array vec({10, 1}, -2.0 + 4. * I); + + mat({0, 10}, {1, 2}) = vec; + + namespace blas = multi::blas; + + { + auto olap1 = +blas::gemm(1., blas::H(mat), vec); + auto olap2 = +blas::gemm(1., blas::H(mat({0, 10}, {0, 1})), vec); + + BOOST_TEST( blas::H(mat)[1].size() == (~vec)[0].size() ); + BOOST_TEST( blas::dot(blas::H(mat)[0], (~vec)[0]) == olap1[0][0] ); + BOOST_TEST( std::inner_product(blas::H(mat)[0].begin(), blas::H(mat)[0].end(), (~vec)[0].begin(), complex{0}) == olap1[0][0] ); + + multi::array mat2 = mat({0, 10}, {0, 1}); + auto olap3 = +blas::gemm(1., blas::H(mat2), vec); + + BOOST_TEST(olap1[0][0] == olap2[0][0]); + BOOST_TEST(olap3[0][0] == olap2[0][0]); + } + { + multi::array mat2 = mat({0, 3}, {0, 1}); + auto olap3 = +blas::gemm(1., blas::H(mat({0, 3}, {0, 1})), vec); + BOOST_TEST( (+blas::gemm(1., blas::H(mat2), vec))[0][0] == (+blas::gemm(1., blas::H(mat({0, 3}, {0, 1})), vec))[0][0] ); + } + } + + BOOST_AUTO_TEST_CASE(blas_issue_109_part2) { + multi::array const A({3, 4}, 5.); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({2, 3}, 7.); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({2, 4}, 999.0); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm(1.0, ~A, ~B, 0.0, ~C); + + BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); + BOOST_TEST_REQUIRE( C[0][1] == 105.0 ); + BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); + } + + BOOST_AUTO_TEST_CASE(blas_issue_109) { + multi::array const A({3, 4}, 5.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({2, 3}, 7.0); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({4, 2}, 999.0); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm(1., ~A, ~B, 0., C); + + BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); + BOOST_TEST_REQUIRE( C[0][1] == 105.0 ); + BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); + } + + BOOST_AUTO_TEST_CASE(blas_issue_109_part2_complex) { + multi::array, 2> const A({3, 4}, {5.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + multi::array, 2> const B({2, 3}, {7.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + multi::array, 2> C({2, 4}, {999.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm({1.0, 0.0}, ~A, ~B, {0.0, 0.0}, ~C); + + BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); + BOOST_TEST_REQUIRE( C[0][1] == 105.0 ); + BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); + } + + BOOST_AUTO_TEST_CASE(blas_issue_109_complex) { + multi::array, 2> const A({3, 4}, {5.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + multi::array, 2> const B({2, 3}, {7.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + multi::array, 2> C({4, 2}, {999.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm({1.0, 0.0}, ~A, ~B, {0.0, 0.0}, C); + + BOOST_TEST( C[0][0] == 105.0 ); + BOOST_TEST( C[0][1] == 105.0 ); + BOOST_TEST( C[1][0] == 105.0 ); + } +#endif + + BOOST_AUTO_TEST_CASE(blas_issue_109_complex_mx2) { + multi::array, 2> const A({3, 4}, {5.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + multi::array, 2> const B({2, 3}, {7.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + multi::array, 2> C({4, 2}, {999.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm({1.0, 0.0}, ~A, ~B, {0.0, 0.0}, C); + + BOOST_TEST( C[0][0] == 105.0 ); + BOOST_TEST( C[1][0] == 105.0 ); + } + + BOOST_AUTO_TEST_CASE(blas_issue_109_complex_mx1) { + multi::array, 2> const A({3, 4}, {5.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + multi::array, 2> const B({1, 3}, {7.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + + multi::array, 2> C({4, 1}, {999.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm(std::complex{1.0, 0.0}, ~A, ~B, std::complex{0.0, 0.0}, C); + + BOOST_TEST( C[0][0] == 105.0 ); + BOOST_TEST( C[1][0] == 105.0 ); + } + + BOOST_AUTO_TEST_CASE(blas_issue_109_double_mx1) { + multi::array const A({3, 4}, 5.0); // NOLINT(readability-identifier-length) BLAS naming + multi::array const B({1, 3}, 7.0); // NOLINT(readability-identifier-length) BLAS naming + + multi::array C({4, 1}, 999.0); // NOLINT(readability-identifier-length) BLAS naming + blas::gemm(1.0, ~A, ~B, 0.0, C); + + BOOST_TEST( C[0][0] == 105.0 ); + BOOST_TEST( C[1][0] == 105.0 ); + } + + return boost::report_errors(); +} // NOLINT(readability/fn_size) diff --git a/include/boost/multi/adaptors/blas/test/gemv.cpp b/include/boost/multi/adaptors/blas/test/gemv.cpp new file mode 100644 index 000000000..9a7d8e88f --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/gemv.cpp @@ -0,0 +1,537 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for operator- +#include // for gemv, context, dot, nrm2 +#include // for dot, dot_ref +#include // for gemv_range, gemv, oper... +#include // for operator^ +#include // for array, layout_t, array... + +#include + +#include // for generate, transform +#include // for abs // IWYU pragma: keep +#include // for complex, operator* +// #include // IWYU pragma: keep +// IWYU pragma: no_include // for abs +#include // for char_traits, basic_ost... +#include // for size, begin +// IWYU pragma: no_include // for allocator +#include // for inner_product +#include // for normal_distribution +#include // for is_same_v +#include // for move, forward +// IWYU pragma: no_include // for abs + +namespace multi = boost::multi; +namespace blas = multi::blas; + +// using fp_types = boost::mpl::list; // old versions of Boost.Test need MPL Type lists explicitly + +namespace { +template +auto MV(M const& a, VI const& x, VO&& y) -> VO&& { // NOLINT(readability-identifier-naming,readability-identifier-length) BLAS naming + std::transform( + a.begin(), a.end(), y.begin(), + [&x](auto const& row) { return std::inner_product(row.begin(), row.end(), x.begin(), typename VI::value_type{}); } + ); + return std::forward(y); +} +} // end unnamed namespace + +namespace { +void gemv_broadcast() { + // NOLINTNEXTLINE(readability-identifier-length) + multi::array const a = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0} + }; + + { + multi::array const ones({3}, 1.0); + + BOOST_TEST( ones.stride() == 1 ); + + BOOST_TEST( ones[0] == 1.0 ); + BOOST_TEST( ones[1] == 1.0 ); + BOOST_TEST( ones[2] == 1.0 ); + + multi::array sum_by_rows({2}, 0.0); + blas::gemv_n(1.0, a.begin(), 2, ones.begin(), 0.0, sum_by_rows.begin()); + + std::cout << sum_by_rows[0] << " " << sum_by_rows[1] << "\n"; + BOOST_TEST( std::abs( sum_by_rows[0] - (1.0 + 2.0 + 3.0)) < 1.0e-8 ); + BOOST_TEST( std::abs( sum_by_rows[1] - (4.0 + 5.0 + 6.0)) < 1.0e-8 ); + } + // BLAS GEMV doesn't work with stride zero + // { + // multi::array const one(1.0); + // auto const& ones = one.broadcasted(); + + // BOOST_TEST( ones.stride() == 0 ); + + // BOOST_TEST( ones[0] == 1.0 ); + // BOOST_TEST( ones[1] == 1.0 ); + // BOOST_TEST( ones[2] == 1.0 ); + + // multi::array sum_by_rows({2}, 0.0); + // blas::gemv_n(1.0, a.begin(), 2, ones.begin(), 0.0, sum_by_rows.begin()); + + // std::cout << sum_by_rows[0] << " " << sum_by_rows[1] << "\n"; + // BOOST_TEST( std::abs( sum_by_rows[0] - (1.0 + 2.0 + 3.0)) < 1.0e-8 ); + // BOOST_TEST( std::abs( sum_by_rows[1] - (4.0 + 5.0 + 6.0)) < 1.0e-8 ); + // } +} + +} // end unnamed namespace + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_double) + { + using T = double; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + { 9.0, 24.0, 30.0, 9.0}, + { 4.0, 10.0, 12.0, 7.0}, + {14.0, 16.0, 36.0, 1.0}, + }; + multi::array const x = {1.1, 2.1, 3.1, 4.1}; // NOLINT(readability-identifier-length) BLAS naming + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming + blas::gemv_n(1.0, a.begin(), a.size(), x.begin(), 0.0, y.begin()); + BOOST_TEST( std::abs( y[1] - 91.3 ) < 0.0001); + if(!std::is_same_v) { // workaround Apple Accelerate BLAS bug in dot + BOOST_TEST( std::abs( y[2] - +blas::dot(a[2], x)) < 0.0001); + } + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming + multi::array const aT{~a}; + blas::gemv_n(1.0, (~aT).begin(), (~aT).size(), x.begin(), 0.0, y.begin()); + BOOST_TEST( std::abs( y[1] - 91.3 ) < 0.0001); + + if(!std::is_same_v) { // workaround Apple Accelerate BLAS bug in dot + BOOST_TEST( std::abs( y[2] - +blas::dot(a[2], x)) < 0.0001); + } + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming + auto mv = blas::gemv(1.0, a, x); + copy_n(mv.begin(), mv.size(), y.begin()); + BOOST_TEST( std::abs( y[1] - 91.3) < 0.00001); + + multi::array w2(multi::extensions_t<1>{multi::iextension{size(a)}}); + MV(a, x, w2); + BOOST_TEST( std::abs(w2[0] - y[0]) < 0.00001); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming + y = blas::gemv(1.0, a, x); + BOOST_TEST( std::abs(y[1] - 91.3) < 0.00001); + } + { + multi::array y = blas::gemv(1.0, a, x); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( std::abs(y[1] - 91.3) < 0.00001); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}, 0.); // NOLINT(readability-identifier-length) BLAS naming + y += blas::gemv(1.0, a, x); + BOOST_TEST( std::abs(y[1] - 91.3) < 0.00001); + } + { + multi::array y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) BLAS naming + blas::gemv(1.1, a, x, 1.0, y); // y = a*M*x + b*y + BOOST_TEST( std::abs(y[1] - 105.43) < 0.00001); + } + } + + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_float) + { + using T = float; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + { 9.0, 24.0, 30.0, 9.0}, + { 4.0, 10.0, 12.0, 7.0}, + {14.0, 16.0, 36.0, 1.0}, + }; + multi::array const x = {1.1F, 2.1F, 3.1F, 4.1F}; // NOLINT(readability-identifier-length) BLAS naming + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming + blas::gemv_n(1.0, a.begin(), a.size(), x.begin(), 0.0, y.begin()); + BOOST_TEST( std::abs( y[1] - 91.3F ) < 0.0001F ); + if(!std::is_same_v) { // workaround Apple Accelerate BLAS bug in dot + BOOST_TEST( std::abs( y[2] - +blas::dot(a[2], x)) < 0.0001F ); + } + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming + multi::array const aT{~a}; + blas::gemv_n(1.0, (~aT).begin(), (~aT).size(), x.begin(), 0.0, y.begin()); + BOOST_TEST( std::abs( y[1] - 91.3F ) < 0.0001F ); + + if(!std::is_same_v) { // workaround Apple Accelerate BLAS bug in dot + BOOST_TEST( std::abs( y[2] - +blas::dot(a[2], x)) < 0.0001F ); + } + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming + auto mv = blas::gemv(1.0, a, x); + copy_n(mv.begin(), mv.size(), y.begin()); + BOOST_TEST( std::abs( y[1] - 91.3F) < 0.00001F); + + multi::array w2(multi::extensions_t<1>{multi::iextension{size(a)}}); + MV(a, x, w2); + BOOST_TEST_LT(std::abs(w2[0] - y[0]), 0.0001); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming + y = blas::gemv(1.0, a, x); + BOOST_TEST( std::abs(y[1] - 91.3F) < 0.00001F ); + } + { + multi::array y = blas::gemv(1.0, a, x); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( std::abs(y[1] - 91.3F) < 0.00001F); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}, 0.); // NOLINT(readability-identifier-length) BLAS naming + y += blas::gemv(1.F, a, x); + BOOST_TEST( std::abs(y[1] - 91.3F) < 0.00001F ); + } + { + multi::array y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) BLAS naming + blas::gemv(1.1F, a, x, 1.0F, y); // y = a*M*x + b*y + BOOST_TEST( std::abs( y[1] - 105.43F) < 0.00001F ); + } + } + + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_real_double) + { + using T = double; + namespace blas = multi::blas; + + using std::abs; // NOLINT(misc-include-cleaner) bug in clang-tidy 21.1.2 + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + { 9.0, 24.0, 30.0, 9.0}, + { 4.0, 10.0, 12.0, 7.0}, + {14.0, 16.0, 36.0, 1.0}, + }; + multi::array const x = {1.1, 2.1, 3.1, 4.1}; // NOLINT(readability-identifier-length) BLAS naming + { + multi::array y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) BLAS naming + T const alpha = 1.1; + T const beta = 1.2; + blas::gemv(alpha, a, x, beta, y); // y = a*M*x + b*y + + multi::array const y3 = {214.02, 106.43, 188.37}; + BOOST_TEST( std::abs(y[1] - y3[1]) < 2e-14 ); + } + if constexpr(!std::is_same_v) { + auto Y = +blas::gemv(1.0, a, x); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( std::abs( Y[0] - +blas::dot(a[0], x)) < 0.00001 ); + BOOST_TEST( std::abs( Y[1] - +blas::dot(a[1], x)) < 0.00001); + BOOST_TEST( std::abs( Y[2] - +blas::dot(a[2], x)) < 0.00001); + } + { + multi::array const x_shadow = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const dot = blas::gemv(1.0, multi::array({x_shadow}), y); + if(!std::is_same_v) { // workaround Apple Accelerate BLAS bug in dot + BOOST_TEST( std::abs( dot[0] - blas::dot(x_shadow, y) ) < 1e-10 ); + } + } + { + using blas::operators::operator%; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + using blas::operators::operator-; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + using blas::operators::operator^; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + BOOST_TEST((((~+~a) % x - a % x) ^ 2) < 1e-9); + } + } + + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_real_float) + { + using T = float; + namespace blas = multi::blas; + + using std::abs; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + { 9.0, 24.0, 30.0, 9.0}, + { 4.0, 10.0, 12.0, 7.0}, + {14.0, 16.0, 36.0, 1.0}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const x = {static_cast(1.1), static_cast(2.1), static_cast(3.1), static_cast(4.1)}; + { + multi::array y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) BLAS naming + auto const alpha = static_cast(1.1); + auto const beta = static_cast(1.2); + blas::gemv(alpha, a, x, beta, y); // y = a*M*x + b*y + + multi::array const y3 = {static_cast(214.02), static_cast(106.43F), static_cast(188.37)}; + BOOST_TEST( std::abs(y[1] - y3[1]) < 2e-14F ); + } + if constexpr(!std::is_same_v) { + auto Y = +blas::gemv(1.0, a, x); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( std::abs( Y[0] - +blas::dot(a[0], x)) < 0.0001F ); + BOOST_TEST( std::abs( Y[1] - +blas::dot(a[1], x)) < 0.0001F ); + BOOST_TEST( std::abs( Y[2] - +blas::dot(a[2], x)) < 0.0001F ); + } + { + multi::array const x_shadow = {1.0, 2.0, 3.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array const dot = blas::gemv(1.0, multi::array({x_shadow}), y); + if(!std::is_same_v) { // workaround Apple Accelerate BLAS bug in dot + BOOST_TEST( std::abs( dot[0] - +blas::dot(x_shadow, y) ) < 1e-10F ); + } + } + { + using blas::operators::operator%; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + using blas::operators::operator-; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + using blas::operators::operator^; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + BOOST_TEST((((~+~a) % x - a % x) ^ 2) < 1e-9); + } + } + + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_real_complex_double) + { + using T = double; + namespace blas = multi::blas; + using complex = std::complex; + using std::abs; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const M = { + { {9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0}}, + { {4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0}}, + {{14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0}}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const X = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0}, + {4.1, 0.0}, + }; + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array Y = { + {4.0, 0.0}, + {5.0, 0.0}, + {6.0, 0.0}, + }; + + auto const alpha = T{1.1}; + auto const beta = T{1.2}; + + blas::gemv(alpha, M, X, beta, Y); // y = a*M*x + b*y + + multi::array const Y3 = { + {214.02, 0.0}, + {106.43, 0.0}, + {188.37, 0.0}, + }; + + using blas::operators::operator-; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + T const n2{blas::nrm2(Y - Y3)}; + BOOST_TEST(n2 < T{1.0e-4}); + } + } + + // BOOST_AUTO_TEST_CASE(blas_gemv_complex_float_mimic_cdot) + { + multi::array, 2> const v1 = { + {std::complex{1.0F, 2.0F}, std::complex{3.0F, 4.0F}, std::complex{5.0F, 6.0F}} + }; + BOOST_TEST( v1.size() == 1 ); + BOOST_TEST( v1.num_elements() == 3 ); + + multi::array, 1> const v2 = { + std::complex{ 7.0F, 8.0F}, + std::complex{ 9.0F, 10.0F}, + std::complex{11.0F, 12.0F} + }; + BOOST_TEST( v2.size() == 3 ); + + multi::array, 1> res({1}, std::complex{}); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( res.size() == 1 ); + + blas::gemv(1.F, v1, v2, 0.F, res); + + BOOST_TEST( std::abs(res[0] - (v1[0][0]*v2[0] + v1[0][1]*v2[1] + v1[0][2]*v2[2])) < 1e-8F ); + + std::complex res_dot; // NOLINT(fuchsia-default-arguments-calls) + + blas::dot(v1[0], v2, res_dot); + + BOOST_TEST( std::abs(res[0] - res_dot) < 1e-8F ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_real_complex_float) + { + using T = float; + namespace blas = multi::blas; + using complex = std::complex; + using std::abs; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const M = { + { {9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0}}, + { {4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0}}, + {{14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0}}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const X = { + {1.1F, 0.0F}, + {2.1F, 0.0F}, + {3.1F, 0.0F}, + {4.1F, 0.0F}, + }; + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array Y = { + {4.0, 0.0}, + {5.0, 0.0}, + {6.0, 0.0}, + }; + + auto const alpha = 1.1F; + auto const beta = 1.2F; + + blas::gemv(alpha, M, X, beta, Y); // y = a*M*x + b*y + + multi::array const Y3 = { + {214.02F, 0.0F}, + {106.43F, 0.0F}, + {188.37F, 0.0F}, + }; + + using blas::operators::operator-; // cppcheck-suppress constStatement ; bug in cppcheck 2.18 + T const n2{blas::nrm2(Y - Y3)}; + BOOST_TEST( std::abs(n2) < 1e-4F ); + } + } + + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_complex) + { + namespace blas = multi::blas; + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + using std::abs; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + {2.0 + 3.0 * I, 2.0 + 1.0 * I, 1.0 + 2.0 * I}, + {4.0 + 2.0 * I, 2.0 + 4.0 * I, 3.0 + 1.0 * I}, + {7.0 + 1.0 * I, 1.0 + 5.0 * I, 0.0 + 3.0 * I}, + }; + multi::array const x = {1.0 + 2.0 * I, 2.0 + 1.0 * I, 9.0 + 2.0 * I}; // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST(( +blas::gemv(1., a, x) == multi::array{4.0 + 31.*I, 25.0 + 35.0*I, -4.0 + 53.0*I} )); + + auto aT = +~a; + BOOST_TEST(( +blas::gemv(1., ~aT, x) == multi::array{4.0 + 31.0*I, 25.0 + 35.0*I, -4.0 + 53.0*I} )); + + BOOST_TEST( +blas::gemv(1., ~a, x) == (multi::array{63.0 + 38.0*I, -1.0 + 62.0*I, -4.0 + 36.0*I}) ); + BOOST_TEST( +blas::gemv(1., ~a, x) == + blas::gemv(1.0, aT, x) ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_temporary) + { + using complex = std::complex; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {{1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + {{0.0, 0.0}, {1.0, 0.0}, {0.0, 0.0}}, + {{0.0, 0.0}, {0.0, 0.0}, {1.0, 0.0}}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + auto const B = [](auto array) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp) test purposes + auto rand = [gauss = std::normal_distribution<>{}, gen = std::mt19937{}]() mutable { + return complex{gauss(gen), gauss(gen)}; + }; + std::generate(array.elements().begin(), array.elements().end(), rand); + return array; + }(multi::array({3, 3})); + + // using blas::operators::operator*; + // using blas::operators::operator-; + // using blas::operators::operator^; + // BOOST_TEST( (((+(A*B))[0] - B[0])^2) == 0.0 ); + // BOOST_TEST( (((+(A*B))[1] - B[1])^2) == 0.0 ); + // BOOST_TEST( (((+(A*B))[2] - B[2])^2) == 0.0 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_context) + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + { 9.0, 24.0, 30.0, 9.0}, + { 4.0, 10.0, 12.0, 7.0}, + {14.0, 16.0, 36.0, 1.0}, + }; + multi::array const x = {1.1, 2.1, 3.1, 4.1}; // NOLINT(readability-identifier-length) conventional name in BLAS + + blas::context ctxt; + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS + blas::gemv_n(&ctxt, 1.0, begin(a), size(a), begin(x), 0.0, begin(y)); + BOOST_TEST( std::abs( y[1] - 91.3) < 0.0001); + BOOST_TEST( std::abs( y[2] - +blas::dot(a[2], x)) < 0.0001); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS + multi::array const aT{~a}; + blas::gemv_n(&ctxt, 1.0, begin(~aT), size(~aT), begin(x), 0.0, begin(y)); + BOOST_TEST( std::abs( y[1] - 91.3) < 0.00001); + BOOST_TEST( std::abs( y[2] - +blas::dot(a[2], x)) < 0.00001); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS + auto&& mv = blas::gemv(&ctxt, 1.0, a, x); + copy_n(mv.begin(), mv.size(), y.begin()); + BOOST_TEST( std::abs( y[1] - 91.3) < 0.00001 ); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS + y = blas::gemv(&ctxt, 1.0, a, x); + BOOST_TEST( std::abs( y[1] - 91.3) < 0.00001 ); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS + y = blas::gemv(1.0, a, x); + BOOST_TEST( std::abs( y[1] - 91.3) < 0.00001 ); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS + y() = blas::gemv(1.0, a, x); + BOOST_TEST( std::abs( y[1] - 91.3) < 0.00001 ); + } + { + multi::array y = blas::gemv(&ctxt, 1.0, a, x); // NOLINT(readability-identifier-length) conventional name in BLAS + BOOST_TEST( std::abs( y[1] - 91.3) < 0.00001 ); + } + { + multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}, 0.0); // NOLINT(readability-identifier-length) conventional name in BLAS + y += blas::gemv(&ctxt, 1.0, a, x); + BOOST_TEST( std::abs( y[1] - 91.3) < 0.00001 ); + } + { + multi::array y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) conventional name in BLAS + y += blas::gemv(&ctxt, 1.1, a, x); + BOOST_TEST( std::abs( y[1] - 105.43) < 0.00001 ); + } + } + + gemv_broadcast(); + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/blas/test/herk.cpp b/include/boost/multi/adaptors/blas/test/herk.cpp new file mode 100644 index 000000000..bdb0c90d5 --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/herk.cpp @@ -0,0 +1,343 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include // for filling +#include // for gemm, gemm_range +#include // for herk +#include // for nrm2_ref, nrm2 +#include // for involuted, under... +#include // for H, T, (anonymous) +// IWYU pragma: no_include "boost/multi/adaptors/blas/traits.hpp" // for blas // needed for iwyu-clang-macos + +#include // for array, layout_t + +#include // for sqrt +// IWYU pragma: no_include +#include // for operator*, opera... +// #include // for operator<<, basi... +#include // for size +#include // for numeric_limits +#include // for char_traits, bas... +#include // for is_same +// IWYU pragma: no_include // for forward + +namespace multi = boost::multi; + +// namespace { +// // NOLINTNEXTLINE(fuchsia-default-arguments-declarations,fuchsia-default-arguments-calls) +// template auto print(M const& mat, std::string const& msg = "") -> decltype(auto) { +// using multi::size; +// using std::cout; +// cout << msg << "\n" +// << '{'; +// for(int i = 0; i != size(mat); ++i) { +// cout << '{'; +// for(auto j : mat[i].extension()) { // NOLINT(altera-unroll-loops) +// cout << mat[i][j]; +// if(j + 1 != size(mat[i])) { +// cout << ", "; +// } +// } +// cout << '}' << '\n'; +// if(i + 1 != size(mat)) { +// cout << ", "; +// } +// } +// return cout << '}' << '\n'; +// } +// } // end unnamed namespace + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_blas_herk) + { + namespace blas = multi::blas; + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS + multi::array const a = { + {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, + {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, + }; + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional name in BLAS + blas::herk(a, c); + BOOST_TEST( c[1][0] == complex(50.0, -49.0) ); + BOOST_TEST( c[0][1] == complex(50.0, +49.0) ); + + multi::array const c_copy = blas::herk(1.0, a); + BOOST_TEST( c == c_copy ); + + BOOST_TEST( +blas::gemm(1.0, a, blas::H(a)) == blas::herk(a) ); + } + } + + // BOOST_AUTO_TEST_CASE(inq_case) + { + namespace blas = multi::blas; + // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS + multi::array const a = { + {0.0, 1.0, 2.0}, + {3.0, 4.0, 5.0}, + {6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0}, + }; + + BOOST_TEST( (+blas::gemm(1.0, a, blas::T(a)))[1][2] == 86.0 ); + { + multi::array c({4, 4}); // NOLINT(readability-identifier-length) conventional name in BLAS + blas::herk(1.0, a, c); + BOOST_TEST( std::abs( c[1][2] - (+blas::gemm(1.0, a, blas::T(a)))[1][2] ) < 1e-10 ); + // BOOST_TEST( c[2][1] == (+blas::gemm(1., a, blas::T(a)))[2][1] ); + } + { + multi::array const c = blas::herk(1.0, a); // NOLINT(readability-identifier-length) conventional name in BLAS + BOOST_TEST( c == +blas::gemm(1., a, blas::T(a)) ); + BOOST_TEST( blas::herk(a) == +blas::gemm(1.0, a, blas::T(a)) ); + BOOST_TEST( blas::herk(2.0, a) == +blas::gemm(2.0, a, blas::T(a)) ); + } + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk_real) + { + namespace blas = multi::blas; + // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS + multi::array const a = { + {1.0, 3.0, 4.0}, + {9.0, 7.0, 1.0}, + }; + { + multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) BLAS naming + blas::herk(1.0, a, c); + BOOST_TEST( c[0][1] == 34.0 ); + } + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_case) + { + namespace blas = multi::blas; + // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS + multi::array const a = { + {1.0, 2.0, 3.0}, + }; + multi::array b = blas::herk(a); // NOLINT(readability-identifier-length) BLAS naming + + BOOST_TEST( size(b) == 1 ); + BOOST_TEST( std::abs( b[0][0] - (1.0*1.0 + 2.0*2.0 + 3.0*3.0)) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_case_scale) + { + namespace blas = multi::blas; + // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS + multi::array const a = { + {1.0, 2.0, 3.0}, + }; + + multi::array b = blas::herk(0.1, a); // NOLINT(readability-identifier-length) BLAS naming + + BOOST_TEST( size(b) == 1 ); + BOOST_TEST( std::abs( b[0][0] - (((1.0*1.0) + (2.0*2.0) + (3.0*3.0))*0.1) ) < 1E-6 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_real_case) + { + namespace blas = multi::blas; + + using complex = std::complex; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}}, + }; + multi::array b = blas::herk(1.0, a); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( size(b) == 1 ); + BOOST_TEST( b[0][0] == (1.0*1.0) + (2.0*2.0) + (3.0*3.0) ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_real_case_scale) + { + namespace blas = multi::blas; + + using complex = std::complex; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const a = { + {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}}, + }; + multi::array b = blas::herk(0.1, a); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( size(b) == 1 ); + BOOST_TEST( std::abs( real( b[0][0]/0.1 ) - (1.0*1.0 + 2.0*2.0 + 3.0*3.0) ) < 1E-6 ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_case) + { + namespace blas = multi::blas; + + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS + multi::array const a = { + {1.0 + 2.0 * I, 2.0 + 3.0 * I, 3.0 + 4.0 * I}, + }; + multi::array b = blas::herk(a); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( size(b) == 1 ); + BOOST_TEST( b[0][0] == std::norm(1.0 + 2.0*I) + std::norm(2.0 + 3.0*I) + std::norm(3.0 + 4.0*I) ); + + BOOST_TEST( std::sqrt(real(blas::herk(a)[0][0])) == blas::nrm2(a[0]) ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_case_hermitized_out_param) + { + namespace blas = multi::blas; + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array const a = {{1.0 + 2.0 * I}, {2.0 + 3.0 * I}, {3.0 + 4.0 * I}}; // NOLINT(readability-identifier-length) BLAS naming + multi::array b({1, 1}); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( size(b) == 1 ); + + blas::herk(blas::filling::upper, 1.0, blas::H(a), 0.0, b); + + BOOST_TEST( b[0][0] == std::norm(1.0 + 2.0*I) + std::norm(2.0 + 3.0*I) + std::norm(3.0 + 4.0*I) ); + + // BOOST_TEST( std::sqrt(real(b[0][0])) == blas::nrm2(blas::T(a)[0])() ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_case_hermitized) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) conventional name in BLAS + + // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS + multi::array const a = { + {1.0 + 2.0 * I}, + {2.0 + 3.0 * I}, + {3.0 + 4.0 * I}, + }; + + namespace blas = multi::blas; + + multi::array b = blas::herk(blas::H(a)); // NOLINT(readability-identifier-length) BLAS naming + + BOOST_TEST( size(b) == 1 ); + BOOST_TEST( b[0][0] == std::norm(1.0 + 2.0*I) + std::norm(2.0 + 3.0*I) + std::norm(3.0 + 4.0*I) ); + + BOOST_TEST( std::sqrt(real(blas::herk(blas::H(a))[0][0])) == blas::nrm2(a.rotated()[0]) ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_case_hermitized_auto) + { + namespace blas = multi::blas; + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array const arr = { + {1.0 + 2.0 * I}, + {2.0 + 3.0 * I}, + {3.0 + 4.0 * I}, + }; + auto arr2 = blas::herk(1.0, blas::hermitized(arr)); + static_assert(std::is_same>{}); + BOOST_TEST( size(arr2) == 1 ); + BOOST_TEST( arr2[0][0] == std::norm(1.0 + 2.0*I) + std::norm(2.0 + 3.0*I) + std::norm(3.0 + 4.0*I) ); + + BOOST_TEST( std::sqrt(real(blas::herk(blas::H(arr))[0][0])) == blas::nrm2(arr.rotated()[0]) ); + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk_complex_identity) + { + namespace blas = multi::blas; + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array const arr = { + {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, + {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, + }; + + { + multi::array arr2({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional one-letter operation BLAS + blas::herk(blas::filling::lower, 1.0, arr, 0.0, arr2); // c^dagger = c = a a^dagger = (a a^dagger)^dagger, `c` is lower triangular + BOOST_TEST(( arr2[1][0] == complex{50.0, -49.0} )); + BOOST_TEST( arr2[0][1] == 9999.0 ); + } + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional one-letter operation BLAS + static_assert(blas::is_conjugated::value); + + blas::herk(blas::filling::lower, 1.0, arr, 0.0, blas::H(c)); // c^dagger = c = a a^dagger = (aa^dagger)^daggerr, `c` in upper triangular + + BOOST_TEST( std::abs( static_cast(blas::H(c)[1][0]) - complex{50.0, -49.0} ) < 1e-10 ); + BOOST_TEST( std::abs( static_cast(blas::H(c)[0][1]) - complex{9999.0, 0.0} ) < 1e-10 ); + } + { + // NOLINTNEXTLINE(readability-identifier-length) : conventional one-letter operation BLASs + multi::array c({3, 3}, {9999.0, 0.0}); + herk(blas::filling::lower, 1.0, blas::T(arr), 0.0, blas::T(c)); // c†=c=aT(aT)† not supported + BOOST_TEST( std::abs( c.transposed()[1][0] - complex{52.0, -90.0} ) < 1e-10 ); + BOOST_TEST( std::abs( c.transposed()[0][1] - 9999.0 ) < 1e-10 ); + } + { + // NOLINTNEXTLINE(readability-identifier-length) : conventional one-letter operation BLASs + multi::array c({3, 3}, {9999.0, 0.0}); + blas::herk(blas::filling::lower, 1.0, blas::T(arr), 0.0, blas::H(blas::T(c))); // c†=c=aT(aT)† not supported + BOOST_TEST(( blas::H(blas::T(c))[1][0] == complex{52.0, -90.0} )); + BOOST_TEST( blas::H(blas::T(c))[0][1] == 9999.0 ); + } + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLAS + blas::herk(blas::filling::upper, 1.0, arr, 0.0, c); // c†=c=aa†=(aa†)†, `c` in upper triangular + BOOST_TEST(( c[0][1] == complex{50.0, +49.0} )); + BOOST_TEST( c[1][0] == 9999.0 ); + } + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLAS + blas::herk_nm(blas::filling::upper, 1.0, arr.home(), arr.size(), (~arr).size(), 0.0, c.home()); + BOOST_TEST(( c[0][1] == complex{50.0, +49.0} )); + BOOST_TEST( c[1][0] == 9999.0 ); + } + // { + // multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLAS + // c() = blas::herk(blas::filling::upper, 1.0, arr); + // BOOST_TEST(( c[0][1] == complex{50.0, +49.0} )); + // // BOOST_TEST( c[1][0] == 9999.0 ); + // } + { + multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLAS + blas::herk(1.0, arr, c); // c†=c=aa†=(aa†)† + BOOST_TEST(( c[0][1] == complex{50.0, +49.0} )); + BOOST_TEST(( c[1][0] == complex{50.0, -49.0} )); + } + { + multi::array c({3, 3}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLAS + blas::herk(blas::filling::lower, 1.0, blas::H(arr), 0.0, c); // c†=c=aa†=(aa†)†, `c` in lower triangular + BOOST_TEST(( c[1][0] == complex{52.0, 90.0} )); + BOOST_TEST( c[0][1] == 9999.0 ); + } + } + + // BOOST_AUTO_TEST_CASE(multi_blas_herk_complex_square) + { + namespace blas = multi::blas; + + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + auto const nan = std::numeric_limits::quiet_NaN(); + + // NOLINTNEXTLINE(readability-identifier-length) lapack conventional name + multi::array const A = { + {12.9388 + I * 0.0, 9.80028 + I * -0.00011091, 9.66966 + I * -0.0114817}, + { nan + I * nan, 8.44604 + I * 0.0, 3.78646 + I * 0.0170734}, + { nan + I * nan, nan + I * nan, 7.70655 + I * 0.0}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) lapack conventional name + multi::array C({3, 3}, complex{0.0, 0.0}); + + blas::herk(boost::multi::blas::filling::upper, complex{1.0, 0.0}, A, complex{0.0, 0.0}, C); + } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/blas/test/iamax.cpp b/include/boost/multi/adaptors/blas/test/iamax.cpp new file mode 100644 index 000000000..05854e106 --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/iamax.cpp @@ -0,0 +1,71 @@ +// Copyright 2019-2024 Alfredo A. Correa + +// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS/cuBLAS iamax" + +// #include + +#include "../../blas/iamax.hpp" + +#include "../../../adaptors/blas/cuda.hpp" +#include "../../../adaptors/cuda.hpp" +#include "../../../array.hpp" + +#include + +using std::cout; +namespace multi = boost::multi; +namespace blas = multi::blas; + +using complex = std::complex; +constexpr complex I{0.0, 1.0}; + +BOOST_AUTO_TEST_CASE(multi_adaptors_blas_iamax) { + multi::array const A = { + {1.0 + 2. * I, 2.0, 3.0, 4.0}, + { 5.0, 6.0 + 3.0 * I, 7.0, 8.0}, + { 9.0, 10.0, 11.0 + 4. * I, 12.0}, + }; + + using blas::iamax; + + auto chess = [](auto const& a, auto const& b) { + using std::abs; + return abs(real(a)) + abs(imag(a)) < abs(real(b)) + abs(imag(b)); + }; + + BOOST_REQUIRE(iamax(A[1])==std::max_element(begin(A[1]), end(A[1]), chess)-begin(A[1])); + BOOST_REQUIRE(A[1][iamax(A[1])]==*std::max_element(begin(A[1]), end(A[1]), chess)); +} + +BOOST_AUTO_TEST_CASE(multi_adaptors_blas_iamax_cuda) { + multi::cuda::array const A = { + {1.0 + 2.0 * I, 2.0, 3.0, 4.0}, + { 5.0, 6.0 + 3.0 * I, 7.0, 8.0}, + { 9.0, 10.0, 11.0 + 4.0 * I, 12.0}, + }; + using blas::iamax; + BOOST_REQUIRE(iamax(A[1]) == 1); +} + +BOOST_AUTO_TEST_CASE(multi_adaptors_blas_iamax_real) { + multi::array const A = {1.0, 2.0, 3.0, 4.0}; + + auto i = blas::iamax(A); + + BOOST_REQUIRE( i == 3 ); + BOOST_REQUIRE( A[blas::iamax(A)] == 4.0 ); + + BOOST_REQUIRE( *blas::amax(A) == 4.0 ); +} + +using complex = std::complex; + +BOOST_AUTO_TEST_CASE(multi_adaptors_blas_iamax_complex) { + multi::array const A = {1.0, 2.0, 3.0, 4.0}; + + auto i = blas::iamax(A); + + BOOST_REQUIRE( i == 3 ); + BOOST_REQUIRE( A[blas::iamax(A)] == 4.0 ); + BOOST_REQUIRE( *blas::amax(A) == 4.0 ); +} diff --git a/include/boost/multi/adaptors/blas/test/nrm2.cpp b/include/boost/multi/adaptors/blas/test/nrm2.cpp new file mode 100644 index 000000000..4bd3b200f --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/nrm2.cpp @@ -0,0 +1,192 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +// IWYU pragma: no_include "boost/multi/adaptors/blas/core.hpp" // for context +// IWYU pragma: no_include "boost/multi/adaptors/blas/traits.hpp" // for blas, multi +#include // for context +#include // for dot, dot_ref, operator== +#include // for nrm2, nrm2_ref + +#include // for array, layout_t, impli... + + +#include // for sqrt, NAN +#include +// IWYU pragma: no_include // for abs +#include +#include +// IWYU pragma: no_include // for size, begin +// IWYU pragma: no_include // for is_same_v +// IWYU pragma: no_include // for declval, forward + +namespace multi = boost::multi; + +using complex = std::complex; + +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit + + BOOST_AUTO_TEST_CASE(multi_blas_nrm2) { + namespace blas = multi::blas; + + // NOLINTNEXTLINE(readability-identifier-length) blas conventional name + multi::array const A = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0, 12.0}, + }; + BOOST_TEST( blas::nrm2(A[1]) == std::sqrt(blas::dot(A[1], A[1])) ); + + { + multi::array const x = {1.0 + 1.0 * I, 3.0 + 2.0 * I, 3.0 + 4.0 * I}; // NOLINT(readability-identifier-length) blas conventional name + + BOOST_TEST( std::abs(+blas::dot(x, x) - ((1.0 + 1.0*I)*(1.0 + 1.0*I) + (3.0 + 2.0*I)*(3.0 + 2.0*I) + (3.0 + 4.0*I)*(3.0 + 4.0*I))) < 1.0e-8 ); + + std::cout << "nrm2 "<< blas::nrm2(x) << " " << std::sqrt(norm(1.0 + 1.0*I) + norm(3.0 + 2.0*I) + norm(3.0 + 4.0*I)) << '\n'; + BOOST_TEST( std::abs( blas::nrm2(x) - std::sqrt(norm(1.0 + 1.0*I) + norm(3.0 + 2.0*I) + norm(3.0 + 4.0*I)) ) < 1.0e-8 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_real) { + namespace blas = multi::blas; + + multi::array const cA = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 10.0, 11.0, 12.0}, + }; + + double n = std::numeric_limits::quiet_NaN(); // NOLINT(readability-identifier-length) BLAS naming + blas::nrm2(cA.rotated()[1], n); + + // BOOST_TEST( blas::nrm2(rotated(cA)[1], n) == std::sqrt( 2.0*2.0 + 6.0*6.0 + 10.0*10.0) ); // TODO(correaa) nrm2 is returning a pointer? + BOOST_TEST( n == std::sqrt( (2.0*2.0) + (6.0*6.0) + (10.0*10.0) ) ); + + // BOOST_TEST( blas::nrm2(rotated(cA)[1]) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); + + // double n2 = blas::nrm2(rotated(cA)[1]); + // BOOST_TEST( n == n2 ); + + // multi::array R(4); + // blas::nrm2( rotated(cA)[1], R[2]); + // BOOST_TEST( R[2] == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); + + // multi::array R0; + // blas::nrm2( rotated(cA)[1], R0); + // BOOST_TEST( R0 == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); + + // BOOST_TEST( blas::nrm2(rotated(cA)[1]) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); + } + + // BOOST_AUTO_TEST_CASE(multi_adaptor_blas_nrm2_operators) + { + multi::array const X = {1.1, 2.1, 3.1, 4.1}; // NOLINT(readability-identifier-length) BLAS naming + + { + auto n = std::numeric_limits::quiet_NaN(); // NOLINT(readability-identifier-length) BLAS naming + + multi::blas::nrm2(X, n); + BOOST_TEST( std::abs( n - multi::blas::nrm2(X) ) < 1e-10 ); + } + { + auto n = std::numeric_limits::quiet_NaN(); // NOLINT(readability-identifier-length) BLAS naming + + n = multi::blas::nrm2(X); // cppcheck-suppress redundantAssignment ; test assignment from conversion + BOOST_TEST( std::abs( n - multi::blas::nrm2(X) ) < 1e-10 ); // cppcheck-suppress knownConditionTrueFalse ; + } + { + double const n = multi::blas::nrm2(X); // NOLINT(readability-identifier-length) BLAS naming + BOOST_TEST( std::abs( n - multi::blas::nrm2(X) ) < 1e-10 ); // cppcheck-suppress knownConditionTrueFalse ; + } + { + multi::array res{0.0}; + multi::array const xx = {1.0, 2.0, 3.0}; + + // multi::blas::dot(xx, xx, res); + // multi::blas::nrm2(xx, res); + multi::blas::context ctx; + multi::blas::dot_n(&ctx, xx.begin(), xx.size(), xx.begin(), res.base()); + // multi::blas::nrm2_n(&ctx, xx.begin(), xx.size(), res.base()); + + // BOOST_TEST( *res.base() == 1.0*1.0 + 2.0*2.0 + 3.0*3.0 ); + + // multi::blas::nrm2(xx, res); + + // BOOST_TEST( *res.base() == 1.0*1.0 + 2.0*2.0 + 3.0*3.0 ); + } + } + + // BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_complex_real_case){ + // using complex = std::complex; + // multi::array const cA = { + // {1.0, 2.0, 3.0, 4.0}, + // {5.0, 6.0, 7.0, 8.0}, + // {9.0, 10.0, 11.0, 12.0} + // }; + // } + + // using multi::blas::nrm2; + // double n; + // BOOST_TEST( nrm2(rotated(cA)[1], n) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); + // BOOST_TEST( nrm2(rotated(cA)[1]) == n ); + //} + + // #if 0 + // BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_complex_real_case_thrust){ + // using complex = thrust::complex; + // multi::array const cA = { + // {1., 2., 3., 4.}, + // {5., 6., 7., 8.}, + // {9., 10., 11., 12.} + // }; + + // using multi::blas::nrm2; + // double n; + // BOOST_TEST( nrm2(rotated(cA)[1], n) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); + // BOOST_TEST( nrm2(rotated(cA)[1]) == n ); + //} + + // BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_complex_real_case_types){ + // boost::mpl::for_each, + // thrust::complex//, + // // boost::multi::complex // TODO make this work + // >>([](auto cplx){ + // multi::array const cA = { + // {1., 2., 3., 4.}, + // {5., 6., 7., 8.}, + // {9., 10., 11., 12.} + // }; + + // using multi::blas::nrm2; + // double n; + // BOOST_TEST( nrm2(rotated(cA)[1], n) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); + // BOOST_TEST( nrm2(rotated(cA)[1]) == n ); + // }); + //} + // #endif + + // BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_complex){ + // using complex = std::complex; complex const I{0,1}; + // multi::array const cA = { + // {1., 2. + 1.*I, 3., 4.}, + // {5., 6. + 4.*I, 7., 8.}, + // {9., 10. - 3.*I, 11., 12.} + // }; + + // using multi::blas::nrm2; + // double n; + // BOOST_TEST( nrm2(rotated(cA)[1], n) == std::sqrt( norm(cA[0][1]) + norm(cA[1][1]) + norm(cA[2][1]) ) ); + // BOOST_TEST( nrm2(rotated(cA)[1]) == std::sqrt( norm(cA[0][1]) + norm(cA[1][1]) + norm(cA[2][1]) ) ); + + // using namespace multi::blas::operators; + // BOOST_TEST_REQUIRE( (rotated(cA)[1]^-1) == 1/std::sqrt(norm(cA[0][1]) + norm(cA[1][1]) + norm(cA[2][1])) , boost::test_tools::tolerance(1e-15) ); + // BOOST_TEST_REQUIRE( (rotated(cA)[1]^2) == norm(cA[0][1]) + norm(cA[1][1]) + norm(cA[2][1]) , boost::test_tools::tolerance(1e-15) ); + //} + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/blas/test/numeric.cpp b/include/boost/multi/adaptors/blas/test/numeric.cpp new file mode 100644 index 000000000..15229e59a --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/numeric.cpp @@ -0,0 +1,182 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +// IWYU pragma: no_include "boost/multi/adaptors/blas/complex_traits.hpp" // for blas // needed by iwyu-clang-macos + +#include + +#include +#include + +namespace multi = boost::multi; + +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_numeric_imag) { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + namespace blas = multi::blas; + multi::array const array = {1.0 + 2.0 * I, 3.0 + 5.0 * I, 9.0 + 2.0 * I}; + BOOST_TEST( blas::imag(array)[2] == 2.0 ); + BOOST_TEST( blas::real(array)[2] == 9.0 ); + } + + BOOST_AUTO_TEST_CASE(multi_blas_numeric_real_conjugated) { + using complex = std::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array array = { + {1.0 - 3.0 * I, 6.0 + 2.0 * I}, + {8.0 + 2.0 * I, 2.0 + 4.0 * I}, + {2.0 - 1.0 * I, 1.0 + 1.0 * I} + }; + BOOST_TEST( array[0][0] == 1.0 - 3.0*I ); + + multi::array const carray = { + {1.0 - 3.0 * I, 6.0 + 2.0 * I}, + {8.0 + 2.0 * I, 2.0 + 4.0 * I}, + {2.0 - 1.0 * I, 1.0 + 1.0 * I} + }; + BOOST_TEST( carray[0][0] == 1.0 - 3.0*I ); + + namespace blas = multi::blas; + auto conjr = blas::make_conjugater(array.data_elements()); + + decltype(blas::make_conjugater(carray.data_elements())) ppp; // = BdataC; + ppp = conjr; + + BOOST_TEST( *ppp == 1.0 + 3.0*I ); + + // static_assert( multi::blas::is_complex_array, 2>>{}, "!"); + static_assert(blas::is_complex_array{}); + static_assert(!blas::is_conjugated{}); + + auto&& conjd_array = blas::conj(array); + static_assert(blas::is_conjugated{}); + + BOOST_TEST( conjd_array[0][0] == 1.0 + 3.0*I ); + BOOST_TEST( std::abs( imag(*conjd_array.base()) - +3.0 ) < 1e-10 ); + + // BOOST_TEST_REQUIRE( base(Bconj)->imag() == +3 ); + BOOST_TEST( conjd_array[0][1] == conjd_array.rotated()[1][0] ); + BOOST_TEST( conjd_array.rotated()[1][0] == conjd_array[0][1] ); + + // BOOST_TEST( base(Bconj) == -3.0*I ); + static_assert(blas::is_complex_array{}); + + BOOST_TEST( blas::conj(conjd_array) == array ); + + BOOST_TEST( blas::conj(array)[1][0] == std::conj(array[1][0]) ); + } + + BOOST_AUTO_TEST_CASE(multi_blas_numeric_decay) { + using complex = std::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array arr = { + {1.0 - 3.0 * I, 6.0 + 2.0 * I, 9.0 + 3.0 * I}, + {8.0 + 2.0 * I, 2.0 + 4.0 * I, 9.0 + 3.0 * I}, + {2.0 - 1.0 * I, 1.0 + 1.0 * I, 9.0 + 3.0 * I}, + {9.0 + 3.0 * I, 9.0 + 3.0 * I, 9.0 + 3.0 * I} + }; + + namespace blas = multi::blas; + multi::array conj_arr{blas::conj(arr)}; + multi::array const conj_arr2{blas::conj(arr)}; + + BOOST_TEST( conj_arr[2][1] == std::conj(arr[2][1]) ); + BOOST_TEST( blas::conj(arr)[2][1] == std::conj(arr[2][1]) ); + + BOOST_TEST( blas::T(arr)[1][2] == arr[2][1] ); + BOOST_TEST( blas::T(arr) == ~arr ); + + BOOST_TEST( blas::conj(arr)[1][2] == blas::hermitized(arr)[2][1] ); + BOOST_TEST( blas::conj(blas::T(arr)) == blas::hermitized(arr) ); + + BOOST_TEST( blas::hermitized(arr)[2][1] == blas::conj(arr)[1][2] ); + BOOST_TEST( blas::hermitized(arr) == blas::conj(blas::T(arr)) ); + + BOOST_TEST( std::abs( blas::real(arr)[2][1] - std::real(arr[2][1]) ) < 1e-10 ); + BOOST_TEST( std::abs( blas::imag(arr)[2][1] - std::imag(arr[2][1]) ) < 1e-10 ); + + multi::array const B_real_doubled = { + {1.0, -3.0, 6.0, 2.0, 9.0, 3.0}, + {8.0, 2.0, 2.0, 4.0, 9.0, 3.0}, + {2.0, -1.0, 1.0, 1.0, 9.0, 3.0}, + {9.0, 3.0, 9.0, 3.0, 9.0, 3.0} + }; + BOOST_TEST( blas::real_doubled(arr).sizes() == B_real_doubled.sizes() ); + BOOST_TEST( blas::real_doubled(arr) == B_real_doubled ); + } + +#if defined(CUDA_FOUND) and CUDA_FOUND + #include + + BOOST_AUTO_TEST_CASE(multi_blas_numeric_decay_thrust) { + using complex = thrust::complex; + complex const I{0.0, 1.0}; + + multi::array B = { + {1.0 - 3.0 * I, 6.0 + 2.0 * I}, + {8.0 + 2.0 * I, 2.0 + 4.0 * I}, + {2.0 - 1.0 * I, 1.0 + 1.0 * I}, + }; + + namespace blas = multi::blas; + multi::array conjB = blas::conj(B); + BOOST_TEST( conjB[1][2] == conj(B[1][2]) ); + } +#endif + + BOOST_AUTO_TEST_CASE(multi_blas_numeric_real_imag_part) { + using complex = std::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array arr = { + {1.0, 3.0, 4.0}, + {9.0, 7.0, 1.0} + }; + multi::array complex_arr = arr; + BOOST_TEST( complex_arr[1][1] == arr[1][1] ); + + multi::array arr2 = { + {1.0 - 3.0 * I, 6.0 + 2.0 * I}, + {8.0 + 2.0 * I, 2.0 + 4.0 * I}, + {2.0 - 1.0 * I, 1.0 + 1.0 * I} + }; + + multi::array const arr2_real = { + {1.0, 6.0}, + {8.0, 2.0}, + {2.0, 1.0}, + }; + multi::array const arr2_imag = { + {-3.0, +2.0}, + {+2.0, +4.0}, + {-1.0, +1.0}, + }; + + using multi::blas::imag; + using multi::blas::real; + + BOOST_TEST( arr2_real == real(arr2) ); + BOOST_TEST( real(arr2) == arr2_real ); + BOOST_TEST( imag(arr2) == arr2_imag ); + + BOOST_TEST( arr2[1][0] == 8.0 + 2.0*I ); + BOOST_TEST( arr2[1][0].imag() == 2.0 ); + + namespace blas = multi::blas; + BOOST_TEST( blas::hermitized(arr2)[1][2] == std::conj( arr2[2][1] ) ); + + blas::hermitized(arr2)[1][2] = 20.0 + 30.0 * I; + BOOST_TEST( arr2[2][1] == 20.0 - 30.0*I ); + } + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/blas/test/operations.cpp b/include/boost/multi/adaptors/blas/test/operations.cpp new file mode 100644 index 000000000..a561aef1c --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/operations.cpp @@ -0,0 +1,48 @@ +// © Alfredo A. Correa 2019-2024 + +#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS operations and cuda" +#define BOOST_TEST_DYN_LINK + +// #include + +#include "../../blas/dot.hpp" + +#include "../../../array.hpp" +#include "../../blas/cuda.hpp" + +#include "../../../adaptors/cuda.hpp" +#include "../../../complex.hpp" + +#include +#include +#include + +using std::cout; +namespace multi = boost::multi; +namespace blas = multi::blas; + +using complex = std::complex; constexpr complex I{0.0, 1.0}; + +BOOST_AUTO_TEST_CASE(const blas_conjugated_cpu) { + multi::array const a = {5.0 + 2.0*I, 6.0 + 6.0*I, 7.0 + 2.0*I, 8.0 - 3.0*I}; + BOOST_REQUIRE( blas::C(a)[1] == conj(a[1]) ); + + namespace cuda = multi::cuda; + + cuda::array const agpu = {5.0 + 2.0*I, 6.0 + 6.0*I, 7.0 + 2.0*I, 8.0 - 3.0*I}; + BOOST_REQUIRE( blas::C(agpu)[1] == conj(agpu[1]) ); +} + +BOOST_AUTO_TEST_CASE(blas_conjugated_gpu){ +#if 0 + cuda::array const acu = {1.0 + I, 2.0 + 3.0*I, 3.0 + 2.0*I, 4.0 - 9.0*I}; + cuda::array const bcu = {5.0 + 2.0*I, 6.0 + 6.0*I, 7.0 + 2.0*I, 8.0 - 3.0*I}; + + { + cuda::array ccu; + blas::dot(acu, bcu, ccu); + BOOST_REQUIRE( ccu() == 19.0 - 27.0*I ); + } + BOOST_REQUIRE( blas::C(bcu)[1] == 2.0 - 3.0*I ); +#endif +} diff --git a/include/boost/multi/adaptors/blas/test/scal.cpp b/include/boost/multi/adaptors/blas/test/scal.cpp new file mode 100644 index 000000000..e4644e33d --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/scal.cpp @@ -0,0 +1,102 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +// IWYU pragma: no_include "boost/multi/adaptors/blas/traits.hpp" // for blas, multi + +#include + +#include +#include // for complex, operator* + +namespace multi = boost::multi; +namespace blas = multi::blas; + +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ +// #define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) +// #define BOOST_REQUIRE_SMALL(X, ToL) BOOST_TEST( std::abs( X ) < (ToL) ) + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) +// BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_n) +{ + multi::array arr = { + { 1.0, 2.0, 3.0, 4.0 }, + { 5.0, 6.0, 7.0, 8.0 }, + { 9.0, 10.0, 11.0, 12.0 }, + }; + BOOST_TEST( (arr[0][2] == 3.0) && (arr[2][2] == 11.0) ); + + blas::scal_n(2.0, arr[2].begin(), arr[2].size()); + BOOST_TEST( std::abs( arr[0][2] - 3.0 ) < 1e-10 ); + BOOST_TEST( std::abs( arr[2][2] - (11.0*2.0) ) < 1e-10 ); +} + +// BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_it) +{ + multi::array arr = { + { 1.0, 2.0, 3.0, 4.0 }, + { 5.0, 6.0, 7.0, 8.0 }, + { 9.0, 10.0, 11.0, 12.0 }, + }; + BOOST_TEST( arr[0][2] == 3.0 ); + BOOST_TEST( arr[2][2] == 11.0 ); + + blas::scal(2.0, arr[2].begin(), arr[2].end()); + BOOST_TEST( std::abs( arr[0][2] - 3.0 ) < 1e-10 ); + BOOST_TEST( std::abs( arr[2][2] - (11.0*2.0) ) < 1e-10 ); +} + +BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_real) { + multi::array arr = { + { 1.0, 2.0, 3.0, 4.0 }, + { 5.0, 6.0, 7.0, 8.0 }, + { 9.0, 10.0, 11.0, 12.0 }, + }; + BOOST_TEST( arr[0][2] == 3.0 ); + BOOST_TEST( arr[2][2] == 11.0 ); + + BOOST_TEST( blas::scal(1.0, arr[2]) == arr[2] ); + BOOST_TEST( &blas::scal(1.0, arr[2]) == &arr[2] ); + BOOST_TEST( +blas::scal(1.0, arr[2]) == arr[2] ); + + blas::scal(2.0, arr[2]); + BOOST_TEST( std::abs( arr[0][2] - 3.0 ) < 1e-10 ); + BOOST_TEST( std::abs( arr[2][2] - (11.0*2.0) ) < 1e-10 ); + + BOOST_TEST( &blas::scal(1.0, arr[2]) == &arr[2] ); +} + +BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_real_2D) { + multi::array arr = { + { 1.0, 2.0, 3.0, 4.0 }, + { 5.0, 6.0, 7.0, 8.0 }, + { 9.0, 10.0, 11.0, 12.0 }, + }; + BOOST_TEST( arr[0][2] == 3.0 ); + BOOST_TEST( arr[2][2] == 11.0 ); + + blas::scal(2.0, arr.elements()); + + BOOST_TEST( arr[0][2] == 6.0 ); + BOOST_TEST( arr[2][2] == 22.0 ); +} + +BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_complex_2D) { + auto const I = std::complex(0.0, 1.0); // NOLINT(readability-identifier-length) blas conventional name + + multi::array, 2> arr = { + { 1.0 + 0.0 * I, 2.0 + 0.0 * I, 3.0 + 0.0 * I, 4.0 + 0.0 * I }, + { 5.0 + 0.0 * I, 6.0 + 0.0 * I, 7.0 + 0.0 * I, 8.0 + 0.0 * I }, + { 9.0 + 0.0 * I, 10.0 + 0.0 * I, 11.0 + 0.0 * I, 12.0 + 0.0 * I }, + }; + BOOST_TEST( arr[0][2] == 3.0 ); + BOOST_TEST( arr[2][2] == 11.0 ); + + blas::scal(2.0, arr.elements()); + + BOOST_TEST( arr[0][2] == 6.0 ); + BOOST_TEST( arr[2][2] == 22.0 ); +} +return boost::report_errors();} diff --git a/include/multi/adaptors/blas/test/swap.cpp b/include/boost/multi/adaptors/blas/test/swap.cpp similarity index 92% rename from include/multi/adaptors/blas/test/swap.cpp rename to include/boost/multi/adaptors/blas/test/swap.cpp index d87c8c6dc..d4555c31e 100644 --- a/include/multi/adaptors/blas/test/swap.cpp +++ b/include/boost/multi/adaptors/blas/test/swap.cpp @@ -1,8 +1,7 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2024 Alfredo A. Correa #define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS swap" -#include +// #include #include "../../blas.hpp" @@ -50,7 +49,7 @@ BOOST_AUTO_TEST_CASE(lapack_potrf, *boost::unit_test::tolerance(0.00001)) { } { using complex = std::complex; - complex const I{0, 1}; + complex const I{0, 1}; multi::array A = { {1.0 + 2. * I, 2.0, 3.0, 4.0 + 3.0 * I}, { 5.0, 6.0, 7.0, 8.0}, diff --git a/include/boost/multi/adaptors/blas/test/syrk.cpp b/include/boost/multi/adaptors/blas/test/syrk.cpp new file mode 100644 index 000000000..a46be9262 --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/syrk.cpp @@ -0,0 +1,319 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include // for filling +#include // for transposed, T +#include // for syrk +// IWYU pragma: no_include "boost/multi/adaptors/blas/traits.hpp" // for blas, multi + +#include // for array, layout_t + +#include +#include // for operator*, complex +// IWYU pragma: no_include // for size +// IWYU pragma: no_include // for forward + +namespace multi = boost::multi; + +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_AUTO_TEST_CASE(multi_blas_syrk_real) { + // NOLINTNEXTLINE(readability-identifier-length) + multi::array const a = { + {1.0, 3.0, 4.0}, + {9.0, 7.0, 1.0}, + }; + { + multi::array c({3, 3}, 9999.0); // NOLINT(readability-identifier-length) + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::lower, 1.0, blas::T(a), 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular + + BOOST_TEST( c[2][1] == 19.0 ); + BOOST_TEST( c[1][2] == 9999.0 ); + } + { + multi::array c({3, 3}, 9999.0); // NOLINT(readability-identifier-length) + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::upper, 1.0, blas::T(a), 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular + + BOOST_TEST( c[1][2] == 19.0 ); + BOOST_TEST( c[2][1] == 9999.0 ); + } + { + multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) + namespace blas = multi::blas; + + using blas::filling; + using blas::syrk; + + syrk(filling::lower, 1.0, a, 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular + + BOOST_TEST( c[1][0] == 34.0 ); + BOOST_TEST( c[0][1] == 9999.0 ); + } + { + multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::upper, 1.0, a, 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, a⸆a, `c` in lower triangular + + BOOST_TEST( c[0][1] == 34.0 ); + BOOST_TEST( c[1][0] == 9999.0 ); + } + { + multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::upper, 1.0, a, 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, a⸆a, `c` in lower triangular + + BOOST_TEST( c[0][1] == 34.0 ); + BOOST_TEST( c[1][0] == 9999.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_syrk_real_special_case) { + // NOLINTNEXTLINE(readability-identifier-length) + multi::array const a = { + {1.0, 3.0, 4.0}, + }; + { + multi::array c({1, 1}, 9999.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + using blas::filling; + + syrk(filling::lower, 1.0, a, 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular + + BOOST_TEST( std::abs( c[0][0] - (1.0*1.0 + 3.0*3.0 + 4.0*4.0) ) < 1e-10 ); + } + { + multi::array c({1, 1}, 9999.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + using blas::filling; + + syrk(filling::upper, 1.0, a, 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular + + BOOST_TEST( std::abs( c[0][0] - (1.0*1.0 + 3.0*3.0 + 4.0*4.0) ) < 1e-10 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_syrk_complex_real_case) { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) + multi::array const a = { + {1.0 + I * 0.0, 3.0 + I * 0.0, 4.0 + I * 0.0}, + {9.0 + I * 0.0, 7.0 + I * 0.0, 1.0 + I * 0.0}, + }; + { + multi::array c({3, 3}, 9999.0 + I * 0.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::lower, 1.0, blas::T(a), 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( real(c[2][1]) == 19.0 ); + BOOST_TEST( real(c[1][2]) == 9999.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_syrk_complex) { + using complex = std::complex; + + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) + + // NOLINTNEXTLINE(readability-identifier-length) + multi::array const a = { + {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, + {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, + }; + { + multi::array c({3, 3}, 9999.0 + I * 0.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + syrk(blas::filling::lower, 1.0, blas::T(a), 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( std::abs( real(c[2][1]) - - 3.0 ) < 1e-10 ); + BOOST_TEST( std::abs( imag(c[2][1]) - -34.0 ) < 1e-10 ); + } + { + multi::array c({2, 2}, 9999.0 + I * 0.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + syrk(blas::filling::lower, 1.0, a, 0.0, c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in lower triangular // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( c[1][0] == complex(18.0, -21.0) ); + BOOST_TEST( c[0][1] == 9999.0 ); + } + { + multi::array c({2, 2}, 9999.0 + I * 0.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + syrk(blas::filling::upper, 1.0, a, 0.0, c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in upper triangular // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( c[0][1] == complex(18.0, -21.0) ); + BOOST_TEST( c[1][0] == 9999.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_syrk_automatic_operation_complex) { + using complex = std::complex; + + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) + + // NOLINTNEXTLINE(readability-identifier-length) + multi::array const a = { + {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, + {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, + }; + + { + multi::array c({2, 2}, 9999.0 + I * 0.0); // NOLINT(readability-identifier-length) + + using multi::blas::filling; + + syrk(filling::lower, 1.0, a, 0.0, c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in lower triangular // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( c[1][0] == complex(18.0, -21.0) ); + BOOST_TEST( c[0][1] == 9999.0 ); + } + { + multi::array c({3, 3}, 9999.0); // NOLINT(readability-identifier-length) // NOLINT(fuchsia-default-arguments-calls) + + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::lower, 1.0, blas::T(a), 0.0, c); // c⸆=c=a⸆a=(aa⸆)⸆, `c` in lower triangular // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( c[2][1] == complex(-3.0, -34.0) ); + BOOST_TEST( c[1][2] == 9999.0 ); + } + { + multi::array c({3, 3}, 9999.0); // NOLINT(readability-identifier-length) // NOLINT(fuchsia-default-arguments-calls) + + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::lower, 1.0, a.rotated(), 0.0, c); // c⸆=c=a⸆a=(aa⸆)⸆, `c` in lower triangular // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( c[2][1] == complex(-3.0, -34.0) ); + BOOST_TEST( c[1][2] == 9999.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_syrk_automatic_operation_real) { + // NOLINTNEXTLINE(readability-identifier-length) + multi::array const a = { + {1.0, 3.0, 4.0}, + {9.0, 7.0, 1.0}, + }; + { + multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) + + using multi::blas::filling; + + syrk(filling::lower, 1.0, a, 0.0, c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in lower triangular + + BOOST_TEST( c[1][0] == 34.0 ); + BOOST_TEST( c[0][1] == 9999.0 ); + } + { + multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) + + using multi::blas::filling; + + syrk(filling::upper, 1.0, a, 0.0, c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in upper triangular + + BOOST_TEST( c[0][1] == 34.0 ); + BOOST_TEST( c[1][0] == 9999.0 ); + } + { + multi::array c({3, 3}, 9999.0); // NOLINT(readability-identifier-length) + + using multi::blas::filling; + + syrk(filling::lower, 1.0, a.rotated(), 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular + + BOOST_TEST( c[2][1] == 19.0 ); + BOOST_TEST( c[1][2] == 9999.0 ); + } + { + multi::array c({3, 3}, 9999.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::lower, 1.0, blas::T(a), 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular + + BOOST_TEST( c[2][1] == 19.0 ); + BOOST_TEST( c[1][2] == 9999.0 ); + } + { + multi::array c({3, 3}, 9999.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + using blas::filling; + + syrk(filling::upper, 1.0, blas::T(a), 0.0, c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in upper triangular + + BOOST_TEST( c[1][2] == 19.0 ); + BOOST_TEST( c[2][1] == 9999.0 ); + } + { + multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) + + namespace blas = multi::blas; + + syrk(blas::filling::upper, 1.0, a, 0.0, blas::T(c)); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in upper triangular + + BOOST_TEST( c[0][1] == 9999.0 ); + BOOST_TEST( c[1][0] == 34.0 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_syrk_automatic_implicit_zero) { + // NOLINTNEXTLINE(readability-identifier-length) + multi::array const a = { + {1.0, 3.0, 4.0}, + {9.0, 7.0, 1.0}, + }; + { + multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) + + using multi::blas::filling; + + syrk(filling::lower, 1.0, a, c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in lower triangular + + BOOST_TEST( c[1][0] == 34.0 ); + BOOST_TEST( c[0][1] == 9999.0 ); + } + } + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/blas/test/traits.cpp b/include/boost/multi/adaptors/blas/test/traits.cpp new file mode 100644 index 000000000..12a2fadd1 --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/traits.cpp @@ -0,0 +1,33 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include +#include + +#include + +namespace multi = boost::multi; +namespace blas = multi::blas; + + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_adaptors_blas_traits_simple_array) + { + multi::array const arr; + BOOST_TEST( arr.empty() ); + } + + // BOOST_AUTO_TEST_CASE(multi_adaptors_blas_traits) + { + static_assert(blas::is_d{}); + static_assert(blas::is_s{}); + + static_assert(blas::is_c>{}); + static_assert(blas::is_z>{}); + } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/blas/test/trsm.cpp b/include/boost/multi/adaptors/blas/test/trsm.cpp new file mode 100644 index 000000000..f3450a802 --- /dev/null +++ b/include/boost/multi/adaptors/blas/test/trsm.cpp @@ -0,0 +1,468 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for filling +#include // for gemm, gemm_range +// IWYU pragma: no_include "boost/multi/adaptors/blas/numeric.hpp" // for underlying +#include // for T, H, (anonymous) +#include // for side +#include // for trsm, diagonal +// IWYU pragma: no_include "boost/multi/adaptors/blas/traits.hpp" // for blas + +#include // for array, subarray + +// IWYU pragma: no_include // for min +#include // for NAN, abs +#include // for operator*, opera... +#include +// IWYU pragma: no_include // for size +// IWYU pragma: no_include // for NAN, abs +// #include // for size // NOLINT(misc-include-cleaner) +// IWYU pragma: no_include // for allocator +// IWYU pragma: no_include // for remove_reference... +// IWYU pragma: no_include // for forward + +namespace multi = boost::multi; + +#include + +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ +#define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_0x0) { + namespace blas = multi::blas; + multi::array const A; // NOLINT(readability-identifier-length) BLAS naming + + { + multi::array B; // NOLINT(readability-identifier-length) BLAS naming + // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::general, 1.0, A, B); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_1x1) { + namespace blas = multi::blas; + + // clang-format off + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { {10.0, }, }; + // clang-format on + { + // clang-format off + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = {{3.0, }, }; + // clang-format on + + auto const B_cpy = B; + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::general, 1.0, A, B); + // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) + BOOST_REQUIRE_CLOSE(B[0][0], 3.0 / 10.0, 0.00001); + BOOST_REQUIRE_CLOSE((+blas::gemm(1.0, A, B))[0][0], B_cpy[0][0], 0.00001); + } + { + // clang-format off + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = {{3.0, }, }; + + auto const B_cpy = B; + // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::general, 2.0, A, B); + BOOST_REQUIRE_CLOSE(B[0][0], 2.0 * 3.0 / 10.0, 0.00001); + BOOST_REQUIRE_CLOSE((+blas::gemm(1.0, A, B))[0][0], 2. * B_cpy[0][0], 0.00001); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + { 3.0, 4.0, 5.0 }, + }; + auto const B_cpy = B; + // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::general, 1.0, A, B); + BOOST_REQUIRE_CLOSE(B[0][0], 3. / 10., 0.00001); + BOOST_REQUIRE_CLOSE(B[0][1], 4. / 10., 0.00001); + BOOST_REQUIRE_CLOSE(B[0][2], 5. / 10., 0.00001); + BOOST_REQUIRE_CLOSE((+blas::gemm(1., A, B))[0][1], B_cpy[0][1], 0.00001); + } +} + +BOOST_AUTO_TEST_CASE(multi_blas_trsm_real_square) { + namespace blas = multi::blas; + + constexpr auto nan = std::numeric_limits::quiet_NaN(); + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + { 1.0, 3.0, 4.0 }, + { nan, 7.0, 1.0 }, + { nan, nan, 8.0 } + }; + auto const A_cpy = triangular(blas::filling::upper, A); + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + { 1.0, 3.0, 4.0 }, + { 2.0, 7.0, 1.0 }, + { 3.0, 4.0, 2.0 } + }; + auto const B_cpy = B; + blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) + + BOOST_REQUIRE_CLOSE(B[1][2], 0.107143, 0.001); + BOOST_TEST( std::abs( (+blas::gemm(1., A_cpy, B))[1][2] - B_cpy[1][2] ) < 1e-10 ); + } + { + auto const AT = +~A; + auto const AT_cpy = triangular(blas::filling::lower, AT); + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + { 1.0, 3.0, 4.0 }, + { 2.0, 7.0, 1.0 }, + { 3.0, 4.0, 2.0 } + }; + auto const B_cpy = B; + blas::trsm(blas::side::left, blas::filling::upper, 1., blas::T(AT), B); + BOOST_REQUIRE_CLOSE(B[1][2], 0.107143, 0.001); + BOOST_TEST( std::abs( (+blas::gemm(1., blas::T(AT_cpy), B))[1][2] - B_cpy[1][2] ) < 1e-10 ); + } + { + auto const AT = +~A; + auto const AT_cpy = triangular(blas::filling::lower, AT); + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const B = { + { 1.0, 3.0, 4.0 }, + { 2.0, 7.0, 1.0 }, + { 3.0, 4.0, 2.0 } + }; + auto BT = +~B; + blas::trsm(blas::side::left, blas::filling::upper, 1., blas::T(AT), blas::T(BT)); + BOOST_REQUIRE_CLOSE(blas::T(BT)[1][2], 0.107143, 0.001); + BOOST_TEST( std::abs( (+blas::gemm(1.0, blas::T(AT_cpy), blas::T(BT)))[1][2] - B[1][2] ) < 1e-10 ); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const B = { + { 1.0, 3.0, 4.0 }, + { 2.0, 7.0, 1.0 }, + { 3.0, 4.0, 2.0 } + }; + auto BT = +~B; + blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, blas::T(BT)); + BOOST_REQUIRE_CLOSE((~BT)[1][2], 0.107143, 0.001); + } +} + +BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex) { + namespace blas = multi::blas; + + using complex = std::complex; + + auto const I = complex{ 0.0, 1.0 }; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + { 1.0 + 2.0 * I, 3.0 - 1.0 * I, 4.0 + 9.0 * I }, + { NAN, 7.0 + 4.0 * I, 1.0 + 8.0 * I }, + { NAN, NAN, 8.0 + 2.0 * I } + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + { 1.0 - 9.0 * I, 3.0 + 2.0 * I, 4.0 + 3.0 * I }, + { 2.0 - 2.0 * I, 7.0 - 2.0 * I, 1.0 - 1.0 * I }, + { 3.0 + 1.0 * I, 4.0 + 8.0 * I, 2.0 + 7.0 * I } + }; + + // B=alpha Inv[A†].B, B†=B†.Inv[A], Solve(A†.X=B, X), Solve(X†.A=B†, X), A is upper triangular (with implicit zeros below) + blas::trsm(blas::side::left, blas::filling::lower, 2. + 1. * I, blas::H(A), B); + BOOST_REQUIRE_CLOSE(real(B[1][2]), 2.33846, 0.0001); + BOOST_REQUIRE_CLOSE(imag(B[1][2]), -0.0923077, 0.0001); +} + +BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_rectangular) { + namespace blas = multi::blas; + using complex = std::complex; + complex const I{ 0, 1 }; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + { 1.0 + 2.0 * I, 3.0 - 1.0 * I, 4.0 + 9.0 * I }, + { NAN, 7.0 + 4.0 * I, 1.0 + 8.0 * I }, + { NAN, NAN, 8.0 + 2.0 * I } + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + { 1.0 - 9.0 * I, 3.0 + 2.0 * I }, + { 2.0 - 2.0 * I, 7.0 - 2.0 * I }, + { 3.0 + 1.0 * I, 4.0 + 8.0 * I } + }; + + // B=alpha Inv[A†].B, B†=B†.Inv[A], Solve(A†.X=B, X), Solve(X†.A=B†, X), A is upper triangular (with implicit zeros below) + blas::trsm(blas::side::left, blas::filling::lower, 2.0 + 1.0 * I, blas::H(A), B); + + BOOST_REQUIRE_CLOSE(real(B[2][0]), -4.16471, 0.0001); + BOOST_REQUIRE_CLOSE(imag(B[2][0]), 8.25882, 0.0001); +} + +BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_column) { + namespace blas = multi::blas; + using complex = std::complex; + complex const I{ 0, 1 }; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + { 1.0 + 2.0 * I, 3.0 - 1.0 * I, 4.0 + 9.0 * I }, + { NAN, 7.0 + 4.0 * I, 1.0 + 8.0 * I }, + { NAN, NAN, 8.0 + 2.0 * I } + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + { 1. - 9. * I }, + { 2. - 2. * I }, + { 3. + 1. * I } + }; + + // B=alpha Inv[A†].B, B†=B†.Inv[A], Solve(A†.X=B, X), Solve(X†.A=B†, X), A is upper triangular (with implicit zeros below) + blas::trsm(blas::side::left, blas::filling::lower, 2.0 + 1.0 * I, blas::H(A), B); + + BOOST_REQUIRE_CLOSE(real(B[2][0]), -4.16471, 0.0001); + BOOST_REQUIRE_CLOSE(imag(B[2][0]), 8.25882, 0.0001); +} + +BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_column_cpu) { + namespace blas = multi::blas; + using complex = std::complex; + complex const I{ 0, 1 }; // NOLINT(readability-identifier-length) imaginary unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + { 1.0 + 2.0 * I, 3.0 - 1.0 * I, 4.0 + 9.0 * I }, + { NAN, 7.0 + 4.0 * I, 1.0 + 8.0 * I }, + { NAN, NAN, 8.0 + 2.0 * I } + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + { 1.0 - 9.0 * I }, + { 2.0 - 2.0 * I }, + { 3.0 + 1.0 * I } + }; + + // B=alpha Inv[A†].B, B†=B†.Inv[A], Solve(A†.X=B, X), Solve(X†.A=B†, X), A is upper triangular (with implicit zeros below) + blas::trsm(blas::side::left, blas::filling::lower, 2.0 + 1.0 * I, blas::H(A), B); + BOOST_REQUIRE_CLOSE(real(B[2][0]), -4.16471, 0.0001); + BOOST_REQUIRE_CLOSE(imag(B[2][0]), 8.25882, 0.0001); +} + +BOOST_AUTO_TEST_CASE(multi_blas_trsm_hydrogen_inq_case_real) { + namespace blas = multi::blas; + + // clang-format off + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = {{2.0, }, }; + // clang-format on + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0, 2.0, 3.0}, + }; + BOOST_TEST( B.size() == 1 ); + auto const B_cpy = B; + blas::trsm(blas::side::left, blas::filling::lower, 1.0, A, B); + BOOST_TEST( std::abs( B[0][1] - (B_cpy[0][1]/A[0][0]) ) < 1e-10 ); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0}, + {2.0}, + {3.0}, + }; + auto const B_cpy = B; + blas::trsm(blas::side::left, blas::filling::lower, 1.0, A, blas::T(B)); + BOOST_TEST( std::abs( blas::T(B)[0][1] - (blas::T(B_cpy)[0][1]/A[0][0]) ) < 1e-10 ); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_trsm_hydrogen_inq_case_complex) { + namespace blas = multi::blas; + using complex = std::complex; + + // clang-format off + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = {{{ 2.0, 0.0 }, }, }; + // clang-format on + + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}}, + }; + auto const B_cpy = B; + blas::trsm(blas::side::left, blas::filling::lower, {1.0, 0.0}, A, B); + BOOST_TEST( std::abs( B[0][1] - B_cpy[0][1]/A[0][0] ) < 1e-10 ); + } + multi::array B1 = { + {{1.0, 0.0}}, + {{2.0, 0.0}}, + {{3.0, 0.0}}, + }; + multi::array B2 = { + {{1.0, 0.0}}, + {{2.0, 0.0}}, + {{3.0, 0.0}}, + }; + + blas::trsm(blas::side::left, blas::filling::lower, {1.0, 0.0}, A, blas::H(B1)); + + { + auto const B_cpy = B2; + blas::trsm(blas::side::right, blas::filling::upper, {1.0, 0.0}, blas::H(A), B2); + // BOOST_TEST( (+blas::gemm(1., A, blas::H(B)))[0][1] == blas::H(B_cpy)[0][1] ); + BOOST_TEST( (+blas::gemm(1., B2, blas::H(A)))[1][0] == B_cpy[1][0] ); + } + BOOST_TEST( B1 == B2 ); + } + + BOOST_AUTO_TEST_CASE(multi_blas_trsm_real_nonsquare) { + namespace blas = multi::blas; + + constexpr auto nan = std::numeric_limits::quiet_NaN(); + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0, 3.0, 40.0}, + {nan, 7.0, 1.0}, + {nan, nan, 8.0} + }; + auto const A_cpy = triangular(blas::filling::upper, A); + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0, 3.0, 4.0, 8.0}, + {2.0, 7.0, 1.0, 9.0}, + {3.0, 4.0, 2.0, 1.0}, + }; + auto const B_cpy = +B; + multi::array BT = +~B; + BOOST_TEST( BT == ~B ); // cppcheck-suppress knownConditionTrueFalse ; for testing + + blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) + BOOST_REQUIRE_CLOSE(B[1][2], 0.107143, 0.001); + BOOST_REQUIRE_CLOSE((+blas::gemm(1., A_cpy, B))[1][2], B_cpy[1][2], 0.001); + + auto const BT_cpy = BT; + blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, blas::T(BT)); + BOOST_REQUIRE_CLOSE(blas::T(BT)[1][2], 0.107143, 0.001); + + BOOST_REQUIRE_CLOSE((+blas::gemm(1., A_cpy, blas::T(BT)))[1][2], blas::T(BT_cpy)[1][2], 0.00001); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0, 3.0, 4.0, 8.0}, + {2.0, 7.0, 1.0, 9.0}, + {3.0, 4.0, 2.0, 1.0}, + }; + multi::array AT{~A}; + multi::array BT{~B}; + + // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) + blas::trsm(blas::side::left, blas::filling::upper, 1.0, blas::T(AT), B); + BOOST_REQUIRE_CLOSE(B[1][2], 0.107143, 0.001); + + blas::trsm(blas::side::left, blas::filling::upper, 1.0, blas::T(AT), blas::T(BT)); + BOOST_REQUIRE_CLOSE((~BT)[1][2], 0.107143, 0.001); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0}, + {2.0}, + {3.0}, + }; + auto const B_cpy = +B; + blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) + BOOST_REQUIRE_CLOSE(B[2][0], 0.375, 0.00001); + BOOST_REQUIRE_CLOSE((+blas::gemm(1., A_cpy, B))[1][0], B_cpy[1][0], 0.00001); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0}, + {2.0}, + {3.0}, + }; + auto const B_cpy = +B; + blas::trsm(blas::side::left, blas::filling::upper, 1.2, A, B); + BOOST_REQUIRE_CLOSE((+blas::gemm(1.0, A_cpy, B))[1][0], 1.2 * B_cpy[1][0], 0.00001); + BOOST_REQUIRE_CLOSE((+blas::gemm(1.0 / 1.2, A_cpy, B))[1][0], B_cpy[1][0], 0.00001); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0}, + {2.0}, + {3.0}, + }; + multi::array BT{B.rotated()}; + blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, blas::T(BT)); + BOOST_REQUIRE_CLOSE((~BT)[2][0], 0.375, 0.00001); + } + } + + BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { + namespace blas = multi::blas; + using complex = std::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + using multi::blas::filling; + using multi::blas::hermitized; + using multi::blas::trsm; + blas::trsm(blas::side::left, blas::filling::upper, {1.0, 0.0}, A, blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† + BOOST_REQUIRE_CLOSE(imag(B[1][2]), -0.147059, 0.001); + } + + BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { + namespace blas = multi::blas; + using complex = std::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + using multi::blas::trsm; + + blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(A), blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† + BOOST_REQUIRE_CLOSE(imag(B[1][2]), -0.147059, 0.001); + } + return boost::report_errors(); +} diff --git a/include/multi/adaptors/blas/test/trsv.cpp b/include/boost/multi/adaptors/blas/test/trsv.cpp similarity index 68% rename from include/multi/adaptors/blas/test/trsv.cpp rename to include/boost/multi/adaptors/blas/test/trsv.cpp index 16bfb73e9..ff449513e 100644 --- a/include/multi/adaptors/blas/test/trsv.cpp +++ b/include/boost/multi/adaptors/blas/test/trsv.cpp @@ -1,11 +1,10 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXX $0 -o $0x -lcudart -lcublas -lboost_unit_test_framework `pkg-config --libs blas`&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2019-2020 +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuBLAS trsv" #define BOOST_TEST_DYN_LINK -#include +// #include #include "../../../memory/adaptors/cuda/managed/ptr.hpp" @@ -34,14 +33,13 @@ complex const I{0, 1}; namespace multi = boost::multi; namespace blas = multi::blas; -BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_cpu, *utf::tolerance(0.00001)){ - +BOOST_AUTO_TEST_CASE(const multi_blas_trsm_complex_cpu, *utf::tolerance(0.00001)){ multi::array const A = { - { 1. + 1.*I, 3. - 2.*I, 4. + 1.*I}, - {NAN , 7. - 10.*I, 1. + 2.*I}, - {NAN , NAN , 8. + 1.*I} + { 1.0 + 1.0*I, 3.0 - 2.0*I, 4.0 + 1.0*I}, + {NAN , 7.0 - 10.0*I, 1.0 + 2.0*I}, + {NAN , NAN , 8.0 + 1.0*I}, }; - multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; + multi::array b = {1.0 + 2.0*I, 3.0 + 1.0*I, 4.0 + 5.0*I}; blas::trsv(blas::filling::upper, blas::diagonal::general, A, b); BOOST_TEST_REQUIRE( real(b[0]) == -1.37259 ); BOOST_TEST_REQUIRE( real(b[1]) == 0.2127 ); @@ -51,11 +49,11 @@ BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_cpu, *utf::tolerance(0.00001)){ BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_cuda, *utf::tolerance(0.0001)){ namespace cuda = multi::cuda; cuda::managed::array const A = { - { 1. + 1.*I, 3. - 2.*I, 4. + 1.*I}, - {NAN , 7. - 10.*I, 1. + 2.*I}, - {NAN , NAN , 8. + 1.*I} + { 1.0 + 1.0*I, 3.0 - 2.0*I, 4.0 + 1.0*I}, + {NAN , 7.0 - 10.0*I, 1.0 + 2.0*I}, + {NAN , NAN , 8.0 + 1.0*I}, }; - cuda::managed::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; + cuda::managed::array b = {1.0 + 2.0*I, 3.0 + 1.0*I, 4.0 + 5.0*I}; blas::trsv(blas::filling::upper, blas::diagonal::general, A, b); BOOST_TEST_REQUIRE( real(b[0]) == -1.37259 ); @@ -66,11 +64,11 @@ BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_cuda, *utf::tolerance(0.0001)){ BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_cuda_managed, *utf::tolerance(0.00001)){ namespace cuda = multi::cuda; cuda::managed::array const A = { - { 1. + 1.*I, 3. - 2.*I, 4. + 1.*I}, - {NAN , 7. - 10.*I, 1. + 2.*I}, - {NAN , NAN , 8. + 1.*I} + { 1.0 + 1.0*I, 3.0 - 2.0*I, 4.0 + 1.0*I}, + {NAN , 7.0 - 10.0*I, 1.0 + 2.0*I}, + {NAN , NAN , 8.0 + 1.0*I} }; - cuda::managed::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; + cuda::managed::array b = {1.0 + 2.0*I, 3.0 + 1.0*I, 4.0 + 5.0*I}; blas::trsv(blas::filling::upper, A, b); // this operation happens in GPU when #include "adaptors/blas/cuda.hpp" multi::array const b_cpu = b; @@ -82,11 +80,11 @@ BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_cuda_managed, *utf::tolerance(0.000 BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_cuda_managed, *utf::tolerance(0.00001)){ namespace cuda = multi::cuda; cuda::managed::array const A = { - { 1., 3., 4.}, - {NAN, 7., 1.}, - {NAN, NAN, 8.} + { 1.0, 3.0, 4.0}, + {NAN , 7.0, 1.0}, + {NAN , NAN , 8.0}, }; - cuda::managed::array b = {1., 3., 4.}; + cuda::managed::array b = {1.0, 3.0, 4.0}; blas::trsv(blas::filling::upper, A, b); // this operation happens in GPU when #include "adaptors/blas/cuda.hpp" multi::array const b_cpu = b; @@ -98,14 +96,13 @@ BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_cuda_managed, *utf::tolerance(0.0000 BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_cuda2, *utf::tolerance(0.00001)){ namespace blas = multi::blas; multi::cuda::array const A = { - { 1. + 1.*I, 3. - 2.*I, 4. + 1.*I}, - {NAN , 7. - 10.*I, 1. + 2.*I}, - {NAN , NAN , 8. + 1.*I} + { 1.0 + 1.0*I, 3.0 - 2.0*I, 4.0 + 1.0*I}, + {NAN , 7.0 - 10.0*I, 1.0 + 2.0*I}, + {NAN , NAN , 8.0 + 1.0*I}, }; - multi::cuda::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; + multi::cuda::array b = {1.0 + 2.0*I, 3.0 + 1.0*I, 4.0 + 5.0*I}; blas::trsv(blas::filling::upper, blas::diagonal::general, A, b); BOOST_TEST_REQUIRE( real(b[0]) == -1.37259 ); BOOST_TEST_REQUIRE( real(b[1]) == 0.2127 ); BOOST_TEST_REQUIRE( real(b[2]) == 0.569231 ); } - diff --git a/include/multi/adaptors/blas/traits.hpp b/include/boost/multi/adaptors/blas/traits.hpp similarity index 60% rename from include/multi/adaptors/blas/traits.hpp rename to include/boost/multi/adaptors/blas/traits.hpp index 588966383..011028718 100644 --- a/include/multi/adaptors/blas/traits.hpp +++ b/include/boost/multi/adaptors/blas/traits.hpp @@ -1,36 +1,37 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_BLAS_TRAITS_HPP -#define MULTI_ADAPTORS_BLAS_TRAITS_HPP -#pragma once +#ifndef BOOST_MULTI_ADAPTORS_BLAS_TRAITS_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_TRAITS_HPP -#include -#include +#include +#include // for enable_if_t, false_type, is_convertible, true... +#include // for declval // IWYU pragma: keep -namespace boost::multi::blas { +namespace boost::multi::blas { // TODO(correaa) include in blas/detail? // TODO(correaa) : create a BinaryDouble concept? - template()/std::declval()), float>{} >> + template()/std::declval()), float>{} > > auto is_s_aux(F&&) -> std::true_type ; auto is_s_aux(...) -> std::false_type; template struct is_s : decltype(is_s_aux(std::declval())) {using archetype = float;}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - template()/std::declval()), double>{}>> + template()/std::declval()), double>{}> > auto is_d_aux(D&&) -> std::true_type ; auto is_d_aux(...) -> std::false_type; template struct is_d : decltype(is_d_aux(std::declval())) {using archetype = double;}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - template) and is_s().real())>{} and is_s().imag())>{}>> + template) && is_s().real())>{} && is_s().imag())>{}>> auto is_c_aux(C&&) -> std::true_type; auto is_c_aux(...) -> std::false_type; template struct is_c : decltype(is_c_aux(std::declval())) {using archetype = std::complex;}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - template) and is_d().real())>{} and is_d().imag())>{}>> + template) && is_d().real())>{} && is_d().imag())>{}>> auto is_z_aux(Z&&) -> std::true_type ; auto is_z_aux(...) -> std::false_type; diff --git a/include/boost/multi/adaptors/blas/trsm.hpp b/include/boost/multi/adaptors/blas/trsm.hpp new file mode 100644 index 000000000..273ed21d4 --- /dev/null +++ b/include/boost/multi/adaptors/blas/trsm.hpp @@ -0,0 +1,187 @@ +// Copyright 2019-2023 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_BLAS_TRSM_HPP +#define BOOST_MULTI_ADAPTORS_BLAS_TRSM_HPP + +#include +#include +#include +#include + +namespace boost::multi::blas { + +enum class diagonal : char { + unit = 'U', + non_unit = 'N', general = non_unit +}; + +template +class triangular_part { + Array const& ref_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + + public: + explicit triangular_part(Array const& ref) : ref_{ref} {} + static constexpr auto filling() { return Fill; } + using underlying_type = Array; + auto underlying() const -> Array const& { return ref_;} +}; + +template +auto triangular_parted(Array const& arr) { + return triangular_part{arr}; +} + +template +auto lower_parted(Array const& arr) {return triangular_parted(arr);} + +template +auto upper_parted(Array const& arr) {return triangular_parted(arr);} + +template auto L(Array const& arr) { return lower_parted(arr); } // NOLINT(readability-identifier-naming) BLAS naming +template auto U(Array const& arr) { return upper_parted(arr); } // NOLINT(readability-identifier-naming) BLAS naming + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wswitch-default" +#endif + +template +auto triangular(multi::blas::filling f, Matrix const& m) { // NOLINT(readability-identifier-length) BLAS naming + auto ret =+ m; + switch(f) { // NOLINT(clang-diagnostic-switch-default) + case multi::blas::filling::upper: + { + auto const ext = extension(ret); + std::for_each(ext.begin(), ext.end(), [&ret](auto idx) { + std::fill_n(ret[idx].begin(), std::min(idx, size(~ret)), 0.0); + }); + } + break; + case multi::blas::filling::lower: + { + auto extt = extension(~ret); + std::for_each(extt.begin(), extt.end(), [&ret](auto jdx) { + std::fill_n( (~ret)[jdx].begin(), std::min(jdx, size( ret)), 0.0); + }); + } + break; + } + return ret; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +using core::trsm; + +template +auto trsm(Context&& ctxt, blas::side a_side, blas::filling a_fill, blas::diagonal a_diag, typename A2D::element_type alpha, A2D const& a, B2D&& b) // NOLINT(readability-function-cognitive-complexity,readability-identifier-length) cognitive load 115, BLAS naming +-> B2D&& { + if(a_side == blas::side::left ) {assert(size(~a) >= size( b));} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + if(a_side == blas::side::right) {assert(size( a) >= size(~b));} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + assert( stride( a) == 1 || stride(~a) == 1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + assert( stride( b) == 1 || stride(~b) == 1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + // cppcheck-suppress-begin knownConditionTrueFalse ; bug in cppcheck 2.18? + if(size(b) != 0) { + #define CTXT std::forward(ctxt) + if constexpr(! is_conjugated{} && ! is_conjugated{}) { + if (stride( a)==1 && stride( b)==1) {CTXT->trsm(static_cast( (a_side)), static_cast(-a_fill), 'N', static_cast(a_diag), size( b), size(~b), alpha , a.base() , stride(~a), b.base() , stride(~b));} + else if(stride(~a)==1 && stride(~b)==1) {CTXT->trsm(static_cast(swap(a_side)), static_cast(+a_fill), 'N', static_cast(a_diag), size(~b), size( b), alpha , a.base() , stride( a), b.base() , stride( b));} + else if(stride( a)==1 && stride(~b)==1) {CTXT->trsm(static_cast(swap(a_side)), static_cast(-a_fill), 'T', static_cast(a_diag), size(~b), size( b), alpha , a.base() , stride(~a), b.base() , stride( b));} + else if(stride(~a)==1 && stride( b)==1) {CTXT->trsm(static_cast( (a_side)), static_cast(+a_fill), 'T', static_cast(a_diag), size( b), size(~b), alpha , a.base() , stride( a), b.base() , stride(~b));} + else {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE + } else if constexpr( is_conjugated{} && ! is_conjugated{}) { + if (stride( a)==1 && stride(~b)==1) {CTXT->trsm(static_cast(swap(a_side)), static_cast(-a_fill), 'C', static_cast(a_diag), size(~b), size( b), alpha , underlying(a.base()), stride(~a), b.base() , stride( b));} + else if(stride(~a)==1 && stride( b)==1) {CTXT->trsm(static_cast( (a_side)), static_cast(+a_fill), 'C', static_cast(a_diag), size( b), size(~b), alpha , underlying(a.base()), stride( a), b.base() , stride(~b));} + else {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE + } else if constexpr(! is_conjugated{} && is_conjugated{}) { + if (stride(~a)==1 && stride( b)==1) {CTXT->trsm(static_cast( (a_side)), static_cast(+a_fill), 'C', static_cast(a_diag), size( b), size(~b), conj(alpha), a.base() , stride( a), underlying(b.base()), stride(~b));} + // else if(stride( a)==1 && stride(~b)==1) {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE + else {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE + } else if constexpr( is_conjugated{} && is_conjugated{}) { + if (stride( a)==1 && stride(~b)==1) {CTXT->trsm(static_cast(swap(a_side)), static_cast(-a_fill), 'T', static_cast(a_diag), size(~b), size( b), conj(alpha), underlying(a.base()), stride(~a), underlying(b.base()), stride( b));} + else if(stride(~a)==1 && stride( b)==1) {CTXT->trsm(static_cast( (a_side)), static_cast(+a_fill), 'T', static_cast(a_diag), size( b), size(~b), conj(alpha), underlying(a.base()), stride( a), underlying(bbase(b)), stride(~b));} + else {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE + } + #undef CTXT + } + // cppcheck-suppress-end knownConditionTrueFalse ; bug in cppcheck 2.18? + + return std::forward(b); +} + +template +auto trsm(blas::side a_side, blas::filling a_fill, blas::diagonal a_diag, typename A2D::element_type alpha, A2D const& a, B2D&& b) -> decltype(auto) { // NOLINT(readability-identifier-length) BLAS naming + if constexpr(! is_conjugated{}) {return trsm(default_context_of( a.base() ), a_side, a_fill, a_diag, alpha, a, std::forward(b));} + else {return trsm(default_context_of(underlying(a.base())), a_side, a_fill, a_diag, alpha, a, std::forward(b));} +} + +template +auto trsm(Context&& ctxt, blas::side a_side, blas::filling a_fill, typename A2D::element_type alpha, A2D const& a, B2D&& b) // NOLINT(readability-identifier-length) BLAS naming +->decltype(trsm(std::forward(ctxt), a_side, a_fill, blas::diagonal::non_unit, alpha, a, std::forward(b))) { + return trsm(std::forward(ctxt), a_side, a_fill, blas::diagonal::non_unit, alpha, a, std::forward(b)); } + +#ifdef __NVCC__ // in place of global -Xcudafe \"--diag_suppress=implicit_return_from_non_void_function\" +#pragma nv_diagnostic push +#pragma nv_diag_suppress = implicit_return_from_non_void_function +#endif + +#ifdef __NVCOMPILER +#pragma diagnostic push +#pragma diag_suppress = implicit_return_from_non_void_function +#endif +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type" +#endif + +template +auto trsm(blas::side a_side, blas::filling a_fill, typename A2D::element_type alpha, A2D const& a, B2D&& b) -> decltype(auto) { // NOLINT(readability-identifier-length) BLAS naming + if constexpr(! is_conjugated{}) {return trsm(blas::default_context_of( a.base() ), a_side, a_fill, alpha, a, std::forward(b));} + else {return trsm(blas::default_context_of(underlying(a.base())), a_side, a_fill, alpha, a, std::forward(b));} +} + +#ifdef __NVCC__ +#pragma nv_diagnostic pop +#elif defined(__NVCOMPILER) +#pragma diagnostic pop +#endif + +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +template +auto trsm(blas::side a_side, typename UTArr::underlying_type::element_type alpha, UTArr const& a, B2D&& b) // NOLINT(readability-identifier-length) BLAS naming +->decltype(trsm(a_side, a.filling(), blas::diagonal::non_unit, alpha, a.underlying(), std::forward(b))) { + return trsm(a_side, a.filling(), blas::diagonal::non_unit, alpha, a.underlying(), std::forward(b)); } + +namespace operators { + + template + auto operator/=(B2D&& b, UL const& a) // NOLINT(readability-identifier-length) BLAS naming + ->decltype(blas::trsm(blas::side::right, typename std::decay_t::element_type{1.0}, a, std::forward(b))) { + return blas::trsm(blas::side::right, typename std::decay_t::element_type{1.0}, a, std::forward(b)); } + + template + auto operator|=(B2D&& b, UL const& a) // NOLINT(readability-identifier-length) BLAS naming + ->decltype(blas::trsm(blas::side::left, typename std::decay_t::element_type{1.0}, a, std::forward(b))) { + return blas::trsm(blas::side::left, typename std::decay_t::element_type{1.0}, a, std::forward(b)); } + + using blas::U; + using blas::L; + +} // end namespace operators + +} // end namespace boost::multi::blas + +#endif diff --git a/include/boost/multi/adaptors/blas/trsv.hpp b/include/boost/multi/adaptors/blas/trsv.hpp new file mode 100644 index 000000000..9b5bf5104 --- /dev/null +++ b/include/boost/multi/adaptors/blas/trsv.hpp @@ -0,0 +1,597 @@ +#ifndef MULTI_ADAPTORS_BLAS_TRSV_HPP +#define MULTI_ADAPTORS_BLAS_TRSV_HPP +// Copyright 2019-2024 Alfredo A. Correa + +#include "../blas/core.hpp" + +#include "../blas/operations.hpp" // uplo +#include "../blas/filling.hpp" +#include "../blas/side.hpp" + +#include "../../config/NODISCARD.hpp" + +namespace boost::multi::blas { + +enum class diagonal : char {//typename std::underlying_type::type{ + unit = 'U', + non_unit = 'N', general = non_unit +}; + +using core::trsv; + +template{}, int> =0> +auto trsv_base(A&& a) {return base(a);} + +template{}, int> =0> +auto trsv_base(A&& a) {return underlying(base(a));} + +template +auto trsv(filling a_nonzero_side, diagonal a_diag, A2D const& a, X1D&& x) +->decltype(trsv(static_cast(flip(a_nonzero_side)), 'N', static_cast(a_diag), size(x), trsv_base(a), stride(rotated(a)), trsv_base(x), stride(x)), std::forward(x)) +{ +// if(is_conjugated(x)) trsv(a_nonzero_side, a_diag, conjugated(a), conjugated(std::forward(x))); + { + auto base_a = trsv_base(a); + auto base_x = trsv_base(x); + if(! is_conjugated{}) { + if (stride( a )==1) {trsv(static_cast(flip(a_nonzero_side)), 'N', static_cast(a_diag), size(x), base_a, stride(rotated(a)), base_x, stride(x));} + else if(stride(rotated(a))==1) {trsv(static_cast( a_nonzero_side ), 'T', static_cast(a_diag), size(x), base_a, stride( a ), base_x, stride(x));} + else {assert(0);} + }else{ + if (stride( a )==1) {assert(0);} // TODO(correaa) fallback to trsm? + else if(stride(rotated(a))==1) {trsv(static_cast( a_nonzero_side ), 'C', static_cast(a_diag), size(x), base_a, stride( a ), base_x, stride(x));} + else {assert(0);} + } + } + return std::forward(x); +} + +template +auto trsv(filling a_nonzero_side, A2D const& a, X1D&& x) +->decltype(trsv(a_nonzero_side, diagonal::general, a, std::forward(x))) { + return trsv(a_nonzero_side, diagonal::general, a, std::forward(x)); } + +#if 0 +template +Ret trsv(filling a_nonzero_side, diagonal a_diag, A2D const& a, X1D const& x, void* = 0){ + return trsv(a_nonzero_side, a_diag, a, Ret{x});} + +template +Ret trsv(filling a_nonzero_side, A2D const& a, X1D const& x, void* = 0){ + return trsv(a_nonzero_side, a, Ret{x});} +#endif + +} // end namespace boost::multi::blas + +//#if not __INCLUDE_LEVEL__ // _TEST_MULTI_ADAPTORS_BLAS_TRSV + +//#define BOOST_TEST_MODULE "C++ Unit Tests for Multi.BLAS trsv" +//#define BOOST_TEST_DYN_LINK +//#include + +//#include "../blas/gemm.hpp" + +//#include "../../array.hpp" + +//#include + +//namespace multi = boost::multi; + +//template decltype(auto) print_1D(M const& C){ +// using boost::multi::size; using std::cout; +// for(int i = 0; i != size(C); ++i) +// cout<< C[i] <<' '; +// cout< decltype(auto) print(M const& C){ +// using boost::multi::size; using std::cout; +// for(int i = 0; i != size(C); ++i){ +// for(int j = 0; j != size(C[i]); ++j) +// cout<< C[i][j] <<' '; +// cout< const A = { +// { 1., 3., 4.}, +// { NAN, 7., 1.}, +// { NAN, NAN, 8.} +// }; +// multi::array b = {1., 3., 4.}; +// blas::trsv(blas::filling::upper, blas::diagonal::general, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ +// BOOST_TEST( b[0] == -2.07143 ); +// BOOST_TEST( b[1] == 0.357143 ); +// BOOST_TEST( b[2] == 0.5 ); +// } +// { +// multi::array const A = { +// { 1., 3., 4.}, +// { NAN, 7., 1.}, +// { NAN, NAN, 8.} +// }; +// multi::array b = {1., 3., 4.}; +// blas::trsv(blas::filling::lower, blas::diagonal::general, blas::T(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +// BOOST_TEST( b[0] == 1. ); +// BOOST_TEST( b[1] == 0. ); +// BOOST_TEST( b[2] == 0. ); +// } +//#if 0 +// { +// multi::array b = {3., 3., 1.}; +// // trsv(filling::lower, diagonal::general, hermitized(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +// // BOOST_TEST( b[0] == 3. ); +// // BOOST_TEST( b[1] == -0.857143 ); +// // BOOST_TEST( b[2] == -1.26786 ); +// } +//#endif +//} + +//#if 0 +//using complex = std::complex; + +//BOOST_AUTO_TEST_CASE(multi_blas_trsv_complex_real_case_square, *utf::tolerance(0.00001)){ +// multi::array const A = { +// { 1., 3., 4.}, +// {NAN, 7., 1.}, +// {NAN, NAN, 8.} +// }; +// using blas::filling; +// using blas::diagonal; +// using blas::transposed; +// using blas::hermitized; +// using blas::conjugated; +// using blas::trsv; +// { +// multi::array b = {1., 3., 4.}; +// blas::trsv(filling::upper, diagonal::general, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ +// BOOST_TEST( real(b[0]) == -2.07143 ); +// BOOST_TEST( real(b[1]) == 0.357143 ); +// BOOST_TEST( real(b[2]) == 0.5 ); +// } +// { +// multi::array const b = {1., 3., 4.}; +// auto b_copy = blas::trsv(filling::upper, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ +// BOOST_TEST( real(b[0]) == 1. ); +// BOOST_TEST( real(b_copy[0]) == -2.07143 ); +// BOOST_TEST( real(b_copy[1]) == 0.357143 ); +// BOOST_TEST( real(b_copy[2]) == 0.5 ); +// } +// { +// multi::array const b = {1., 3., 4.}; +// auto b_copy = blas::trsv(filling::upper, diagonal::general, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ +// BOOST_TEST( real(b[0]) == 1. ); +// BOOST_TEST( real(b_copy[0]) == -2.07143 ); +// BOOST_TEST( real(b_copy[1]) == 0.357143 ); +// BOOST_TEST( real(b_copy[2]) == 0.5 ); +// } +// { +// multi::array b = {3., 3., 1.}; +// trsv(filling::lower, diagonal::general, transposed(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +// BOOST_TEST( real(b[0]) == 3. ); +// BOOST_TEST( real(b[1]) == -0.857143 ); +// BOOST_TEST( real(b[2]) == -1.26786 ); +// } +// { +// multi::array b = {3., 3., 1.}; +// // trsv(filling::lower, diagonal::general, hermitized(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +// // BOOST_TEST( real(b[0]) == 3. ); +// // BOOST_TEST( real(b[1]) == -0.857143 ); +// // BOOST_TEST( real(b[2]) == -1.26786 ); +// } +// { +// multi::array b = {3., 3., 1.}; +//// trsv(filling::lower, diagonal::general, hermitized(A), conjugated(b)); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +//// BOOST_TEST( real(b[0]) == 3. ); +//// BOOST_TEST( real(b[1]) == -0.857143 ); +//// BOOST_TEST( real(b[2]) == -1.26786 ); +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsv_complex_square, *utf::tolerance(0.00001)){ +// namespace blas = multi::blas; + +// multi::array const A = { +// { 1. + 1.*I, 3. - 2.*I, 4. + 1.*I}, +// {NAN , 7. - 10.*I, 1. + 2.*I}, +// {NAN , NAN , 8. + 1.*I} +// }; +// using blas::filling; +// using blas::diagonal; +// using blas::transposed; +// using blas::hermitized; +// using blas::conjugated; +// using blas::trsv; +// { +// multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; +// blas::trsv(filling::upper, diagonal::general, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ +// BOOST_TEST( real(b[0]) == -1.37259 ); +// BOOST_TEST( real(b[1]) == 0.2127 ); +// BOOST_TEST( real(b[2]) == 0.569231 ); +// } +// { +// multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; +// trsv(filling::lower, diagonal::general, transposed(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +// BOOST_TEST( real(b[0]) == 1.5 ); BOOST_TEST( imag(b[0]) == 0.5 ); +// BOOST_TEST( real(b[1]) == -0.285235 ); BOOST_TEST( imag(b[1]) == -0.0503356 ); +// BOOST_TEST( real(b[2]) == -0.129272 ); BOOST_TEST( imag(b[2]) == 0.28126 ); +// } +// { +// multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; +// trsv(filling::upper, diagonal::general, blas::H(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +// print_1D(b); +// BOOST_TEST( real(b[0]) == -0.661693 ); BOOST_TEST( imag(b[0]) == -1.13934 ); +// BOOST_TEST( real(b[1]) == 0.135261 ); BOOST_TEST( imag(b[1]) == -0.0283944 ); +// BOOST_TEST( real(b[2]) == 0.415385 ); BOOST_TEST( imag(b[2]) == 0.676923 ); +// } +// { +// multi::array b = {1. - 2.*I, 3. - 1.*I, 4. - 5.*I}; +// trsv(filling::upper, diagonal::general, blas::H(A), blas::conj(b)); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +//// print_1D(b); +//// BOOST_TEST( real(conjugated(b)[0]) == -0.661693 ); BOOST_TEST( imag(conjugated(b)[0]) == -1.13934 ); +//// BOOST_TEST( real(conjugated(b)[1]) == 0.135261 ); BOOST_TEST( imag(conjugated(b)[1]) == -0.0283944 ); +//// BOOST_TEST( real(conjugated(b)[2]) == 0.415385 ); BOOST_TEST( imag(conjugated(b)[2]) == 0.676923 ); +// } +// { +// multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; +// // trsv(filling::lower, diagonal::general, hermitized(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ +// // BOOST_TEST( real(b[0]) == -0.5 ); BOOST_TEST( imag(b[0]) == 1.5 ); +// // BOOST_TEST( real(b[1]) == 0.184564 ); BOOST_TEST( imag(b[1]) == -0.620805 ); +// // BOOST_TEST( real(b[2]) == 0.691791 ); BOOST_TEST( imag(b[2]) == 0.0227155 ); +// } +//} + + +//#if 0 +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_1x1, *utf::tolerance(0.00001)){ +// multi::array const A = { +// {10.,}, +// }; +// using multi::blas::side; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array B = { +// {3.,}, +// }; +// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) +// BOOST_TEST( B[0][0] == 3./10. ); +// } +// { +// multi::array B = { +// {3.,}, +// }; +// trsm(filling::upper, diagonal::general, 2., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) +// BOOST_TEST( B[0][0] == 2.*3./10. ); +// } +// { +// multi::array B = { +// {3., 4., 5.}, +// }; +// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) +// BOOST_TEST( B[0][1] == 4./10. ); +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_0x0, *utf::tolerance(0.00001)){ +// multi::array const A; +// using multi::blas::side; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array B; +// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_real_nonsquare, *utf::tolerance(0.00001)){ +// multi::array const A = { +// { 1., 3., 4.}, +// { 0., 7., 1.}, +// { 0., 0., 8.} +// }; +// using multi::blas::side; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array B = { +// {1., 3., 4., 8.}, +// {2., 7., 1., 9.}, +// {3., 4., 2., 1.}, +// }; +// multi::array BT = rotated(B); +// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) +// BOOST_TEST( B[1][2] == 0.107143 ); + +// trsm(filling::upper, diagonal::general, 1., A, rotated(BT)); +// BOOST_TEST( rotated(BT)[1][2] == 0.107143 ); +// } +// { +// multi::array B = { +// {1., 3., 4., 8.}, +// {2., 7., 1., 9.}, +// {3., 4., 2., 1.}, +// }; +// multi::array AT = rotated(A); +// multi::array BT = rotated(B); +// trsm(filling::upper, diagonal::general, 1., rotated(AT), B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) +// BOOST_TEST( B[1][2] == 0.107143 ); + +// trsm(filling::upper, diagonal::general, 1., rotated(AT), rotated(BT)); +// print(rotated(BT)); +// BOOST_TEST( rotated(BT)[1][2] == 0.107143 ); +// } +// { +// multi::array B = { +// {1.}, +// {2.}, +// {3.}, +// }; +// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) +// BOOST_TEST( B[2][0] == 0.375 ); +// } +// { +// multi::array B = { +// {1.}, +// {2.}, +// {3.}, +// }; +// multi::array BT = rotated(B); +// trsm(filling::upper, diagonal::general, 1., A, rotated(BT)); +// BOOST_TEST( rotated(BT)[2][0] == 0.375 ); +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_real_nonsquare_default_diagonal_gemm_check, *utf::tolerance(0.00001)){ +// multi::array const A = { +// { 1., 3., 4.}, +// { 0., 7., 1.}, +// { 0., 0., 8.} +// }; +// using multi::blas::side; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array const B = { +// {1.},// 3., 4.}, +// {2.},// 7., 1.}, +// {3.},// 4., 2.}, +// }; +// using multi::blas::gemm; +// { +// auto S = trsm(filling::upper, diagonal::general, 1., A, B); +// BOOST_REQUIRE( S[2][0] == 0.375 ); +// auto Bck=gemm(1., A, S); +// BOOST_REQUIRE( Bck[2][0] == 3. ); +// for(int i{};i<3;++i)for(int j{};j const BT = rotated(B); +// auto Bck=gemm(1., A, trsm(filling::upper, diagonal::general, 1., A, rotated(BT))); +// for(int i{};i<3;++i)for(int j{};j const A = { +// { 4.}, +// }; +// using multi::blas::side; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array const B = { +// {5.}, +// }; +// { +// auto S = trsm(filling::upper, diagonal::general, 3., A, B); +// BOOST_REQUIRE( S[0][0] == 3.*5./4. ); +// } +// { +// auto S = trsm(filling::upper, 1., A, B); +// BOOST_REQUIRE( S[0][0] == 1.*5./4. ); +// } +// { +// auto S = trsm(filling::upper, A, B); +// BOOST_REQUIRE( S[0][0] == 1.*5./4. ); +// } +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_1x1_check, *utf::tolerance(0.00001)){ +// using complex = std::complex; complex const I = complex{0, 1}; +// multi::array const A = { +// { 4. + 2.*I}, +// }; +// using multi::blas::side; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array const B = { +// {5. + 1.*I}, +// }; +// using multi::blas::gemm; +// { +// auto S = trsm(filling::upper, diagonal::general, 3.+5.*I, A, B); +// BOOST_TEST( real(S[0][0]) == real((3.+5.*I)*B[0][0]/A[0][0]) ); +// BOOST_TEST( imag(S[0][0]) == imag((3.+5.*I)*B[0][0]/A[0][0]) ); +// } +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_one_check, *utf::tolerance(0.00001)){ +// using complex = std::complex; complex const I{0, 1}; +// multi::array const A = { +// { 1. + 4.*I, 3., 4.- 10.*I}, +// { 0., 7.- 3.*I, 1.}, +// { 0., 0., 8.- 2.*I} +// }; +// using multi::blas::side; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array const B = { +// {1. + 1.*I}, +// {2. + 1.*I}, +// {3. + 1.*I}, +// }; +// using multi::blas::gemm; +// { +// auto S = trsm(filling::upper, diagonal::general, 1., A, B); +// BOOST_TEST( real(S[2][0]) == 0.323529 ); +// } +// { +// auto const BT = +rotated(B); +// auto S = trsm(filling::upper, diagonal::general, 1., A, rotated(BT)); +// BOOST_TEST( real(S[2][0]) == 0.323529 ); +// } +// { +// auto const AT = +rotated(A); +// auto S = trsm(filling::upper, diagonal::general, 1., rotated(AT), B); +// BOOST_TEST( real(S[2][0]) == 0.323529 ); +// } +// { +// auto const AT = +rotated(A); +// auto const BT = +rotated(B); +// auto S = trsm(filling::upper, diagonal::general, 1., rotated(AT), rotated(BT)); +// BOOST_TEST( real(S[2][0]) == 0.323529 ); +// } +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_gemm_check, *utf::tolerance(0.00001)){ +// using complex = std::complex; complex const I{0, 1}; +// multi::array const A = { +// { 1. + 4.*I, 3., 4.- 10.*I}, +// { 0., 7.- 3.*I, 1.}, +// { 0., 0., 8.- 2.*I} +// }; +// using multi::blas::side; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array const B = { +// {1. + 1.*I, 5. + 3.*I}, +// {2. + 1.*I, 9. + 3.*I}, +// {3. + 1.*I, 1. - 1.*I}, +// }; +// using multi::blas::gemm; +// { +// auto S = trsm(filling::upper, diagonal::general, 1., A, B); // S = Ainv.B +// BOOST_TEST( real(S[2][1]) == 0.147059 ); +// } +// { +// auto const BT = +rotated(B); +// auto S = trsm(filling::upper, diagonal::general, 1., A, rotated(BT)); +// BOOST_TEST( real(S[2][1]) == 0.147059 ); +// } +// { +// auto const AT = +rotated(A); +// auto S = trsm(filling::upper, diagonal::general, 1., rotated(AT), B); +// BOOST_TEST( real(S[2][1]) == 0.147059 ); +// } +// { +// auto const AT = +rotated(A); +// auto const BT = +rotated(B); +// auto S = trsm(filling::upper, diagonal::general, 1., rotated(AT), rotated(BT)); +// BOOST_TEST( real(S[2][1]) == 0.147059 ); +// } +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check, *utf::tolerance(0.00001)){ +// using complex = std::complex; complex const I{0, 1}; +// multi::array const A = { +// { 1. + 4.*I, 3., 4.- 10.*I}, +// { 0., 7.- 3.*I, 1.}, +// { 0., 0., 8.- 2.*I} +// }; +// using multi::blas::filling; +// using multi::blas::diagonal; +// { +// multi::array const B = { +// {1. + 1.*I, 5. + 3.*I}, +// {2. + 1.*I, 9. + 3.*I}, +// {3. + 1.*I, 1. - 1.*I}, +// }; +// using multi::blas::hermitized; +// { +// auto S = trsm(filling::lower, diagonal::general, 1., hermitized(A), B); // S = A⁻¹†.B, S† = B†.A⁻¹ +// BOOST_TEST( real(S[2][1]) == 1.71608 ); +// } +// { +// multi::array const B = { +// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, +// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} +// }; +// auto S =* trsm(filling::upper, 1., A, hermitized(B)); // S = A⁻¹B†, S†=B.A⁻¹†, S=(B.A⁻¹)†, B <- S†, B <- B.A⁻¹† +// BOOST_TEST( imag(S[2][1]) == +0.147059 ); +// BOOST_TEST( imag(B[1][2]) == -0.147059 ); +// } +// { +// multi::array const B = { +// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, +// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} +// }; +// auto S =* trsm(filling::upper, 2., A, hermitized(B)); // S = A⁻¹B†, S†=B.A⁻¹†, S=(B.A⁻¹)†, B <- S†, B <- B.A⁻¹† +// BOOST_TEST( imag(S[2][1]) == +0.147059*2. ); +// BOOST_TEST( imag(B[1][2]) == -0.147059*2. ); +// } +// } +//} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const, *utf::tolerance(0.00001)){ +// using complex = std::complex; complex const I{0, 1}; +// multi::array const A = { +// { 1. + 4.*I, 3., 4.- 10.*I}, +// { 0., 7.- 3.*I, 1.}, +// { 0., 0., 8.- 2.*I} +// }; +// multi::array B = { +// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, +// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} +// }; +// using multi::blas::trsm; +// using multi::blas::filling; +// using multi::blas::hermitized; +// trsm(filling::upper, A, hermitized(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† +// BOOST_TEST( imag(B[1][2]) == -0.147059 ); +//} +//#endif +//#endif + + +//#endif +#endif diff --git a/include/multi/adaptors/complex/CMakeLists.txt b/include/boost/multi/adaptors/complex/CMakeLists.txt similarity index 100% rename from include/multi/adaptors/complex/CMakeLists.txt rename to include/boost/multi/adaptors/complex/CMakeLists.txt diff --git a/include/boost/multi/adaptors/complex/adl.hpp b/include/boost/multi/adaptors/complex/adl.hpp new file mode 100644 index 000000000..7f59ee33c --- /dev/null +++ b/include/boost/multi/adaptors/complex/adl.hpp @@ -0,0 +1,176 @@ +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// TODO(correaa) move this header to blas/numeric + +#ifndef BOOST_MULTI_ADAPTORS_COMPLEX_ADL_HPP +#define BOOST_MULTI_ADAPTORS_COMPLEX_ADL_HPP + +// #include + +// #include "detail/fix_complex_traits.hpp" + +#include +#include // for forward + +#define BOOST_MULTI_DECLRETURN(ExpR) -> decltype(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing +#define BOOST_MULTI_JUSTRETURN(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing + +namespace boost { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat +namespace multi { + +class adl_conj_t { + template constexpr auto _(priority<1> /**/, As&&... args) const BOOST_MULTI_JUSTRETURN(std::conj(std::forward(args)...)) template constexpr auto _(priority<2> /**/, As&&... args) const BOOST_MULTI_DECLRETURN(conj(std::forward(args)...)) template constexpr auto _(priority<3> /**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).conj(std::forward(args)...)) + + public : template + constexpr auto + operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<3>{}, std::forward(args)...)) +}; + +inline constexpr adl_conj_t adl_conj; + +class adl_real_t { + template constexpr auto _(priority<1> /**/, As&&... args) const BOOST_MULTI_DECLRETURN(std::real(std::forward(args)...)) template constexpr auto _(priority<2> /**/, As&&... args) const BOOST_MULTI_DECLRETURN(real(std::forward(args)...)) template constexpr auto _(priority<3> /**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).real(std::forward(args)...)) + + public : template + constexpr auto + operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<3>{}, std::forward(args)...)) +}; + +inline constexpr adl_real_t adl_real; + +class adl_imag_t { + template constexpr auto _(priority<1> /**/, As&&... args) const BOOST_MULTI_DECLRETURN(std::imag(std::forward(args)...)) template constexpr auto _(priority<2> /**/, As&&... args) const BOOST_MULTI_DECLRETURN(imag(std::forward(args)...)) template constexpr auto _(priority<3> /**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).imag(std::forward(args)...)) + + public : template + constexpr auto + operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<3>{}, std::forward(args)...)) +}; + +inline constexpr adl_imag_t adl_imag; + +struct real_t; +struct imag_t; + +template +struct _complex { // NOLINT(readability-identifier-naming) deprecating this + using value_type = ValueType; + + private: + value_type re_; + value_type im_; + + public: + _complex() = default; + + constexpr explicit _complex(value_type real) : re_{real}, im_{value_type{0}} {} + constexpr _complex(value_type real, value_type imag) // NOLINT(bugprone-easily-swappable-parameters) + : re_{real}, im_{imag} {} + + constexpr explicit _complex(std::complex const& other) : re_{other.real()}, im_{other.imag()} {} + + template< + class T, + std::enable_if_t< // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + sizeof(T) == 2 * sizeof(value_type) && + std::is_assignable().real())>{} && std::is_assignable().imag())>{}, + int> =0> + constexpr explicit operator T const&() const& { + return reinterpret_cast(*this); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + template< + class T, + std::enable_if_t< // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + sizeof(T) == 2 * sizeof(value_type) && + std::is_assignable_v().real())> && + std::is_assignable_v().imag())>, + int> = 0> + constexpr explicit operator T&() & { return reinterpret_cast(*this); } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + + constexpr auto std() const& -> std::complex const& { + return reinterpret_cast const&>(*this); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + constexpr auto std() & -> std::complex& { + return reinterpret_cast&>(*this); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + friend constexpr auto abs(_complex const& self) { return abs(self.std()); } + friend constexpr auto operator-(_complex const& self, _complex const& other) + -> _complex { return self.std() - other.std(); } + + constexpr auto real() & -> value_type& { return re_; } + constexpr auto real() const& -> value_type const& { return re_; } + + constexpr auto imag() & -> value_type& { return im_; } + constexpr auto imag() const& -> value_type const& { return im_; } + + template constexpr auto operator+=(Real const& other) & -> decltype(re_ += other, *this) { return re_ += other, *this; } + template constexpr auto operator-=(Real const& other) & -> decltype(re_ -= other, *this) { return re_ -= other, *this; } + template constexpr auto operator*=(Real const& other) & -> decltype(re_ *= other, im_ *= other, *this) { return re_ *= other, im_ *= other, *this; } + template constexpr auto operator/=(Real const& other) & -> decltype(re_ /= other, im_ /= other, *this) { return re_ /= other, im_ /= other, *this; } + + template constexpr auto operator+=(Complex const& other) & -> decltype(re_ += other.re, im_ += other.im, *this) { return re_ += other.re, im_ += other.im, *this; } + template constexpr auto operator-=(Complex const& other) & -> decltype(re_ -= other.re, im_ -= other.im, *this) { return re_ -= other.re, im_ -= other.im, *this; } +}; + +struct real_t { + template::element, typename ValueType = typename E::value_type> + constexpr auto operator()(Array&& array) const + -> decltype(std::forward(array).template reinterpret_array_cast<_complex>().template member_cast(&_complex::real)) { + return std::forward(array).template reinterpret_array_cast<_complex>().template member_cast(&_complex::real); + } + template::value_type, + std::enable_if_t< // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + sizeof(T) == 2 * sizeof(ValueType) && + std::is_assignable_v()))> && + std::is_assignable_v()))>, + int> = 0> + constexpr auto operator()(T& value) const -> ValueType& { return reinterpret_cast&>(value).real; } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : TODO(correaa) : t[0] + template::value_type, + std::enable_if_t< // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + sizeof(T) == 2 * sizeof(ValueType) && + std::is_assignable_v()))> && + std::is_assignable_v()))>, + int> = 0> + auto operator()(T const& value) const -> ValueType const& { + return reinterpret_cast const&>(value).real; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : TODO(correaa) : t[0] + } +}; + +struct imag_t { + template::element, typename ValueType = typename E::value_type> + constexpr auto operator()(Array&& array) const + -> decltype(std::forward(array).template reinterpret_array_cast<_complex>().template member_cast(&_complex::imag)) { + return std::forward(array).template reinterpret_array_cast<_complex>().template member_cast(&_complex::imag); + } + template::value_type, + std::enable_if_t< // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + sizeof(T) == 2 * sizeof(ValueType) && + std::is_assignable()))>{} && + std::is_assignable()))>{}, + int> = 0> + constexpr auto operator()(T& value) const -> ValueType& { + return reinterpret_cast&>(value).imag; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : TODO(correaa) : t[1] + } + template::value_type, + std::enable_if_t< // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + sizeof(T) == 2 * sizeof(ValueType) && + std::is_assignable()))>{} && + std::is_assignable()))>{}, + int> = 0> + constexpr auto operator()(T const& value) const -> ValueType const& { + return reinterpret_cast const&>(value).imag; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : TODO(correaa) : t[1] + } +}; + +//[[maybe_unused]] static constexpr real_t real; +//[[maybe_unused]] static constexpr imag_t imag; + +} // end namespace multi +} // end namespace boost + +#undef BOOST_MULTI_DECLRETURN +#undef BOOST_MULTI_JUSTRETURN + +#endif // BOOST_MULTI_ADAPTORS_COMPLEX_ADL_HPP diff --git a/include/multi/adaptors/complex/test/CMakeLists.txt b/include/boost/multi/adaptors/complex/test/CMakeLists.txt similarity index 70% rename from include/multi/adaptors/complex/test/CMakeLists.txt rename to include/boost/multi/adaptors/complex/test/CMakeLists.txt index 9a01df440..f47938b2c 100644 --- a/include/multi/adaptors/complex/test/CMakeLists.txt +++ b/include/boost/multi/adaptors/complex/test/CMakeLists.txt @@ -1,56 +1,20 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- -#[=[Multi Test suite can be run like this: - mkdir -p build - cd build - cmake .. [-DENABLE_CUDA=1] - make -j - ctest -j --output-on-error [-T memcheck] - exit -#]=] -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.16) -set(CMAKE_VERBOSE_MAKEFILE ON) - -set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -if((NOT - CMAKE_CXX_COMPILER_ID - STREQUAL - "PGI" - ) - AND (NOT - DART_COMPILER_NAME - STREQUAL - "nvcc" - ) - AND (NOT - DART_COMPILER_NAME - STREQUAL - "icpc" - ) -) - find_package(Boost REQUIRED COMPONENTS unit_test_framework) - link_libraries("-lboost_unit_test_framework") -else() - find_package(Boost REQUIRED) # cmake cannot detect this component with pgi compiler - link_libraries("-lboost_unit_test_framework") - - link_libraries("-lblas") # cmake cannot detect BLAS with pgi/nvc++ but it ships with its own version -endif() - if(CMAKE_CXX_COMPILER_ID STREQUAL "PGI") add_definitions(-DRETURN_BY_STACK) endif() if(ENABLE_CUDA OR DEFINED CXXCUDA) enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) + endif() endif() enable_testing() -list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # needs cmake 3.17 include(CTest) include_directories(${CMAKE_BINARY_DIR}) @@ -68,18 +32,20 @@ foreach(TEST_FILE ${TEST_SRCS}) #set_property(TARGET ${TEST_EXE} PROPERTY "${CUDA_ARCH_LIST}") target_compile_options(${TEST_EXE} PRIVATE -std=c++17) endif() - # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") - target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) - target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) + target_link_libraries(${TEST_EXE} PRIVATE multi) - target_link_libraries(${TEST_EXE} PRIVATE multi) target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) - target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) + target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) + + target_compile_definitions(${TEST_EXE} PRIVATE BOOST_TEST_DYN_LINK=1) + target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) + target_compile_definitions(${TEST_EXE} PRIVATE BOOST_TEST_MODULE="C++ Unit Tests for Multi complex") + + if(NOT ENABLE_CUDA AND (NOT DART_COMPILER_NAME @@ -133,7 +99,8 @@ foreach(TEST_FILE ${TEST_SRCS}) --display_error_number --diag_error=incompatible_assignment_operands,returning_ptr_to_local_variable,subscript_out_of_range,used_before_set,undefined_preproc_id,implicit_func_decl,implicit_return_from_non_void_function,missing_type_specifier > $<$: - /W4> + #/W4 + > ) else() target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_TEST_DYN_LINK=1") diff --git a/include/multi/adaptors/complex/test/complex.cpp b/include/boost/multi/adaptors/complex/test/complex.cpp similarity index 74% rename from include/multi/adaptors/complex/test/complex.cpp rename to include/boost/multi/adaptors/complex/test/complex.cpp index 14e620900..91c907e7d 100644 --- a/include/multi/adaptors/complex/test/complex.cpp +++ b/include/boost/multi/adaptors/complex/test/complex.cpp @@ -1,14 +1,15 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi complex" -#include +// #include #include #include "../../complex.hpp" +#include -#include +#include namespace multi = boost::multi; @@ -81,7 +82,7 @@ BOOST_AUTO_TEST_CASE(float_complex_assignment) { } BOOST_AUTO_TEST_CASE(float_complex_aggregate) { - static_assert( std::is_aggregate_v> ); + static_assert(std::is_aggregate_v>); // auto const c = multi::complex{._real = 1.0, ._imag = 2.0}; @@ -98,7 +99,7 @@ BOOST_AUTO_TEST_CASE(double_complex_abs) { BOOST_AUTO_TEST_CASE(double_complex_plus_eq) { using multi::literals::operator""_I; - multi::complex zeta = 1.0 + 2.0_I; + multi::complex zeta = 1.0 + 2.0_I; multi::complex const yeta = 1.0 + 2.0_I; zeta += yeta; @@ -106,3 +107,25 @@ BOOST_AUTO_TEST_CASE(double_complex_plus_eq) { BOOST_REQUIRE( zeta == 2.0 * yeta ); BOOST_REQUIRE( zeta == yeta / 0.5 ); } + +// BOOST_AUTO_TEST_CASE(complex_member_cast) { +// multi::array, 2> A = { +// { {1., 2.}, {3., 4.}}, +// {{22., 33.}, {5., 9.}}, +// }; + +// { +// auto&& Areal = A.member_cast(&multi::complex::re); +// auto&& Aimag = A.member_cast(&multi::complex::im); + +// BOOST_REQUIRE(Areal[1][0] == 22.); +// BOOST_REQUIRE(Aimag[1][0] == 33.); +// } +// { +// auto&& Areal = A.member_cast(&multi::complex::re); +// auto&& Aimag = A.member_cast(&multi::complex::im); + +// BOOST_REQUIRE(Areal[1][0] == 22.); +// BOOST_REQUIRE(Aimag[1][0] == 33.1); +// } +// } diff --git a/include/multi/adaptors/complex.hpp b/include/boost/multi/adaptors/complex_.hpp similarity index 81% rename from include/multi/adaptors/complex.hpp rename to include/boost/multi/adaptors/complex_.hpp index c981684ed..9a8f3209c 100644 --- a/include/multi/adaptors/complex.hpp +++ b/include/boost/multi/adaptors/complex_.hpp @@ -1,8 +1,7 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2024 Alfredo A. Correa -#ifndef MULTI_ADAPTORS_COMPLEX_HPP -#define MULTI_ADAPTORS_COMPLEX_HPP +#ifndef BOOST_MULTI_ADAPTORS_COMPLEX_HPP +#define BOOST_MULTI_ADAPTORS_COMPLEX_HPP #pragma once #include // to define its traits @@ -22,7 +21,7 @@ struct [[nodiscard]] imaginary { using value_type = T; - // constexpr explicit imaginary(T value) : value_{value} {} + // constexpr explicit imaginary(T value) : value_{value} {} template friend constexpr auto operator+(U real, imaginary imag) -> complex; // constexpr static imaginary i{T{1}}; // NOLINT(clang-diagnostic-error) "constexpr variable cannot have non-literal type"? @@ -48,22 +47,22 @@ struct [[nodiscard]] imaginary { [[nodiscard]] constexpr auto operator/(imaginary other) const { return T{1} / other._value; } }; -constexpr imaginary I{}; // NOLINT(readability-identifier-length) imaginary unit +inline constexpr imaginary I{}; // NOLINT(readability-identifier-length) imaginary unit namespace literals { // constexpr imaginary operator""_i(unsigned long long d) { -// return imaginary{static_cast(d)}; +// return imaginary{static_cast(d)}; // } -constexpr auto operator"" _i(long double value) { return imaginary{static_cast(value)}; } -// constexpr auto operator"" i(long double value) {return imaginary{static_cast(value)};} -constexpr auto operator"" _I(long double value) { return imaginary{static_cast(value)}; } +constexpr auto operator""_i(long double value) { return imaginary{static_cast(value)}; } +// constexpr auto operator"" i(long double value) {return imaginary{static_cast(value)};} +constexpr auto operator""_I(long double value) { return imaginary{static_cast(value)}; } // constexpr auto operator"" f_i(long double value) {return imaginary{static_cast(value)};} constexpr auto operator""_f_i(long double value) { return imaginary{static_cast(value)}; } -constexpr auto operator"" _if(long double value) { return imaginary{static_cast(value)}; } +constexpr auto operator""_if(long double value) { return imaginary{static_cast(value)}; } constexpr auto operator""_F_I(long double value) { return imaginary{static_cast(value)}; } -constexpr auto operator"" _IF(long double value) { return imaginary{static_cast(value)}; } +constexpr auto operator""_IF(long double value) { return imaginary{static_cast(value)}; } // template // constexpr auto operator""_FI() noexcept {} @@ -93,10 +92,10 @@ struct [[nodiscard]] complex { friend constexpr auto conj(complex self) { return complex{self._real, -self._imag}; } - constexpr auto operator==(complex const& other) const {return _real == other._real and _imag == other._imag;} - constexpr auto operator!=(complex const& other) const {return _real != other._real or _imag != other._imag;} + constexpr auto operator==(complex const& other) const {return _real == other._real && _imag == other._imag;} + constexpr auto operator!=(complex const& other) const {return _real != other._real || _imag != other._imag;} -// auto operator=(complex const&) -> complex& = default; +// auto operator=(complex const&) -> complex& = default; constexpr auto operator=(real_type re) -> complex& {(*this) = complex{re, real_type{0.0}}; return *this;} friend constexpr auto operator-(complex self) {return complex{-self._real, -self._imag};} @@ -137,7 +136,7 @@ struct [[nodiscard]] complex { // oos = T(1.0) / s; // complex quot( ((ars * brs) + (ais * bis)) * oos - // , ((ais * brs) - (ars * bis)) * oos); + // , ((ais * brs) - (ars * bis)) * oos); // return quot; } friend constexpr auto norm(complex self) { @@ -149,8 +148,8 @@ struct [[nodiscard]] complex { return sqrt(self._real*self._real + self._real*self._real); // bad! according to NR // using std::abs; // return self._real > self._imag? - // abs(self._real)*sqrt(real_type{1} + (self._imag/self._real)*(self._imag/self._real)) - // :abs(self._imag)*sqrt(real_type{1} + (self._real/self._imag)*(self._real/self._imag)) + // abs(self._real)*sqrt(real_type{1} + (self._imag/self._real)*(self._imag/self._real)) + // :abs(self._imag)*sqrt(real_type{1} + (self._real/self._imag)*(self._real/self._imag)) // ; } }; diff --git a/include/multi/adaptors/cuda/CMakeLists.txt b/include/boost/multi/adaptors/cuda/CMakeLists.txt similarity index 69% rename from include/multi/adaptors/cuda/CMakeLists.txt rename to include/boost/multi/adaptors/cuda/CMakeLists.txt index 45fd654e2..fd73056da 100644 --- a/include/multi/adaptors/cuda/CMakeLists.txt +++ b/include/boost/multi/adaptors/cuda/CMakeLists.txt @@ -5,13 +5,11 @@ cmake_minimum_required(VERSION 3.11) # LANGUAGES CXX #) -set(CMAKE_VERBOSE_MAKEFILE ON) - -set(CMAKE_CXX_STANDARD 17) +# set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -find_package(Boost REQUIRED COMPONENTS unit_test_framework) +find_package(Boost REQUIRED COMPONENTS) # unit_test_framework) if(ENABLE_HIP) enable_language(HIP) # may need `module load rocm` @@ -22,7 +20,7 @@ endif() if(ENABLE_CUDA OR DEFINED CXXCUDA) enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -std=c++17 --extended-lambda --expt-relaxed-constexpr") + # set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -std=c++17 --extended-lambda --expt-relaxed-constexpr") enable_testing() list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # needs cmake 3.1 diff --git a/include/boost/multi/adaptors/cuda/algorithms/copy.hpp b/include/boost/multi/adaptors/cuda/algorithms/copy.hpp new file mode 100644 index 000000000..11322e984 --- /dev/null +++ b/include/boost/multi/adaptors/cuda/algorithms/copy.hpp @@ -0,0 +1,67 @@ +#ifdef COMPILATION_INSTRUCTIONS//-*-indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4;-*- +nvcc -D_TEST_MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY -x cu $0 -o $0x -lboost_unit_test_framework -lboost_timer&&$0x&& +clang++ -D_TEST_MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY -x cuda --cuda-gpu-arch=sm_61 -std=c++14 $0 -o $0x -lcudart -lboost_unit_test_framework -lboost_timer&&$0x&& +rm $0x; exit +#endif + +#ifndef BOOST_MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY_HPP +#define BOOST_MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY_HPP + +#include + +#include "../../../adaptors/cuda.hpp" +//#include "../algorithms/for_each.hpp" + +#ifndef BOOST_MULTI_HD +#if defined(__CUDACC__) +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif +#endif + +namespace boost{ +namespace multi{namespace cuda{ + +#if 0 +template{}> > +array_iterator copy( + array_iterator> f, + array_iterator> l, + array_iterator d +){ + assert(0); + assert(f.stride() == l.stride()); static_assert(sizeof(From) == sizeof(To), "!"); + auto n = std::distance(f, l); + if(f.stride()==1 and d.stride()==1){ + auto s = cudaMemcpy(d.data(), raw_pointer_cast(f.data()), n*sizeof(To), cudaMemcpyDeviceToHost); assert( s == cudaSuccess ); + }else{ + auto s = cudaMemcpy2D(d.data(), d.stride()*sizeof(To), raw_pointer_cast(f.data()), f.stride()*sizeof(To), sizeof(To), n, cudaMemcpyDeviceToHost); + assert( s == cudaSuccess ); + } + return d + n; +} + +template{}> > +array_iterator copy( + array_iterator> f, + array_iterator> l, + array_iterator > d +){ + assert(0); + assert(f.stride() == l.stride()); static_assert(sizeof(From) == sizeof(To), "!"); + auto n = std::distance(f, l); + if(f.stride()==1 and d.stride()==1){ + auto s = cudaMemcpy(raw_pointer_cast(d.data()), raw_pointer_cast(f.data()), n*sizeof(To), cudaMemcpyDeviceToHost); assert( s == cudaSuccess ); + }else{ + auto s = cudaMemcpy2D(raw_pointer_cast(d.data()), d.stride()*sizeof(To), raw_pointer_cast(f.data()), f.stride()*sizeof(To), sizeof(To), n, cudaMemcpyDeviceToDevice); + assert( s == cudaSuccess ); + } + return d + n; +} +#endif + +}} +} + +#endif // BOOST_MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY_HPP diff --git a/include/boost/multi/adaptors/cuda/cublas.hpp b/include/boost/multi/adaptors/cuda/cublas.hpp new file mode 100644 index 000000000..e14428cee --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas.hpp @@ -0,0 +1,7 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#pragma once + +#include diff --git a/include/boost/multi/adaptors/cuda/cublas/call.hpp b/include/boost/multi/adaptors/cuda/cublas/call.hpp new file mode 100644 index 000000000..9a3295f22 --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas/call.hpp @@ -0,0 +1,39 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_CUDA_CUBLAS_CALL_HPP +#define BOOST_MULTI_ADAPTORS_CUDA_CUBLAS_CALL_HPP +#pragma once + +#include + +#if !defined(MULTI_USE_HIP) +#include // cudaDeviceSynchronize +#else +#include // cudaDeviceSynchronize +#endif + +#if !defined(MULTI_USE_HIP) +#define hicup(name) cuda##name +#define HICUP(name) CU##name +#else +#define hicup(name) hip##name +#define HICUP(name) HIP##name +#endif + +namespace boost::multi::cuda::cublas{ + +template // needs C++17 +void call(Args... args){ + auto e = static_cast(Function(args...)); + if(e != cublas::error::success) { throw std::system_error{e, "cannot call function "+ std::string{__PRETTY_FUNCTION__}}; } +} + +#define CUBLAS_(F) call + +} + +#undef hicup +#undef HICUP +#endif // BOOST_MULTI_ADAPTORS_CUDA_CUBLAS_CALL_HPP diff --git a/include/boost/multi/adaptors/cuda/cublas/context.hpp b/include/boost/multi/adaptors/cuda/cublas/context.hpp new file mode 100644 index 000000000..e7d53665a --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas/context.hpp @@ -0,0 +1,425 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#pragma once + +// #include +#include + +#include +#include + +#if !defined(MULTI_USE_HIP) +#include // for thrust::cuda::pointer +#else +#include // for thrust::cuda::pointer +#include +#endif + +#if not defined(MULTI_USE_HIP) +#define hicup(name) cuda##name +#define hicu(name) cu##name +#define HICU(name) CU##name +#define thrust_hicup thrust::cuda +#else +#define hicup(name) hip##name +#define hicu(name) hip##name +#define HICU(name) HIP##name +#define thrust_hicup thrust::hip +#endif + +namespace boost { +namespace multi::cuda::cublas { + +class operation { + hicu(blasOperation_t) impl_; + + public: + explicit operation(char trans) : impl_{[=]{ + switch(trans) { + case 'N': return HICU(BLAS_OP_N); + case 'T': return HICU(BLAS_OP_T); + case 'C': return HICU(BLAS_OP_C); + default : assert(0); + } + return hicu(blasOperation_t){}; + }()} {} + operator hicu(blasOperation_t)() const{return impl_;} +}; + +class side { + hicu(blasSideMode_t) impl_; + + public: + explicit side(char trans) : impl_{[=] { + switch(trans) { + case 'L': return HICU(BLAS_SIDE_LEFT); + case 'R': return HICU(BLAS_SIDE_RIGHT); + default: assert(0); + + } + assert(0); return hicu(blasSideMode_t){}; + }()} {} + operator hicu(blasSideMode_t)() const {return impl_;} +}; + +class filling { + hicu(blasFillMode_t) impl_; + + public: + explicit filling(char trans) : impl_{[=] { + switch(trans) { + case 'L': return HICU(BLAS_FILL_MODE_LOWER); + case 'U': return HICU(BLAS_FILL_MODE_UPPER); + } + assert(0); return hicu(blasFillMode_t){}; + }()} {} + operator hicu(blasFillMode_t)() const {return impl_;} +}; + +class diagonal { + hicu(blasDiagType_t) impl_; + + public: + explicit diagonal(char trans) : impl_{[=] { + switch(trans) { + case 'N': return HICU(BLAS_DIAG_NON_UNIT); + case 'U': return HICU(BLAS_DIAG_UNIT); + } + assert(0); return hicu(blasDiagType_t){}; + }()} {} + operator hicu(blasDiagType_t)() const {return impl_;} +}; + +using blas::is_s; +using blas::is_d; +using blas::is_c; +using blas::is_z; + +using std::is_assignable; +using std::is_assignable_v; +using std::is_convertible_v; + +// enum class type {S, D, C, Z}; + +// template +// constexpr auto type_of(T const& = {}) -> cublas::type { +// static_assert(is_s{} || is_d{} || is_c{} || is_z{}); +// if(is_s{}) {return type::S;} +// else if(is_d{}) {return type::D;} +// else if(is_c{}) {return type::C;} +// else if(is_z{}) {return type::Z;} +// } + +#if defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) +#if (hipblasVersionMajor >= 3) + using Complex = hipFloatComplex; + using DoubleComplex = hipDoubleComplex; +#else + using Complex = hipblasComplex; + using DoubleComplex = hipblasDoubleComplex; +#endif +#else // __CUDA__ __NVCC__ or clang cuda + using Complex = cuComplex; + using DoubleComplex = cuDoubleComplex; +#endif + +template{}, int> =0> constexpr auto data_cast(T * p) {return reinterpret_cast(p);} +template{}, int> =0> constexpr auto data_cast(T * p) {return reinterpret_cast(p);} +template{}, int> =0> constexpr auto data_cast(T * p) {return reinterpret_cast(p);} +template{}, int> =0> constexpr auto data_cast(T * p) {return reinterpret_cast(p);} + +template{}, int> =0> constexpr auto data_cast(T const* p) {return reinterpret_cast(p);} +template{}, int> =0> constexpr auto data_cast(T const* p) {return reinterpret_cast(p);} +template{}, int> =0> constexpr auto data_cast(T const* p) {return reinterpret_cast(p);} +template{}, int> =0> constexpr auto data_cast(T const* p) {return reinterpret_cast(p);} + +class context : private std::unique_ptr::element_type, decltype(&hicu(blasDestroy))> { + using pimpl_t = std::unique_ptr::element_type, decltype(&hicu(blasDestroy))>; + hicup(Stream_t) stream() const {hicup(Stream_t) streamId; cuda::cublas::call(this->get(), &streamId); return streamId;} + template + void sync_call(Args... args) const { + call(const_cast(this)->get(), args...); + this->synchronize(); + } + template + void sync_call(Args... args) { + call(this->get(), args...); + this->synchronize(); + } + + public: + using pimpl_t::get; + static context& get_instance() { + thread_local context ctxt; + return ctxt; + }; + context() : pimpl_t{[] {hicu(blasHandle_t) h; hicu(blasCreate)(&h); return h;}(), &hicu(blasDestroy)} {} + // using ssize_t = int; + // static int version() {int ret; cuda::cublas::call(nullptr, &ret); return ret;} // no hipblasGetVersion available + void synchronize() const { + // cudaError_t e = cudaDeviceSynchronize(); + auto s = stream(); + if(s != 0) {throw std::logic_error("CUBLAS stream expected to be zero");} + hicup(Error_t) e = hicup(StreamSynchronize)(s); + if(e != hicup(Success)) {throw std::runtime_error{"cannot synchronize stream in cublas context"};} + } + + template< + class SSize, + class XP, class X = typename std::pointer_traits::element_type, + class YP, class Y = typename std::pointer_traits::element_type, + class = decltype(std::swap(std::declval(), std::declval())), + std::enable_if_t>, int> = 0 + > + void swap(SSize n, XP x, SSize incx, YP y, SSize incy) const { + if(is_s{}) {sync_call(static_cast(n), (float *)raw_pointer_cast(x), static_cast(incx), (float *)raw_pointer_cast(y), static_cast(incy));} + if(is_d{}) {sync_call(static_cast(n), (double *)raw_pointer_cast(x), static_cast(incx), (double *)raw_pointer_cast(y), static_cast(incy));} + if(is_c{}) {sync_call(static_cast(n), (Complex *)raw_pointer_cast(x), static_cast(incx), (Complex *)raw_pointer_cast(y), static_cast(incy));} + if(is_z{}) {sync_call(static_cast(n), (DoubleComplex*)raw_pointer_cast(x), static_cast(incx), (DoubleComplex*)raw_pointer_cast(y), static_cast(incy));} + } + + template< + class SSize, + class XP, class X = typename std::pointer_traits::element_type, + class YP, class Y = typename std::pointer_traits::element_type, + class = decltype(std::declval() = std::declval()), + std::enable_if_t>, int> = 0 + > + void copy(SSize n, XP x, SSize incx, YP y, SSize incy) const { + if(is_s{}) {sync_call(static_cast(n), (float const*)raw_pointer_cast(x), static_cast(incx), (float *)raw_pointer_cast(y), static_cast(incy));} + if(is_d{}) {sync_call(static_cast(n), (double const*)raw_pointer_cast(x), static_cast(incx), (double *)raw_pointer_cast(y), static_cast(incy));} + if(is_c{}) {sync_call(static_cast(n), (Complex const*)raw_pointer_cast(x), static_cast(incx), (Complex *)raw_pointer_cast(y), static_cast(incy));} + if(is_z{}) {sync_call(static_cast(n), (DoubleComplex const*)raw_pointer_cast(x), static_cast(incx), (DoubleComplex*)raw_pointer_cast(y), static_cast(incy));} + } + + template::element_type, + class = decltype(std::declval() *= ALPHA{}), + std::enable_if_t>, int> = 0 + > + void scal(SSize n, ALPHA const& alpha, XP x, SSize incx) const { + if(is_s{}) { sync_call(static_cast(n), (float const*)alpha, (float *)::thrust::raw_pointer_cast(x), static_cast(incx)); } + if(is_d{}) { sync_call(static_cast(n), (double const*)alpha, (double *)::thrust::raw_pointer_cast(x), static_cast(incx)); } + if(is_c{}) { sync_call(static_cast(n), (Complex const*)alpha, (Complex *)::thrust::raw_pointer_cast(x), static_cast(incx)); } + if(is_z{}) { sync_call(static_cast(n), (DoubleComplex const*)alpha, (DoubleComplex*)::thrust::raw_pointer_cast(x), static_cast(incx)); } + } + + template::element_type, class YP, class Y = typename std::pointer_traits::element_type, + typename = decltype(std::declval() = ALPHA{}*X{} + Y{}), + std::enable_if_t> and std::is_convertible_v>, int> = 0 + > + void axpy(SSize n, ALPHA const* alpha, XP x, SSize incx, YP y, SSize incy) { + if(is_d{}) { sync_call(static_cast(n), (double const*)alpha, (double const*)raw_pointer_cast(x), static_cast(incx), (double *)raw_pointer_cast(y), static_cast(incy)); } + if(is_z{}) { sync_call(static_cast(n), (DoubleComplex const*)alpha, (DoubleComplex const*)raw_pointer_cast(x), static_cast(incx), (DoubleComplex*)raw_pointer_cast(y), static_cast(incy)); } + } + + template::element_type, class XXP, class XX = typename std::pointer_traits::element_type, class BETA, class YYP, class YY = typename std::pointer_traits::element_type, + typename = decltype(std::declval() = ALPHA{}*(AA{}*XX{} + AA{}*XX{})), + std::enable_if_t> and std::is_convertible_v> and std::is_convertible_v>, int> = 0 + > + auto gemv(char transA, SSize m, SSize n, ALPHA const* alpha, AAP aa, SSize lda, XXP xx, SSize incx, BETA const* beta, YYP yy, SSize incy) { + if(is_d{}) {sync_call(operation{transA}, static_cast(m), static_cast(n), (double const*)alpha, (double const*)::thrust::raw_pointer_cast(aa), static_cast(lda), (double const*)::thrust::raw_pointer_cast(xx), static_cast(incx), (double const*)beta, (double *)::thrust::raw_pointer_cast(yy), static_cast(incy));} + if(is_z{}) {sync_call(operation{transA}, static_cast(m), static_cast(n), (DoubleComplex const*)alpha, (DoubleComplex const*)::thrust::raw_pointer_cast(aa), static_cast(lda), (DoubleComplex const*)::thrust::raw_pointer_cast(xx), static_cast(incx), (DoubleComplex const*)beta, (DoubleComplex*)::thrust::raw_pointer_cast(yy), static_cast(incy));} + } + + template::element_type, class BBP, class BB = typename std::pointer_traits::element_type, class BETA, class CCP, class CC = typename std::pointer_traits::element_type, + typename = decltype(std::declval() = ALPHA{}*(AA{}*BB{} + AA{}*BB{})), + class = std::enable_if_t> and std::is_convertible_v> and std::is_convertible_v>> + > + void gemm(char transA, char transB, SSize m, SSize n, SSize k, ALPHA const* alpha, AAP aa, SSize lda, BBP bb, SSize ldb, BETA const* beta, CCP cc, SSize ldc) { + /*MULTI_MARK_SCOPE("cublasXgemm");*/ + if(is_d{}) {sync_call(cuda::cublas::operation{transA}, cuda::cublas::operation{transB}, static_cast(m), static_cast(n), static_cast(k), (double const*)alpha, (double const*)::thrust::raw_pointer_cast(aa), static_cast(lda), (double const*)::thrust::raw_pointer_cast(bb), static_cast(ldb), (double const*)beta, (double *)::thrust::raw_pointer_cast(cc), static_cast(ldc));} + if(is_z{}) {sync_call(cuda::cublas::operation{transA}, cuda::cublas::operation{transB}, static_cast(m), static_cast(n), static_cast(k), (DoubleComplex const*)alpha, (DoubleComplex const*)::thrust::raw_pointer_cast(aa), static_cast(lda), (DoubleComplex const*)::thrust::raw_pointer_cast(bb), static_cast(ldb), (DoubleComplex const*)beta, (DoubleComplex*)::thrust::raw_pointer_cast(cc), static_cast(ldc));} + } + + template::element_type, class BBP, class BB = typename std::pointer_traits::element_type, + std::enable_if_t< + is_z{} && is_z{} && is_assignable_v && is_assignable_v && + is_convertible_v> && is_convertible_v> + ,int> =0 + > + void trsm(char side, char ul, char transA, char diag, SSize m, SSize n, ALPHA alpha, AAP aa, SSize lda, BBP bb, SSize ldb) { + sync_call(cuda::cublas::side{side}, cuda::cublas::filling{ul}, cuda::cublas::operation{transA}, cuda::cublas::diagonal{diag}, static_cast(m), static_cast(n), (DoubleComplex const*)&alpha, (DoubleComplex*)raw_pointer_cast(aa), static_cast(lda), (DoubleComplex*)raw_pointer_cast(bb), static_cast(ldb)); + } + + template::element_type, class BBP, class BB = typename std::pointer_traits::element_type, + std::enable_if_t< + is_d{} && is_d{} && is_assignable_v && is_assignable_v && + is_convertible_v> && is_convertible_v> + ,int> =0 + > + void trsm(char side, char ul, char transA, char diag, SSize m, SSize n, ALPHA alpha, AAP aa, SSize lda, BBP bb, SSize ldb) { + sync_call( + cuda::cublas::side{side}, + cuda::cublas::filling{ul}, + cuda::cublas::operation{transA}, + cuda::cublas::diagonal{diag}, + static_cast(m), static_cast(n), (double const*)&alpha, (double const*)raw_pointer_cast(aa), static_cast(lda), (double*)raw_pointer_cast(bb), static_cast(ldb) + ); + } + + template< + class SSize, + class XXP, class XX = typename std::pointer_traits::element_type, + class YYP, class YY = typename std::pointer_traits::element_type, + class RRP, class RR = typename std::pointer_traits::element_type, + std::enable_if_t< + is_d{} && is_d{} && is_d{} && is_assignable_v + && is_convertible_v> && is_convertible_v> + && is_convertible_v + , int> =0 + > + void dot(SSize n, XXP xx, SSize incx, YYP yy, SSize incy, RRP rr) { + hicu(blasPointerMode_t) mode; + auto s = hicu(blasGetPointerMode)(get(), &mode); assert( s == HICU(BLAS_STATUS_SUCCESS) ); + assert( mode == HICU(BLAS_POINTER_MODE_HOST) ); + sync_call(static_cast(n), ::thrust::raw_pointer_cast(xx), static_cast(incx), ::thrust::raw_pointer_cast(yy), static_cast(incy), rr); + } + + template< + class SSize, + class XXP, class XX = typename std::pointer_traits::element_type, + class YYP, class YY = typename std::pointer_traits::element_type, + class RRP, class RR = typename std::pointer_traits::element_type, + std::enable_if_t< + is_z{} and is_z{} and is_z{} and is_assignable{} and + is_convertible_v> and is_convertible_v> + and (is_convertible_v> or is_convertible_v) + , int> =0 + > + void dotc(SSize n, XXP xx, SSize incx, YYP yy, SSize incy, RRP rr) { + hicu(blasPointerMode_t) mode; + auto s = hicu(blasGetPointerMode)(get(), &mode); assert( s == HICU(BLAS_STATUS_SUCCESS) ); + assert( mode == HICU(BLAS_POINTER_MODE_HOST) ); + // cublasSetPointerMode(get(), CUBLAS_POINTER_MODE_DEVICE); + if constexpr(is_convertible_v>) { + sync_call(static_cast(n), (DoubleComplex const*)::thrust::raw_pointer_cast(xx), static_cast(incx), (DoubleComplex const*)::thrust::raw_pointer_cast(yy), static_cast(incy), (DoubleComplex*)::thrust::raw_pointer_cast(rr) ); + } else { + sync_call(static_cast(n), (DoubleComplex const*)::thrust::raw_pointer_cast(xx), static_cast(incx), (DoubleComplex const*)::thrust::raw_pointer_cast(yy), static_cast(incy), (DoubleComplex*)rr); + } + } + + template< + class SSize, + class XXP, class XX = typename std::pointer_traits::element_type, + class RRP, class RR = typename std::pointer_traits::element_type, + std::enable_if_t< + is_z{} && is_d{} && is_assignable{} and + is_convertible_v> && (is_convertible_v> || is_convertible_v) + , int> =0 + > + void asum(SSize n, XXP xx, SSize incx, RRP rr) { + if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_DEVICE));} + if constexpr(is_convertible_v>) { + sync_call(static_cast(n), (DoubleComplex const*)::thrust::raw_pointer_cast(xx), static_cast(incx), (double*)::thrust::raw_pointer_cast(rr) ); + } else { + sync_call(static_cast(n), (DoubleComplex const*)::thrust::raw_pointer_cast(xx), static_cast(incx), (double*) rr ); + } + if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_HOST));} + } + + template< + typename Size, + typename SSize, + class XXP, class XX = typename std::pointer_traits::element_type, + class RRP, class RR = typename std::pointer_traits::element_type + , + std::enable_if_t< + is_z::value + , int> =0 + // , + // std::enable_if_t< + // is_z{} && is_d{} && is_assignable{} && + // is_convertible_v> && (is_convertible_v> or is_convertible_v) + // , int> =0 + > + void nrm2(Size n, XXP xx, SSize incx, RRP rr) { + if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_DEVICE));} + if constexpr(is_convertible_v>) { + sync_call(static_cast(n), reinterpret_cast(::thrust::raw_pointer_cast(xx)), static_cast(incx), reinterpret_cast(::thrust::raw_pointer_cast(rr)) ); + } else { + sync_call(static_cast(n), reinterpret_cast(::thrust::raw_pointer_cast(xx)), static_cast(incx), reinterpret_cast( rr ) ); + } + if(is_convertible_v>) { hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_HOST)); } + } + + template< + typename Size, + typename SSize, + class XXP, class XX = typename std::pointer_traits::element_type, + class RRP, class RR = typename std::pointer_traits::element_type + , + std::enable_if_t< + is_d::value + , int> =0 + // , + // std::enable_if_t< + // is_z{} && is_d{} && is_assignable{} && + // is_convertible_v> && (is_convertible_v> or is_convertible_v) + // , int> =0 + > + void nrm2(Size n, XXP xx, SSize incx, RRP rr) { + if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_DEVICE));} + if constexpr(is_convertible_v>) { + sync_call(static_cast(n), ::thrust::raw_pointer_cast(xx), static_cast(incx), reinterpret_cast(::thrust::raw_pointer_cast(rr)) ); + } else { + sync_call(static_cast(n), ::thrust::raw_pointer_cast(xx), static_cast(incx), reinterpret_cast( rr ) ); + } + if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_HOST));} + } + + template< + class SSize, + class XXP, class XX = typename std::pointer_traits::element_type, + class YYP, class YY = typename std::pointer_traits::element_type, + class RRP, class RR = typename std::pointer_traits::element_type, + std::enable_if_t< + is_z{} and is_z{} and is_z{} and is_assignable{} and + is_convertible_v> and is_convertible_v> + and (is_convertible_v> or is_convertible_v) + , int> =0 + > + void dotu(SSize n, XXP xx, SSize incx, YYP yy, SSize incy, RRP rr) { + hicu(blasPointerMode_t) mode; + auto s = hicu(blasGetPointerMode)(get(), &mode); assert( s == HICU(BLAS_STATUS_SUCCESS) ); + assert( mode == HICU(BLAS_POINTER_MODE_HOST) ); + // cublasSetPointerMode(get(), CUBLAS_POINTER_MODE_DEVICE); + if constexpr(is_convertible_v>) { + sync_call(static_cast(n), reinterpret_cast(::thrust::raw_pointer_cast(xx)), static_cast(incx), reinterpret_cast(::thrust::raw_pointer_cast(yy)), static_cast(incy), reinterpret_cast(::thrust::raw_pointer_cast(rr)) ); + } else { + sync_call(static_cast(n), reinterpret_cast(::thrust::raw_pointer_cast(xx)), static_cast(incx), reinterpret_cast(::thrust::raw_pointer_cast(yy)), static_cast(incy), reinterpret_cast(rr)); + } + // cublasSetPointerMode(get(), CUBLAS_POINTER_MODE_HOST); + } +}; + +} // end namespace multi::cuda::cublas +} // end namespace boost + +namespace boost::multi::blas { + + template<> struct is_context : std::true_type {}; + template<> struct is_context : std::true_type {}; + + template::element_type, std::enable_if_t>{}, int> =0> + boost::multi::cuda::cublas::context* default_context_of(Ptr const&) { + namespace multi = boost::multi; + return &multi::cuda::cublas::context::get_instance(); + } + + template + boost::multi::cuda::cublas::context* + #if defined(__HIPCC__) + default_context_of(::thrust::pointer const&) { + #else // __NVCC__ + default_context_of(::thrust::pointer const&) { + #endif + namespace multi = boost::multi; + return &multi::cuda::cublas::context::get_instance(); + } +} + +#undef hicup +#undef hicu +#undef HICU diff --git a/include/multi/adaptors/cuda/cublas/error.hpp b/include/boost/multi/adaptors/cuda/cublas/error.hpp similarity index 69% rename from include/multi/adaptors/cuda/cublas/error.hpp rename to include/boost/multi/adaptors/cuda/cublas/error.hpp index 9385e7719..8a97d989f 100644 --- a/include/multi/adaptors/cuda/cublas/error.hpp +++ b/include/boost/multi/adaptors/cuda/cublas/error.hpp @@ -1,12 +1,11 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -$CXXX $CXXFLAGS $0 -o $0.$X `pkg-config --cflags --libs cudart-11.0 cublas-11.0 blas` -lboost_unit_test_framework&&$0.$X&&rm $0.$X;exit -#endif -// © Alfredo A. Correa 2020 +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_CUDA_CUBLAS_ERROR_HPP -#define MULTI_ADAPTORS_CUDA_CUBLAS_ERROR_HPP +#ifndef BOOST_MULTI_ADAPTORS_CUDA_CUBLAS_ERROR_HPP +#define BOOST_MULTI_ADAPTORS_CUDA_CUBLAS_ERROR_HPP -#if defined(__NVCC__) +#if !defined(MULTI_USE_HIP) #include // cublasStatus_t #else #include // cublasStatus_t @@ -16,7 +15,7 @@ #include // std::error_category #include // std::underlying_type -#if defined(__NVCC__) +#if !defined(MULTI_USE_HIP) #define hicup(name) cuda##name #define hicu(name) cu##name #define HICU(name) CU##name @@ -28,8 +27,7 @@ #define HICUP(name) HIP##name #endif -namespace boost{ -namespace multi::cuda::cublas{ +namespace boost::multi::cuda::cublas{ enum class error : typename std::underlying_type::type{ success = HICUP(BLAS_STATUS_SUCCESS), @@ -67,46 +65,18 @@ struct error_category : std::error_category{ }; inline std::error_code make_error_code(cublas::error err) noexcept{ - return std::error_code(int(err), cublas::error_category::instance()); + return {int(err), cublas::error_category::instance()}; } -} } -namespace std{ +namespace std { template<> struct is_error_code_enum<::boost::multi::cuda::cublas::error> : true_type{}; } -#if not __INCLUDE_LEVEL__ // _TEST_MULTI_ADAPTORS_BLAS_CUDA - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuBLAS" -#define BOOST_TEST_DYN_LINK -#include - -//#include "../../array.hpp" -//#include "../../utility.hpp" - -//#include "../../adaptors/cuda.hpp" -//#include "../../adaptors/blas.hpp" -//#include "../../adaptors/blas/cuda.hpp" - -#include - -namespace multi = boost::multi; - - -BOOST_AUTO_TEST_CASE(multi_cublas_error){ - - BOOST_CHECK_THROW( - throw (std::system_error{multi::cuda::cublas::make_error_code(multi::cuda::cublas::error::not_initialized), "error test"}), - std::system_error - ); - -} - +#undef hicu #undef hicup +#undef HICU #undef HICUP #endif -#endif - diff --git a/include/boost/multi/adaptors/cuda/cublas/test/CMakeLists.txt b/include/boost/multi/adaptors/cuda/cublas/test/CMakeLists.txt new file mode 100644 index 000000000..341ea0f7c --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas/test/CMakeLists.txt @@ -0,0 +1,166 @@ +cmake_minimum_required(VERSION 3.17) # for CUDAToolkit + +find_package(Boost CONFIG) +#find_package(Boost REQUIRED COMPONENTS) # unit_test_framework) + +if((NOT + CMAKE_CXX_COMPILER_ID + STREQUAL + "PGI" + ) + AND (NOT + CMAKE_CXX_COMPILER_ID + STREQUAL + "NVHPC" + ) + AND (NOT + DART_COMPILER_NAME + STREQUAL + "nvcc" + ) + AND (NOT + DART_COMPILER_NAME + STREQUAL + "icpc" + ) +) + find_package(BLAS) + + if(BLAS_FOUND) + message(STATUS "BLAS library found.") + else() + message(WARNING "BLAS library not found. cuBLAS tests need BLAS.") + return() + endif() + + find_path( + BLAS_INCLUDE_DIRS + cblas.h + /usr/include + /usr/local/include + $ENV{BLAS_HOME}/include + ) +# include_directories(${TEST_EXE} PRIVATE ${BLAS_INCLUDE_DIRS}) + link_libraries(${BLAS_LIBRARIES}) +else() + link_libraries("-lblas") # cmake cannot detect BLAS with pgi/nvc++ but it ships with its own version +endif() + +# include_directories(${TEST_EXE} PRIVATE ${BLAS_INCLUDE_DIRS}) + +if(ENABLE_HIP) + enable_language(HIP) + find_package(hipBLAS REQUIRED) + find_package(rocthrust REQUIRED) +endif() + +if(ENABLE_CUDA OR DEFINED CXXCUDA) + enable_language(CUDA) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) + endif() + find_package(CUDAToolkit REQUIRED COMPONENTS cuBLAS) # requires cmake 3.17 +endif() + +enable_testing() +include(CTest) + +include_directories(${CMAKE_BINARY_DIR}) + +if(ENABLE_HIP) + add_executable(all.hip.x all.hip) + #set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE HIP) + target_link_libraries(all.hip.x PRIVATE multi roc::hipblas Boost::boost) + # target_compile_definitions(all.hip.x PRIVATE BOOST_TEST_DYN_LINK=1) + + add_test(NAME all.hip.x COMMAND ./all.hip.x) +endif() + +if(ENABLE_CUDA) +# find_package(CUDA REQUIRED) + +find_package(CUDAToolkit REQUIRED COMPONENTS cuBLAS) + +# file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) +set(TEST_SRCS + all.cu + axpy.cu + dot.cu + gemm.cu + gemv.cu + herk.cu + nrm2.cu + scal.cu + trsm.cu +) + +foreach(TEST_FILE ${TEST_SRCS}) + if(ENABLE_CUDA OR DEFINED CXXCUDA) + set(TEST_EXE "${TEST_FILE}.x") + add_executable(${TEST_EXE} ${TEST_FILE}) + + # target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") + target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) + + # target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_include_directories(${TEST_EXE} PRIVATE ${CUDA_INCLUDE_DIRS}) + target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) + + #target_compile_options(${TEST_EXE} PRIVATE --expt-relaxed-constexpr) + + # target_link_libraries(${TEST_EXE} PRIVATE Boost::unit_test_framework) + + target_link_libraries(${TEST_EXE} PRIVATE ${CUDA_LIBRARIES}) + target_link_libraries(${TEST_EXE} PRIVATE CUDA::cublas) + target_link_libraries(${TEST_EXE} PRIVATE multi) # Boost::unit_test_framework) + + # target_include_directories(${TEST_EXE} PRIVATE /opt/nvidia/hpc_sdk/Linux_x86_64/22.3/math_libs/include) + + # if(NOT ENABLE_CUDA) target_compile_options (${TEST_EXE} PRIVATE $<$: -Werror -Wall -Wextra -fno-common -Wpedantic -Wformat-truncation -fstack-usage>#-Wconversion + # $<$,$>: + # -Werror -Wall -Wextra -fno-common -Wpedantic -Wmove> $<$: -Werror -Wall -Wextra -fno-common -wd161 -diag-disable=remark -Warray-bounds -Wchar-subscripts -Wcomment -Wenum-compare -Wformat -Wuninitialized + # -Wmaybe-uninitialized -Wmain -Wnarrowing -Wnonnull -Wparentheses -Wpointer-sign -Wreorder -Wno-return-type -Wsign-compare -Wsequence-point -Wtrigraphs -Wunused-function -Wunused-but-set-variable -Wunused-variable -Wwrite-strings + # -Werror -diag-error:3846 > $<$: /W4>) endif() + + target_compile_options( + ${TEST_EXE} PRIVATE + $<$: + $<$: + -Xcompiler=-Wconversion + -Xcompiler=-Wsign-conversion + -Xcompiler=-Wfloat-conversion + -Xcompiler=-Wall + -Xcompiler=-Wextra + > + > + $<$: + $<$>: + /W4 # /volatile:iso /WX /Zc:wchar_t /Zc:forScope /Zc:inline + /wd4514 # unreferenced inline function has been removed + /wd4866 # compiler may not enforce left-to-right evaluation order for call to 'boost::multi::subarray >::operator[]' [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\test\array_ref.cpp.x.vcxproj] + /wd4868 # compiler may not enforce left-to-right evaluation order in braced initializer list + /wd5045 # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\include\boost\multi\adaptors\thrust\omp\test\omp.cpp.x.vcxproj] + /wd5031 # likely mismatch, popping warning state pushed in different file + > + $<$: + -Xcompiler=/W4 # /volatile:iso /WX /Zc:wchar_t /Zc:forScope /Zc:inline /W4 + -Xcompiler=/wd4668 # '__CUDA_ARCH__' is not defined as a preprocessor macro, TODO(correaa) move to Thrust target + # -Xcompiler=/wd4464 # relative include path contains '..' + # -Xcompiler=/wd4514 # unreferenced inline function has been removed + # -Xcompiler=/wd4866 # compiler may not enforce left-to-right evaluation order for call + # -Xcompiler=/wd4868 # compiler may not enforce left-to-right evaluation order in braced initializer list + # -Xcompiler=/wd5045 # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\include\boost\multi\adaptors\thrust\omp\test\omp.cpp.x.vcxproj] + > + > + ) + + set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) + # target_compile_options(${TEST_EXE} PRIVATE -extended-lambda) + + add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) + endif() +endforeach() + +target_compile_options(all.cu.x PRIVATE $<$:--extended-lambda>) + +endif() diff --git a/include/boost/multi/adaptors/cuda/cublas/test/all.cu b/include/boost/multi/adaptors/cuda/cublas/test/all.cu new file mode 100644 index 000000000..0f8618b13 --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas/test/all.cu @@ -0,0 +1,2167 @@ +// Copyright 2023-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace multi = boost::multi; + +using complex = thrust::complex; + +template> +auto generate_ABx() { + complex const I{0.0, 1.0}; + multi::array x = {1.0 + I * 0.0, 2.0 + I * 0.0, 3.0 + I * 0.0, 4.0 + I * 0.0}; + + multi::array A = { + { 1.0 + I * 0.0, 2.0 + I * 0.0, 3.0 + I * 0.0, 4.0 + I * 0.0}, + { 5.0 + I * 0.0, 6.0 + I * 0.0, 7.0 + I * 0.0, 8.0 + I * 0.0}, + { 9.0 + I * 0.0, 10.0 + I * 0.0, 11.0 + I * 0.0, 12.0 + I * 0.0}, + {13.0 + I * 0.0, 14.0 + I * 0.0, 15.0 + I * 0.0, 16.0 + I * 0.0}, + }; + + multi::array B = { + { 1.0 + I * 0.0, 2.0 + I * 0.0, 3.0 + I * 0.0, 4.0 + I * 0.0}, + { 5.0 + I * 0.0, 6.0 + I * 0.0, 7.0 + I * 0.0, 8.0 + I * 0.0}, + { 9.0 + I * 0.0, 10.0 + I * 0.0, 11.0 + I * 0.0, 12.0 + I * 0.0}, + {13.0 + I * 0.0, 14.0 + I * 0.0, 15.0 + I * 0.0, 16.0 + I * 0.0}, + }; + + return std::make_tuple(std::move(x), std::move(A), std::move(B)); +} + +// #define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +// #define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) + +int main() { + // BOOST_AUTO_TEST_CASE(cublas_scal_complex_column) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + { + using T = complex; + auto [x, A, B] = generate_ABx>(); + auto const s = 2.0 + I * 3.0; + blas::scal(s, x); // x_i <- s*x_i + + { + auto [x2, A2, B2] = generate_ABx>(); + auto xx = +x2; + blas::scal(s, xx); + BOOST_TEST(xx == x); + } + { + auto [x2, A2, B2] = generate_ABx>(); + using blas::operators::operator*=; + x2 *= s; + BOOST_TEST(x == x2); + } + { + auto [x2, A2, B2] = generate_ABx>(); + thrust::transform(x2.begin(), x2.end(), x2.begin(), [s] __device__(T & e) { return s * e; }); + + BOOST_TEST(x == x2); + } + { + auto [x2, A2, B2] = generate_ABx>(); + thrust::for_each(x2.begin(), x2.end(), [s] __device__(T & e) { return e *= s; }); + + BOOST_TEST(x == x2); + } + } + } + + // BOOST_AUTO_TEST_CASE(cublas_copy_complex) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = {1.0 + I * 8.0, 2.0 + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + multi::array y = {1.0 + I * 9.0, 2.0 + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + + blas::copy(x, y); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); + { + thrust::copy(begin(x), end(x), begin(y)); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); + } + { + blas::copy_n(x.begin(), x.size(), y.begin()); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); + } + { + y() = blas::copy(x); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); + } + { + multi::array yy = blas::copy(x); + BOOST_TEST( static_cast(yy[0]) == 1.0 + I*8.0 ); + } + { + y = blas::copy(x); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); + } + { + { + using blas::operators::operator<<; + y << x; + // BOOST_TEST(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< + } + BOOST_TEST(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< + } + } + +#if 1 + // BOOST_AUTO_TEST_CASE(cublas_swap_complex) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array x = {1.0 + I * 8.0, 2.0 + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + multi::array y = {1.0 + I * 9.0, 2.0 + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + + blas::swap(x, y); + BOOST_TEST( static_cast(x[0]) == 1.0 + I*9.0 ); + { + thrust::swap_ranges(begin(x), end(x), begin(y)); + thrust::swap_ranges(begin(x), end(x), begin(y)); + BOOST_TEST( static_cast(x[0]) == 1.0 + I*9.0 ); + } + { + using blas::operator^; + (x ^ y); + (x ^ y); + BOOST_TEST( static_cast(x[0]) == 1.0 + I*9.0 ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_asum_complex_column) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = {1.0 + I * 8.0, 2.0 + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + + double res; + blas::asum_n(x.begin(), x.size(), &res); + { + double res2; + res2 = blas::asum(x); + BOOST_TEST( res == res2 ); + } + { + double res2 = blas::asum(x); + BOOST_TEST( res == res2 ); + } + { + auto res2 = std::transform_reduce( + x.begin(), x.end(), double{}, std::plus<>{}, [](T const& e) { return std::abs(e.real()) + std::abs(e.imag()); } + ); + BOOST_TEST( res == res2 ); + } + { + auto res2 = thrust::transform_reduce( + x.begin(), x.end(), + [] __host__ __device__(T const& e) { return std::abs(e.real()) + std::abs(e.imag()); }, + double{}, thrust::plus<>{} + ); + BOOST_TEST( res == res2 ); + } + { + multi::static_array> res2({}, 0.0); + res2.assign(&blas::asum(x)); + res2 = blas::asum(x); + BOOST_TEST(( res == static_cast>::element_ref>(res2) )); + BOOST_TEST(( res == static_cast(res2) )); + // BOOST_TEST( res == res2 ); + } + { + multi::array> res2{blas::asum(x)}; + BOOST_TEST(( res == static_cast>::element_ref>(res2) )); + BOOST_TEST(( res == static_cast(res2) )); + // BOOST_TEST( res == res2 ); + } + { + multi::array> res2 = blas::asum(x); + BOOST_TEST(( res == static_cast>::element_ref>(res2) )); + BOOST_TEST(( res == static_cast(res2) )); + // BOOST_TEST( res == res2 ); + } + { + using blas::operators::operator==; + using blas::operators::operator!=; + BOOST_TEST( x != 0 ); + BOOST_TEST( not (x == 0) ); + } + { + using blas::operators::contains_nan; + BOOST_TEST( not contains_nan(x) ); + } + { + using blas::operators::isfinite; + using blas::operators::isinf; + BOOST_TEST( isfinite(x) ); + BOOST_TEST( not isinf(x) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_asum_complex_nans) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = {1.0 + I * 8.0, std::numeric_limits::quiet_NaN() + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + + { + using blas::operators::contains_nan; + BOOST_TEST( contains_nan(x) ); + } + { + using blas::operators::operator==; + using blas::operators::operator!=; + BOOST_TEST( not (x != 0) ); + BOOST_TEST( not (x == 0) ); + } + { + using blas::operators::isfinite; + using blas::operators::isinf; + BOOST_TEST( not isfinite(x) ); + BOOST_TEST( not isinf(x) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_asum_complex_inf) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = {1.0 + I * 8.0, std::numeric_limits::infinity() + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + + // double res; + { + using blas::operators::contains_nan; + BOOST_TEST( not contains_nan(x) ); + } + { + using blas::operators::operator==; + using blas::operators::operator!=; + BOOST_TEST( (x != 0) ); + BOOST_TEST( not (x == 0) ); + } + { + using blas::operators::isfinite; + using blas::operators::isinf; + BOOST_TEST( not isfinite(x) ); + BOOST_TEST( isinf(x) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_nrm2_complex_column) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = {1.0 + I * 8.0, 2.0 + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + + double res; + blas::nrm2(x, res); + { + double res2; + res2 = blas::nrm2(x); + BOOST_TEST( res == res2 ); + } + { + auto res2 = +blas::nrm2(x); + BOOST_TEST( res == res2 ); + } + { + auto res2 = sqrt(thrust::transform_reduce(x.begin(), x.end(), [] __host__ __device__(T const& e) { return thrust::norm(e); }, double{}, thrust::plus<>{})); + BOOST_TEST( res == res2 ); + } + { + multi::array> res2{blas::nrm2(x)}; + BOOST_TEST(( res == static_cast(res2) )); + } + { + multi::array> res2 = blas::nrm2(x); + BOOST_TEST(( res == static_cast(res2) )); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_dot_complex_column) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = {1.0 + I * 8.0, 2.0 + I * 6.0, 3.0 + I * 5.0, 4.0 + I * 3.0}; + multi::array const y = {1.0 + I * 2.0, 2.0 + I * 3.0, 3.0 + I * 5.0, 4.0 + I * 7.0}; + + { + T res; + blas::dot(x, y, res); + { + complex res2; + res2 = blas::dot(x, y); + BOOST_TEST(res == res2); + } + { + multi::array res2(complex{1.0, 0.0}); + res2 = blas::dot(x, y); + BOOST_TEST( static_cast(res2) == res ); + } + { + using blas::operators::operator, ; + auto res2 = +(x, y); + BOOST_TEST(res == res2); + } + { + auto res2 = +blas::dot(x, y); + BOOST_TEST(res == res2); + } + { + // auto [x2, A2, B2] = generate_ABx >(); + // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); + auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}); + BOOST_TEST(res == res2); + } + } + { + T res; + blas::dot(blas::C(x), y, res); + { + using blas::operators::operator, ; + using blas::operators::operator*; + auto res2 = +(*x, y); + BOOST_TEST(res == res2); + } + { + auto res2 = +blas::dot(blas::C(x), y); + BOOST_TEST(res == res2); + } + { + // auto [x2, A2, B2] = generate_ABx >(); + // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); + auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__(T const& t1, T const& t2) { return conj(t1) * t2; }); + BOOST_TEST(res == res2); + } + } + { + T res; + blas::dot(x, blas::C(y), res); + { + using blas::operators::operator, ; + auto res2 = +(x, blas::C(y)); + BOOST_TEST(res == res2); + } + { + auto res2 = +blas::dot(x, blas::C(y)); + BOOST_TEST(res == res2); + } + { + // auto [x2, A2, B2] = generate_ABx >(); + // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); + auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__(T const& t1, T const& t2) { return t1 * conj(t2); }); + BOOST_TEST(res == res2); + } + { + BOOST_TEST( blas::dot(blas::C(x), x) == pow(blas::nrm2(x), 2.0) ); + BOOST_TEST( blas::dot(x, blas::C(x)) == pow(blas::nrm2(x), 2.0) ); + + using blas::operators::operator, ; + using blas::operators::operator*; + using blas::operators::abs; + using blas::operators::norm; + using blas::operators::operator^; + + BOOST_TEST( (*x, x) == pow(abs(x), 2.0) ); + BOOST_TEST( (*x, x) == pow(abs(x), 2) ); + BOOST_TEST( (*x, x) == norm(x) ); + + BOOST_TEST( (x, *x) == pow(abs(x), 2.0) ); + BOOST_TEST( (x, *x) == pow(abs(x), 2) ); + BOOST_TEST( (x, *x) == norm(x) ); + + BOOST_TEST( (*x, x) == (x^2) ); + } + } + { + // T res; + // blas::dot(blas::C(x), blas::C(y), res); + // multi::array res({1, 1}, 0.0); + // auto rr = blas::gemm(1.0, x.partitioned(1), blas::H(y.partitioned(1)), 0.0, res)[0][0]; + // { + // using blas::operators::operator,; + // auto res2 = +(x, blas::C(y)); + // BOOST_TEST(res == res2); + // } + // { + // auto res2 = +blas::dot(x, blas::C(y)); + // BOOST_TEST(res == res2); + // } + // { + // // auto [x2, A2, B2] = generate_ABx >(); + // // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); + // auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__ (T const& t1, T const& t2) {return t1*conj(t2);}); + // BOOST_TEST(res == res2); + // } + // { + // BOOST_TEST( blas::dot(blas::C(x), x) == pow(blas::nrm2(x), 2.0) ); + // BOOST_TEST( blas::dot(x, blas::C(x)) == pow(blas::nrm2(x), 2.0) ); + + // using blas::operators::operator,; + // using blas::operators::operator*; + // using blas::operators::abs; + // using blas::operators::norm; + // using blas::operators::operator^; + + // BOOST_TEST( (*x, x) == pow(abs(x), 2.0) ); + // BOOST_TEST( (*x, x) == pow(abs(x), 2) ); + // BOOST_TEST( (*x, x) == norm(x) ); + + // BOOST_TEST( (x, *x) == pow(abs(x), 2.0) ); + // BOOST_TEST( (x, *x) == pow(abs(x), 2) ); + // BOOST_TEST( (x, *x) == norm(x) ); + + // BOOST_TEST( (*x, x) == (x^2) ); + // } + } + } + + // BOOST_AUTO_TEST_CASE(cublas_axpy_complex_one) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0}, + {4.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + multi::array y = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + + blas::axpy(1.0, x, y); + std::cout << y[0] << std::endl; + BOOST_TEST( static_cast(y[0]) == 3.2 + I*0.0 ); + { + multi::array yy = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; + + thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [] __device__(auto const& ex, auto const& ey) { return ex + ey; }); + BOOST_TEST( yy == y ); // , boost::test_tools::per_element() ); + } + { + multi::array yy = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; + using blas::operators::operator+=; + yy += x; + BOOST_TEST( yy == y ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_axpy_complex_mone) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0}, + {4.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + multi::array y = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + + blas::axpy(-1.0, x, y); + std::cout << y[0] << std::endl; + BOOST_TEST( static_cast(y[0]) == 1.0 + I*0.0 ); + { + multi::array yy = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [] __host__ __device__(T ex, T ey) { return -1.0 * ex + ey; }); + BOOST_TEST( yy == y ); // boost::test_tools::per_element() ); + } + { + multi::array yy = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; + using blas::operators::operator-=; + yy -= x; + BOOST_TEST( yy == y ); + } + { + multi::array yy = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; + using blas::operators::operator-=; + yy -= x; + yy -= y; + using blas::operators::norm; + BOOST_TEST( norm(yy) == 0 ); + using blas::operators::operator==; + BOOST_TEST( operator==(yy, 0) ); + BOOST_TEST( yy == 0 ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_axpy_complex_alpha) + { + namespace blas = multi::blas; + complex const I{0.0, 1.0}; + + using T = complex; + using Alloc = thrust::cuda::allocator; + + multi::array const x = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0}, + {4.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + multi::array y = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + + blas::axpy(3.0, x, y); + std::cout << y[0] << std::endl; + BOOST_TEST( static_cast(y[0]) == 5.4 + I*0.0 ); + // { + // multi::array yy = { + // { 2.1, 0.0}, + // { 4.1, 0.0}, + // { 6.1, 0.0}, + // {11.0, 0.0} + // }; // NOLINT(readability-identifier-length) BLAS naming + // thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [aa = 3.0] __device__(T ex, T ey) { return aa * ex + ey; }); + // BOOST_TEST( yy == y , boost::test_tools::per_element() ); + // } + { + multi::array yy = { + { 2.1, 0.0}, + { 4.1, 0.0}, + { 6.1, 0.0}, + {11.0, 0.0} + }; + using blas::operators::operator+=; + using blas::operators::operator*; + yy += 3.0 * x; + BOOST_TEST( yy == y ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemv_conj_complex_zero) + { + namespace blas = multi::blas; + using T = complex; + complex const I{0.0, 1.0}; + using Alloc = thrust::cuda::allocator; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + { {9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0}}, + { {4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0}}, + {{14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0}}, + }; + multi::array const x = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0}, + {4.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + multi::array y = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + blas::gemv(1.0, A, x, 0.0, y); + { + + multi::array yy = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + std::transform(begin(A), end(A), begin(yy), [&x](auto const& Ac) { return blas::dot(Ac, x); }); + + BOOST_TEST( std::abs(static_cast(y[0]).real() - static_cast(yy[0]).real()) < 1e-10 ); + BOOST_TEST( std::abs(static_cast(y[1]).imag() - static_cast(yy[1]).imag()) < 1e-10 ); + BOOST_TEST( std::abs(static_cast(y[2]).real() - static_cast(yy[2]).real()) < 1e-10 ); + } + { + multi::array yy = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + yy = blas::gemv(1.0, A, x); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + multi::array yy = blas::gemv(1.0, A, x); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + using blas::operators::operator%; + + multi::array yy = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + yy = A % x; + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_conj_zero) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + { 9.0 + I * 0.0, 24.0 + I * 0.0, 30.0 + I * 0.0, 9.0 + I * 0.0}, + { 4.0 + I * 0.0, 10.0 + I * 0.0, 12.0 + I * 0.0, 7.0 + I * 0.0}, + {14.0 + I * 0.0, 16.0 + I * 0.0, 36.0 + I * 0.0, 1.0 + I * 0.0}, + }; + multi::array const x = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array y = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + blas::gemv(1.0, blas::T(A), x, 0.0, y); + { + multi::array yy = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + using blas::operators::operator*; + + std::transform(begin(A.transposed()), end(A.transposed()), begin(yy), [&x](auto const& Ac) { return blas::dot(Ac, x); }); + + BOOST_TEST( std::abs( static_cast(y[0]).real() - static_cast(yy[0]).real()) < 1e-7); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + multi::array yy = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + yy = blas::gemv(1.0, blas::T(A), x); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + multi::array yy = blas::gemv(1.0, blas::T(A), x); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + using blas::operators::operator%; + + multi::array yy = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + yy = ~A % x; + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_zero) + { + namespace blas = multi::blas; + using T = complex; + complex const I{0.0, 1.0}; + using Alloc = thrust::cuda::allocator; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + { {9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0}}, + { {4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0}}, + {{14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0}}, + }; + multi::array const x = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0}, + {4.1, 0.0} + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array y = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0} + }; + blas::gemv(1.0, blas::J(A), x, 0.0, y); + { + multi::array yy = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0} + }; + std::transform(begin(A), end(A), begin(yy), [&x](auto const& Ac) { + using blas::operators::operator*; // nvcc 11.8 needs this to be inside lambda + return blas::dot(*Ac, x); }); + + BOOST_TEST( abs( static_cast(y[0]) - static_cast(yy[0])) < 1e-7 ); + BOOST_TEST( abs( static_cast(y[1]) - static_cast(yy[1])) < 1e-7 ); + BOOST_TEST( abs( static_cast(y[2]) - static_cast(yy[2])) < 1e-7 ); + } + { + multi::array yy = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0} + }; + yy = blas::gemv(1.0, blas::J(A), x); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + multi::array yy = blas::gemv(1.0, blas::J(A), x); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + using blas::operators::operator%; + using blas::operators::operator*; + + multi::array yy = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0} + }; // NOLINT(readability-identifier-length) BLAS naming + yy = *A % x; + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_conjtrans_zero) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = std::allocator; // thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + { 9.0 + I * 0.0, 24.0 + I * 0.0, 30.0 + I * 0.0, 9.0 + I * 0.0}, + { 4.0 + I * 0.0, 10.0 + I * 0.0, 12.0 + I * 0.0, 7.0 + I * 0.0}, + {14.0 + I * 0.0, 16.0 + I * 0.0, 36.0 + I * 0.0, 1.0 + I * 0.0}, + }; + multi::array const x = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array y = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + + // blas::gemv(1.0, blas::H(A), x, 0.0, y); + + { +// TODO(correaa) MKL gives an error here +#if 0 + multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming + std::transform(begin(transposed(A)), end(transposed(A)), begin(yy), [&x] (auto const& Ac) { + using blas::operators::operator*; // nvcc 11.8 needs this to be inside lambda + return blas::dot(*Ac, x);} + ); + + BOOST_TEST( std::abs(static_cast(yy[0]).real() - 61.7) < 1.e-7 ); + BOOST_TEST( std::abs(static_cast(yy[1]).real() - 97.0) < 1.e-7 ); + BOOST_TEST( std::abs(static_cast(yy[2]).real() - 169.8) < 1.e-7 ); + BOOST_TEST( std::abs(static_cast(yy[3]).real() - 27.7) < 1.e-7 ); + + using blas::operators::operator*; + BOOST_TEST( std::abs( static_cast(yy[0]).real() - (+blas::dot(*(~A)[0], x)).real()) < 1.e-7 ); + BOOST_TEST( std::abs( static_cast(yy[1]).real() - (+blas::dot(*(~A)[1], x)).real()) < 1.e-7 ); +#endif + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_trans_one) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + { 9.0 + I * 0.0, 24.0 + I * 0.0, 30.0 + I * 0.0, 9.0 + I * 0.0}, + { 4.0 + I * 0.0, 10.0 + I * 0.0, 12.0 + I * 0.0, 7.0 + I * 0.0}, + {14.0 + I * 0.0, 16.0 + I * 0.0, 36.0 + I * 0.0, 1.0 + I * 0.0}, + }; + multi::array const x = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + multi::array y = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + blas::gemv(3.0 + I * 4.0, blas::T(A), x, 1.0, y); + { + multi::array yy = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + // using blas::operators::operator*; + std::transform(begin(transposed(A)), end(transposed(A)), begin(yy), begin(yy), [&x, aa = 3.0 + I * 4.0, bb = 1.0](auto const& Ac, complex e) { return aa * blas::dot(Ac, x) + bb * e; }); + + BOOST_TEST( std::abs( static_cast(y[0]).real() - static_cast(yy[0]).real()) < 1e-7); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + multi::array yy = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + yy += blas::gemv(3.0 + I * 4.0, blas::T(A), x); + + BOOST_TEST( std::abs( static_cast(y[0]).real() - static_cast(yy[0]).real()) < 1e-7 ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + { + multi::array yy = {1.1 + I * 0.0, 2.1 + I * 0.0, 3.1 + I * 0.0, 6.7 + I * 0.0}; // NOLINT(readability-identifier-length) BLAS naming + using blas::operators::operator*; + yy += (3.0 + I * 4.0) * ~A % x; + + BOOST_TEST( std::abs(static_cast(y[0]).real() - static_cast(yy[0]).real()) < 1e-7 ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_none) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + blas::gemm({1.0, 0.0}, A, B, {0.0, 0.0}, C); + + // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), + // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} + // ); + std::transform(begin(A), end(A), begin(C_copy), end(C_copy), [&B, aa = 1.0, bb = 0](auto const& Ar, auto&& Cr) { return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + C = blas::gemm(1.0 + I * 0.0, A, B); + + // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), + // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} + // ); + std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa = 1.0, bb = 0.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + C += blas::gemm(1.0 + I * 0.0, A, B); + + std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), [&A, aa = 1.0, bb = 1.0](auto const& Bc, auto&& Cc) { return blas::gemv(aa, A, Bc, bb, std::move(Cc)); }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + using blas::operators::operator*; + using blas::operators::operator+=; + C += A * B; + + std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa = 1.0, bb = 1.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_second) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + blas::gemm({1.0, 0.0}, A, blas::T(B), {0.0, 0.0}, C); + + std::transform(begin(B), end(B), begin(transposed(C_copy)), begin(transposed(C_copy)), [&A, aa = 1.0, bb = 0.0](auto const& Bc, auto&& Cc) { return blas::gemv(aa, A, Bc, bb, std::move(Cc)); }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + C = blas::gemm(1.0 + I * 0.0, A, blas::T(B)); + + // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), + // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} + // ); + std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa = 1.0, bb = 0.0](auto const& Ac, auto&& Cr) { + return blas::gemv(aa, B, Ac, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + C += blas::gemm(1.0 + I * 0.0, A, blas::T(B)); + + std::transform(begin(B), end(B), begin(transposed(C_copy)), begin(transposed(C_copy)), [&A, aa = 1.0, bb = 1.0](auto const& Bc, auto&& Cc) { return blas::gemv(aa, A, Bc, bb, std::move(Cc)); }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + using blas::operators::operator*; + using blas::operators::operator+=; + C += A * ~B; + + std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa = 1.0, bb = 1.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, B, Ar, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + using blas::operators::operator*; + using blas::operators::operator+=; + C += 2.0 * (A * ~B); + + std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa = 2.0, bb = 1.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, B, Ar, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_first) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + blas::gemm({1.0, 0.0}, blas::T(A), B, {0.0, 0.0}, C); + + std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), [&A, aa = 1.0, bb = 0.0](auto const& Bc, auto&& Cc) { return blas::gemv(aa, blas::T(A), Bc, bb, std::move(Cc)); }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + C = blas::gemm(1.0 + I * 0.0, blas::T(A), B); + + // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), + // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} + // ); + std::transform(begin(transposed(A)), end(transposed(A)), begin(C_copy), begin(C_copy), [&B, aa = 1.0, bb = 0.0](auto const& Ac, auto&& Cr) { + return blas::gemv(aa, blas::T(B), Ac, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + C += blas::gemm(1.0 + I * 0.0, blas::T(A), B); + + std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), [&A, aa = 1.0, bb = 1.0](auto const& Bc, auto&& Cc) { return blas::gemv(aa, blas::T(A), Bc, bb, std::move(Cc)); }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + using blas::operators::operator*; + using blas::operators::operator+=; + C += ~A * B; + + std::transform(begin(transposed(A)), end(transposed(A)), begin(C_copy), begin(C_copy), [&B, aa = 1.0, bb = 1.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + using blas::operators::operator*; + using blas::operators::operator+=; + C += 2.0 * (~A * B); + + std::transform(begin(A.transposed()), end(A.transposed()), begin(C_copy), begin(C_copy), [&B, aa = 2.0, bb = 1.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_both) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + blas::gemm({1.0, 0.0}, blas::T(A), blas::T(B), {0.0, 0.0}, C); + + std::transform(B.begin(), B.end(), C_copy.transposed().begin(), C_copy.transposed().begin(), [&A, aa = 1.0, bb = 0.0](auto const& Br, auto&& Cc) { return blas::gemv(aa, blas::T(A), Br, bb, std::move(Cc)); }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + C = blas::gemm(1.0 + I * 0.0, blas::T(A), blas::T(B)); + + // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), + // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} + // ); + std::transform(A.transposed().begin(), A.transposed().end(), C_copy.begin(), C_copy.begin(), [&B, aa = 1.0, bb = 0.0](auto const& Ac, auto&& Cr) { + return blas::gemv(aa, B, Ac, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + C += blas::gemm(1.0 + I * 0.0, blas::T(A), blas::T(B)); + + std::transform(begin(B), end(B), C_copy.transposed().begin(), C_copy.transposed().begin(), [&A, aa = 1.0, bb = 1.0](auto const& Br, auto&& Cc) { return blas::gemv(aa, blas::T(A), Br, bb, std::move(Cc)); }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + using blas::operators::operator*; + using blas::operators::operator+=; + C += ~A * ~B; + + std::transform(A.transposed().begin(), A.transposed().end(), C_copy.begin(), C_copy.begin(), [&B, aa = 1.0, bb = 1.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, B, Ar, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto C_copy = C; + using blas::operators::operator*; + using blas::operators::operator+=; + C += 2.0 * (~A * ~B); + + std::transform(A.transposed().begin(), A.transposed().end(), begin(C_copy), begin(C_copy), [&B, aa = 2.0, bb = 1.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, B, Ar, bb, std::move(Cr)); + }); + + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_second) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = std::allocator; // thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto CC = C; + auto C_copy = CC; + // blas::gemm({1.0, 0.0}, A, blas::J(B), {0.0, 0.0}, C); + blas::gemm({1.0, 0.0}, blas::T(B), blas::H(A), {0.0, 0.0}, C_copy); + { + auto const [is, js] = C.extensions(); + for(auto i : is) { + for(auto j : js) { + C[i][j] *= 0.0; + for(auto k : B.extension()) { + C[i][j] += A[i][k] * conj(B[k][j]); + } + } + } + } +// TODO(correaa) MKL gives an error here +// unknown location(0): fatal error: in "cublas_one_gemv_complex_conjtrans_zero": memory access violation at address: 0x00000007: no mapping at fault address +#if 0 + { + std::transform(begin(A), end(A), begin(CC), begin(CC), [BT = transposed(B)](auto const& Ar, auto&& Cr) { + return std::transform( + begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto&& Ce) { + return 1.0*blas::dot(Ar, blas::C(Bc)) + 0.0*Ce; + } + ), std::move(Cr); + }); + } + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + + BOOST_TEST( static_cast(C_copy[1][0]).real() == +static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(C_copy[1][0]).imag() == -static_cast(C[0][1]).imag() ); +#endif + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_first) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = std::allocator; // thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto CC = C; + auto C_copy = CC; + // blas::gemm({1.0, 0.0}, blas::J(A), B, {0.0, 0.0}, C); + // blas::gemm({1.0, 0.0}, blas::T(B), blas::H(A), {0.0, 0.0}, C_copy); + // { + // auto const [is, js] = C.extensions(); + // for(auto i : is) { + // for(auto j : js) { + // C[i][j] *= 0.0; + // for(auto k : B.extension()) { + // C[i][j] += A[i][k]*conj(B[k][j]); + // } + // } + // } + // } + // { + // std::transform(begin(A), end(A), begin(CC), begin(CC), [BT = transposed(B)](auto const& Ar, auto&& Cr) { + // return std::transform( + // begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& BCr, auto&& Ce) { + // return 1.0*blas::dot(Ar, blas::C(BCr)) + 0.0*Ce; + // } + // ), std::move(Cr); + // }); + // } + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + + // BOOST_TEST( static_cast(C_copy[1][0]).real() == +static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(C_copy[1][0]).imag() == -static_cast(C[0][1]).imag() ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_both) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = std::allocator; // thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + { + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + auto CC = C; + auto C_copy = CC; + // blas::gemm({1.0, 0.0}, blas::J(A), blas::J(B), {0.0, 0.0}, C); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_second) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm({1.0, 0.0}, A, blas::H(B), {0.0, 0.0}, C); + { + multi::array CC({2, 2}, {3.0, 0.0}); + + std::transform( + begin(A), end(A), begin(CC), begin(CC), + [&B, aa = 1.0, bb = 0.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, blas::J(B), Ar, bb, std::move(Cr)); + } + ); + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + { + multi::array CC({2, 2}, {3.0, 0.0}); + CC = blas::gemm({1.0, 0.0}, A, blas::H(B)); + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + { + multi::array CC({2, 2}, {3.0, 0.0}); + using blas::operators::operator*; + using blas::operators::operator~; + CC = A * ~*B; + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_second_plus) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm({1.0, 0.0}, A, blas::H(B), {1.0, 0.0}, C); + { + multi::array CC({2, 2}, {3.0, 0.0}); + + std::transform( + begin(A), end(A), begin(CC), begin(CC), + [&B, aa = 1.0, bb = 1.0](auto const& Ar, auto&& Cr) { + return blas::gemv(aa, blas::J(B), Ar, bb, std::move(Cr)); + } + ); + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + { + multi::array CC({2, 2}, {3.0, 0.0}); + CC += blas::gemm({1.0, 0.0}, A, blas::H(B)); + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + { + multi::array CC({2, 2}, {3.0, 0.0}); + using blas::operators::operator*; + using blas::operators::operator~; + CC += A * ~*B; + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_first) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm({1.0, 0.0}, blas::H(A), B, {0.0, 0.0}, C); + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // auto const [is, js] = CC.extensions(); + // for(auto i : is) { + // for(auto j : js) { + // CC[i][j] = 0.0; + // for(auto k : A.extension()) { + // CC[i][j] += 1.0*conj(A[k][i])*B[k][j] ; + // } + // } + // } + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + { + multi::array CC({2, 2}, {3.0, 0.0}); + + std::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BT = transposed(B)](auto const& Ac, auto&& Cr) { + std::transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c) { + return blas::dot(blas::C(Ac), Bc, std::move(c)); + }); + return std::move(Cr); + }); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + { + multi::array CC({2, 2}, {3.0, 0.0}); + CC = blas::gemm({1.0, 0.0}, blas::H(A), B); + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + { + multi::array CC({2, 2}, {3.0, 0.0}); + using blas::operators::operator*; + using blas::operators::operator~; + CC = ~*A * B; + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_both) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm({1.0, 0.0}, blas::H(A), blas::H(B), {0.0, 0.0}, C); + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // auto const [is, js] = CC.extensions(); + // for(auto i : is) { + // for(auto j : js) { + // CC[i][j] = 0.0; + // for(auto k : A.extension()) { + // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; + // } + // } + // } + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + + // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { + // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { + // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); + // // return conj(+blas::dot(Ac, Bc, std::move(c))); + // }); + // return std::move(Cr); + // }); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + { + multi::array CC({2, 2}, {3.0, 0.0}); + + CC = blas::gemm({1.0, 0.0}, blas::H(A), blas::H(B)); + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + { + multi::array CC({2, 2}, {3.0, 0.0}); + using blas::operators::operator*; + using blas::operators::operator~; + CC = ~*A * ~*B; + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_herm) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B), {0.0, 0.0}, C); + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // auto const [is, js] = CC.extensions(); + // for(auto i : is) { + // for(auto j : js) { + // CC[i][j] = 0.0; + // for(auto k : A.extension()) { + // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; + // } + // } + // } + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + + // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { + // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { + // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); + // // return conj(+blas::dot(Ac, Bc, std::move(c))); + // }); + // return std::move(Cr); + // }); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + { + multi::array CC({2, 2}, {3.0, 0.0}); + + CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + { + multi::array CC({2, 2}, {3.0, 0.0}); + using blas::operators::operator*; + using blas::operators::operator~; + CC = ~A * ~*B; + + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_trans) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + + // blas::gemm({1.0, 0.0}, blas::H(A), blas::T(B), {0.0, 0.0}, C); + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // auto const [is, js] = CC.extensions(); + // for(auto i : is) { + // for(auto j : js) { + // CC[i][j] = 0.0; + // for(auto k : A.extension()) { + // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; + // } + // } + // } + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + + // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { + // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { + // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); + // // return conj(+blas::dot(Ac, Bc, std::move(c))); + // }); + // return std::move(Cr); + // }); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + + // CC = blas::gemm({1.0, 0.0}, blas::H(A), blas::T(B)); + + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // using blas::operators::operator*; + // using blas::operators::operator~; + // CC = ~*A * ~B; + + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_herm) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + + // blas::gemm({1.0, 0.0}, blas::J(A), blas::H(B), {0.0, 0.0}, C); + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // auto const [is, js] = CC.extensions(); + // for(auto i : is) { + // for(auto j : js) { + // CC[i][j] = 0.0; + // for(auto k : A.extension()) { + // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; + // } + // } + // } + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + + // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { + // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { + // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); + // // return conj(+blas::dot(Ac, Bc, std::move(c))); + // }); + // return std::move(Cr); + // }); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + + // CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); + + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // using blas::operators::operator*; + // using blas::operators::operator~; + // CC = ~A * ~*B; + + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + } + + // BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_conj) + { + namespace blas = multi::blas; + using T = complex; + using Alloc = thrust::cuda::allocator; + complex const I{0.0, 1.0}; + + // NOLINT(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + + multi::array const B = { + {3.0 - 4.0 * I, 19.0 - 1.0 * I}, + {1.0 + 5.0 * I, 8.0 - 8.0 * I}, + }; + multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + // blas::gemm({1.0, 0.0}, blas::H(A), blas::J(B), {0.0, 0.0}, C); + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // auto const [is, js] = CC.extensions(); + // for(auto i : is) { + // for(auto j : js) { + // CC[i][j] = 0.0; + // for(auto k : A.extension()) { + // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; + // } + // } + // } + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + + // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { + // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { + // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); + // // return conj(+blas::dot(Ac, Bc, std::move(c))); + // }); + // return std::move(Cr); + // }); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + + // CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); + + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + // { + // multi::array CC({2, 2}, {3.0, 0.0}); + // using blas::operators::operator*; + // using blas::operators::operator~; + // CC = ~A * ~*B; + + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // } + } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) + { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + using Alloc = thrust::cuda::allocator; + + multi::array const A = { + // NOLINT(readability-identifier-length) BLAS naming + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + multi::array B = { + // NOLINT(readability-identifier-length) BLAS naming + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + using multi::blas::trsm; + + blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(A), blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† + BOOST_TEST( std::abs( static_cast(B[1][2]).imag() - -0.147059) < 0.001); + } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const_UTH) + { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + using Alloc = thrust::cuda::allocator; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + { 1.0 + 4.0 * I, 0.0 + 0.0 * I, 0.0 - 0.0 * I}, + { 3.0 + 0.0 * I, 7.0 - 3.0 * I, 0.0 + 0.0 * I}, + {4.0 - 10.0 * I, 1.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0 + 1.0 * I, 2.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I}, + {3.0 + 1.0 * I, 1.0 - 1.0 * I}, + }; + + using multi::blas::trsm; + + blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(blas::H(A)), B); + BOOST_TEST( std::abs( static_cast(B[1][1]).imag() - -0.0811359 ) < 0.001 ); + BOOST_TEST( std::abs( static_cast(B[2][1]).imag() - -0.147059 ) < 0.001 ); + } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const) + { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + using Alloc = thrust::cuda::allocator; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0 + 1.0 * I, 2.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I}, + {3.0 + 1.0 * I, 1.0 - 1.0 * I}, + }; + + using multi::blas::trsm; + + blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(A), B); // B←A⁻¹.B, B†←A⁻¹.B† + BOOST_TEST( std::abs( static_cast(B[2][1]).imag() - -0.0882353) < 0.001 ); + } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_conj_second) { + // namespace blas = multi::blas; + // using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + // using Alloc = thrust::cuda::allocator; + + // // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + // multi::array const A = { + // { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, + // { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, + // { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, + // }; + + // // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + // multi::array B = { + // {1.0 + 1.0*I, 2.0 + 1.0*I}, + // {5.0 + 3.0*I, 9.0 + 3.0*I}, + // {3.0 + 1.0*I, 1.0 - 1.0*I}, + // }; + + // using multi::blas::trsm; + + // blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(A), blas::J(B)); // B*←A⁻¹.B*, B^T←A⁻¹.B^T + // BOOST_REQUIRE_CLOSE( static_cast(B[2][1]).imag() , -0.0882353, 0.001); + // } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_operator) + { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + using Alloc = thrust::cuda::universal_allocator; + + multi::array const A = { + // NOLINT(readability-identifier-length) BLAS naming + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + multi::array B = { + // NOLINT(readability-identifier-length) BLAS naming + {1.0 + 1.0 * I, 2.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I}, + {3.0 + 1.0 * I, 1.0 - 1.0 * I}, + }; + + #if !defined(_MSC_VER) + using blas::operators::operator|=; + using blas::operators::U; + B |= U(A); // B←A⁻¹.B, B†←A⁻¹.B† + BOOST_TEST( std::abs( static_cast(B[2][1]).imag() - -0.0882353) < 0.001 ); + #endif + } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_right) + { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + using Alloc = thrust::cuda::allocator; + + multi::array const A = { + // NOLINT(readability-identifier-length) BLAS naming + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + + multi::array B = { + // NOLINT(readability-identifier-length) BLAS naming + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + using multi::blas::trsm; + + blas::trsm(blas::side::right, {1.0, 0.0}, blas::U(A), B); // B←B.A⁻¹, B←B/A, B†←A⁻¹†.B† + BOOST_TEST( std::abs(static_cast(B[1][2]).imag() - 1.60142) < 0.001 ); + } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_right_LT) + { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + using Alloc = thrust::cuda::allocator; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + using multi::blas::trsm; + + blas::trsm(blas::side::right, {1.0, 0.0}, blas::L(blas::T(A)), B); // B←B.Aᵀ⁻¹, B←B/Aᵀ, B†←Aᵀ⁻¹†.B†, Bᵀ←A⁻¹.Bᵀ, Bᵀ←Bᵀ\A + BOOST_TEST( std::abs( static_cast(B[1][2]).imag() - -0.0882353) < 0.001 ); + } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_right_LH) { + // namespace blas = multi::blas; + // using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + // using Alloc = thrust::cuda::allocator; + + // // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + // multi::array const A = { + // { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, + // { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, + // { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, + // }; + + // // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + // multi::array B = { + // { 1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, + // { 5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, + // }; + + // using multi::blas::trsm; + + // blas::trsm(blas::side::right, {1.0, 0.0}, blas::U(blas::J(A)), B); // B←B.A*⁻¹, B←B/A*, B*←B*.A⁻¹ + // BOOST_REQUIRE_CLOSE( static_cast(B[1][2]).imag(), -0.0882353, 0.001); + // } + + // BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_right_operator) + { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + using Alloc = thrust::cuda::allocator; + + multi::array const A = { + // NOLINT(readability-identifier-length) BLAS naming + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + multi::array B = { + // NOLINT(readability-identifier-length) BLAS naming + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + using multi::blas::trsm; + + using blas::operators::operator/=; + B /= blas::U(A); + BOOST_TEST( std::abs( static_cast(B[1][2]).imag() - 1.60142) < 0.001 ); + } + +#endif + + return boost::report_errors(); +} diff --git a/include/multi/adaptors/cuda/cublas/test/all.hip b/include/boost/multi/adaptors/cuda/cublas/test/all.hip similarity index 69% rename from include/multi/adaptors/cuda/cublas/test/all.hip rename to include/boost/multi/adaptors/cuda/cublas/test/all.hip index 2b67abb71..4439206ac 100644 --- a/include/multi/adaptors/cuda/cublas/test/all.hip +++ b/include/boost/multi/adaptors/cuda/cublas/test/all.hip @@ -1,21 +1,24 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUBLAS all" -#include +// #include -#include +#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include #include @@ -49,6 +52,12 @@ auto generate_ABx() { return std::make_tuple(std::move(x), std::move(A), std::move(B)); } +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +#define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) + +int main() { BOOST_AUTO_TEST_CASE(cublas_scal_complex_column) { namespace blas = multi::blas; complex const I{0.0, 1.0}; @@ -63,25 +72,25 @@ BOOST_AUTO_TEST_CASE(cublas_scal_complex_column) { auto [x2, A2, B2] = generate_ABx >(); auto xx = +x2; blas::scal(s, xx); - BOOST_REQUIRE(xx == x); + BOOST_TEST(xx == x); } { auto [x2, A2, B2] = generate_ABx >(); using blas::operators::operator*=; x2 *= s; - BOOST_REQUIRE(x == x2); + BOOST_TEST(x == x2); } { auto [x2, A2, B2] = generate_ABx >(); thrust::transform(x2.begin(), x2.end(), x2.begin(), [s] __device__ (T& e) {return s*e;}); - BOOST_REQUIRE(x == x2); + BOOST_TEST(x == x2); } { auto [x2, A2, B2] = generate_ABx >(); thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); - BOOST_REQUIRE(x == x2); + BOOST_TEST(x == x2); } } } @@ -98,34 +107,34 @@ BOOST_AUTO_TEST_CASE(cublas_copy_complex_device) { multi::array y = { 1.0 + I*9.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; blas::copy(x, y); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); { thrust::copy(begin(x), end(x), begin(y)); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); } { blas::copy_n(x.begin(), x.size(), y.begin()); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); } { y() = blas::copy(x); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); } { multi::array yy = blas::copy(x); - BOOST_REQUIRE( static_cast(yy[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(yy[0]) == 1.0 + I*8.0 ); } { y = blas::copy(x); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); } { { using blas::operators::operator<<; y << x; - // BOOST_REQUIRE(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< + // BOOST_TEST(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< } - BOOST_REQUIRE(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< + BOOST_TEST(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< } } @@ -141,34 +150,34 @@ BOOST_AUTO_TEST_CASE(cublas_copy_complex) { multi::array y = { 1.0 + I*9.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; blas::copy(x, y); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); { thrust::copy(begin(x), end(x), begin(y)); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); } { blas::copy_n(x.begin(), x.size(), y.begin()); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); } { y() = blas::copy(x); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); } { multi::array yy = blas::copy(x); - BOOST_REQUIRE( static_cast(yy[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(yy[0]) == 1.0 + I*8.0 ); } { y = blas::copy(x); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*8.0 ); } { { using blas::operators::operator<<; y << x; - // BOOST_REQUIRE(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< + // BOOST_TEST(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< } - BOOST_REQUIRE(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< + BOOST_TEST(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< } } @@ -184,17 +193,17 @@ BOOST_AUTO_TEST_CASE(cublas_swap_complex) { multi::array y = { 1.0 + I*9.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; blas::swap(x, y); - BOOST_REQUIRE( static_cast(x[0]) == 1.0 + I*9.0 ); + BOOST_TEST( static_cast(x[0]) == 1.0 + I*9.0 ); { thrust::swap_ranges(begin(x), end(x), begin(y)); thrust::swap_ranges(begin(x), end(x), begin(y)); - BOOST_REQUIRE( static_cast(x[0]) == 1.0 + I*9.0 ); + BOOST_TEST( static_cast(x[0]) == 1.0 + I*9.0 ); } { using blas::operator^; (x^y); (x^y); - BOOST_REQUIRE( static_cast(x[0]) == 1.0 + I*9.0 ); + BOOST_TEST( static_cast(x[0]) == 1.0 + I*9.0 ); } } @@ -212,54 +221,54 @@ BOOST_AUTO_TEST_CASE(cublas_asum_complex_column) { { double res2; res2 = blas::asum(x); - BOOST_REQUIRE( res == res2 ); + BOOST_TEST( res == res2 ); } { double res2 = blas::asum(x); - BOOST_REQUIRE( res == res2 ); + BOOST_TEST( res == res2 ); } { auto res2 = std::transform_reduce( x.begin(), x.end(), double{}, std::plus<>{}, [](T const& e) {return std::abs(e.real()) + std::abs(e.imag());} ); - BOOST_REQUIRE( res == res2 ); + BOOST_TEST( res == res2 ); } { auto res2 = thrust::transform_reduce( x.begin(), x.end(), [] __device__ (T const& e) {return std::abs(e.real()) + std::abs(e.imag());}, double{}, thrust::plus<>{} ); - BOOST_REQUIRE( res == res2 ); + BOOST_TEST( res == res2 ); } { multi::static_array> res2({}, 0.0); res2.assign( &blas::asum(x) ); res2 = blas::asum(x); - BOOST_REQUIRE(( res == static_cast>::element_ref>(res2) )); - BOOST_REQUIRE(( res == static_cast(res2) )); - // BOOST_REQUIRE( res == res2 ); + BOOST_TEST(( res == static_cast>::element_ref>(res2) )); + BOOST_TEST(( res == static_cast(res2) )); + // BOOST_TEST( res == res2 ); } { multi::array> res2 = blas::asum(x); - BOOST_REQUIRE(( res == static_cast>::element_ref>(res2) )); - BOOST_REQUIRE(( res == static_cast(res2) )); - // BOOST_REQUIRE( res == res2 ); + BOOST_TEST(( res == static_cast>::element_ref>(res2) )); + BOOST_TEST(( res == static_cast(res2) )); + // BOOST_TEST( res == res2 ); } { using blas::operators::operator==; using blas::operators::operator!=; - BOOST_REQUIRE( x != 0 ); - BOOST_REQUIRE( not (x == 0) ); + BOOST_TEST( x != 0 ); + BOOST_TEST( not (x == 0) ); } { using blas::operators::contains_nan; - BOOST_REQUIRE( not contains_nan(x) ); + BOOST_TEST( not contains_nan(x) ); } { using blas::operators::isfinite; using blas::operators::isinf; - BOOST_REQUIRE( isfinite(x) ); - BOOST_REQUIRE( not isinf(x) ); + BOOST_TEST( isfinite(x) ); + BOOST_TEST( not isinf(x) ); } } @@ -274,19 +283,19 @@ BOOST_AUTO_TEST_CASE(cublas_asum_complex_nans) { { using blas::operators::contains_nan; - BOOST_REQUIRE( contains_nan(x) ); + BOOST_TEST( contains_nan(x) ); } { using blas::operators::operator==; using blas::operators::operator!=; - BOOST_REQUIRE( not (x != 0) ); - BOOST_REQUIRE( not (x == 0) ); + BOOST_TEST( not (x != 0) ); + BOOST_TEST( not (x == 0) ); } { using blas::operators::isfinite; using blas::operators::isinf; - BOOST_REQUIRE( not isfinite(x) ); - BOOST_REQUIRE( not isinf(x) ); + BOOST_TEST( not isfinite(x) ); + BOOST_TEST( not isinf(x) ); } } @@ -302,19 +311,19 @@ BOOST_AUTO_TEST_CASE(cublas_asum_complex_inf) { // double res; { using blas::operators::contains_nan; - BOOST_REQUIRE( not contains_nan(x) ); + BOOST_TEST( not contains_nan(x) ); } { using blas::operators::operator==; using blas::operators::operator!=; - BOOST_REQUIRE( (x != 0) ); - BOOST_REQUIRE( not (x == 0) ); + BOOST_TEST( (x != 0) ); + BOOST_TEST( not (x == 0) ); } { using blas::operators::isfinite; using blas::operators::isinf; - BOOST_REQUIRE( not isfinite(x) ); - BOOST_REQUIRE( isinf(x) ); + BOOST_TEST( not isfinite(x) ); + BOOST_TEST( isinf(x) ); } } @@ -332,22 +341,22 @@ BOOST_AUTO_TEST_CASE(cublas_nrm2_complex_column) { { double res2; res2 = blas::nrm2(x); - BOOST_REQUIRE( res == res2 ); + BOOST_TEST( res == res2 ); } { auto res2 = +blas::nrm2(x); - BOOST_REQUIRE( res == res2 ); + BOOST_TEST( res == res2 ); } { auto res2 = sqrt(thrust::transform_reduce( x.begin(), x.end(), [] __device__ (T const& e) {return thrust::norm(e);}, double{}, thrust::plus<>{} )); - BOOST_REQUIRE( res == res2 ); + BOOST_TEST( res == res2 ); } { multi::array> res2 = blas::nrm2(x); - BOOST_REQUIRE(( res == static_cast(res2) )); + BOOST_TEST(( res == static_cast(res2) )); } } @@ -367,27 +376,27 @@ BOOST_AUTO_TEST_CASE(cublas_dot_complex_column) { { complex res2; res2 = blas::dot(x, y); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } { multi::array res2(complex{1.0, 0.0}); res2 = blas::dot(x, y); - BOOST_REQUIRE( static_cast(res2) == res ); + BOOST_TEST( static_cast(res2) == res ); } { using blas::operators::operator,; auto res2 = +(x, y); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } { auto res2 = +blas::dot(x, y); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } { // auto [x2, A2, B2] = generate_ABx >(); // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } } { @@ -397,17 +406,17 @@ BOOST_AUTO_TEST_CASE(cublas_dot_complex_column) { using blas::operators::operator,; using blas::operators::operator*; auto res2 = +(*x, y); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } { auto res2 = +blas::dot(blas::C(x), y); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } { // auto [x2, A2, B2] = generate_ABx >(); // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__ (T const& t1, T const& t2) {return conj(t1)*t2;}); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } } { @@ -416,21 +425,21 @@ BOOST_AUTO_TEST_CASE(cublas_dot_complex_column) { { using blas::operators::operator,; auto res2 = +(x, blas::C(y)); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } { auto res2 = +blas::dot(x, blas::C(y)); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } { // auto [x2, A2, B2] = generate_ABx >(); // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__ (T const& t1, T const& t2) {return t1*conj(t2);}); - BOOST_REQUIRE(res == res2); + BOOST_TEST(res == res2); } { - BOOST_REQUIRE( blas::dot(blas::C(x), x) == pow(blas::nrm2(x), 2.0) ); - BOOST_REQUIRE( blas::dot(x, blas::C(x)) == pow(blas::nrm2(x), 2.0) ); + BOOST_TEST( blas::dot(blas::C(x), x) == pow(blas::nrm2(x), 2.0) ); + BOOST_TEST( blas::dot(x, blas::C(x)) == pow(blas::nrm2(x), 2.0) ); using blas::operators::operator,; using blas::operators::operator*; @@ -438,15 +447,15 @@ BOOST_AUTO_TEST_CASE(cublas_dot_complex_column) { using blas::operators::norm; using blas::operators::operator^; - BOOST_REQUIRE( (*x, x) == pow(abs(x), 2.0) ); - BOOST_REQUIRE( (*x, x) == pow(abs(x), 2) ); - BOOST_REQUIRE( (*x, x) == norm(x) ); + BOOST_TEST( (*x, x) == pow(abs(x), 2.0) ); + BOOST_TEST( (*x, x) == pow(abs(x), 2) ); + BOOST_TEST( (*x, x) == norm(x) ); - BOOST_REQUIRE( (x, *x) == pow(abs(x), 2.0) ); - BOOST_REQUIRE( (x, *x) == pow(abs(x), 2) ); - BOOST_REQUIRE( (x, *x) == norm(x) ); + BOOST_TEST( (x, *x) == pow(abs(x), 2.0) ); + BOOST_TEST( (x, *x) == pow(abs(x), 2) ); + BOOST_TEST( (x, *x) == norm(x) ); - BOOST_REQUIRE( (*x, x) == (x^2) ); + BOOST_TEST( (*x, x) == (x^2) ); } } { @@ -457,21 +466,21 @@ BOOST_AUTO_TEST_CASE(cublas_dot_complex_column) { // { // using blas::operators::operator,; // auto res2 = +(x, blas::C(y)); - // BOOST_REQUIRE(res == res2); + // BOOST_TEST(res == res2); // } // { // auto res2 = +blas::dot(x, blas::C(y)); - // BOOST_REQUIRE(res == res2); + // BOOST_TEST(res == res2); // } // { // // auto [x2, A2, B2] = generate_ABx >(); // // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); // auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__ (T const& t1, T const& t2) {return t1*conj(t2);}); - // BOOST_REQUIRE(res == res2); + // BOOST_TEST(res == res2); // } // { - // BOOST_REQUIRE( blas::dot(blas::C(x), x) == pow(blas::nrm2(x), 2.0) ); - // BOOST_REQUIRE( blas::dot(x, blas::C(x)) == pow(blas::nrm2(x), 2.0) ); + // BOOST_TEST( blas::dot(blas::C(x), x) == pow(blas::nrm2(x), 2.0) ); + // BOOST_TEST( blas::dot(x, blas::C(x)) == pow(blas::nrm2(x), 2.0) ); // using blas::operators::operator,; // using blas::operators::operator*; @@ -479,15 +488,15 @@ BOOST_AUTO_TEST_CASE(cublas_dot_complex_column) { // using blas::operators::norm; // using blas::operators::operator^; - // BOOST_REQUIRE( (*x, x) == pow(abs(x), 2.0) ); - // BOOST_REQUIRE( (*x, x) == pow(abs(x), 2) ); - // BOOST_REQUIRE( (*x, x) == norm(x) ); + // BOOST_TEST( (*x, x) == pow(abs(x), 2.0) ); + // BOOST_TEST( (*x, x) == pow(abs(x), 2) ); + // BOOST_TEST( (*x, x) == norm(x) ); - // BOOST_REQUIRE( (x, *x) == pow(abs(x), 2.0) ); - // BOOST_REQUIRE( (x, *x) == pow(abs(x), 2) ); - // BOOST_REQUIRE( (x, *x) == norm(x) ); + // BOOST_TEST( (x, *x) == pow(abs(x), 2.0) ); + // BOOST_TEST( (x, *x) == pow(abs(x), 2) ); + // BOOST_TEST( (x, *x) == norm(x) ); - // BOOST_REQUIRE( (*x, x) == (x^2) ); + // BOOST_TEST( (*x, x) == (x^2) ); // } } } @@ -504,17 +513,17 @@ BOOST_AUTO_TEST_CASE(cublas_axpy_complex_one) { blas::axpy(1.0, x, y); std::cout << y[0] << std::endl; - BOOST_REQUIRE( static_cast(y[0]) == 3.2 + I*0.0 ); + BOOST_TEST( static_cast(y[0]) == 3.2 + I*0.0 ); { multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [] __device__ (auto const& ex, auto const& ey) {return ex + ey;}); - BOOST_TEST( yy == y , boost::test_tools::per_element() ); + BOOST_TEST( yy == y ); } { multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; using blas::operators::operator+=; yy += x; - BOOST_REQUIRE( yy == y ); + BOOST_TEST( yy == y ); } } @@ -530,17 +539,17 @@ BOOST_AUTO_TEST_CASE(cublas_axpy_complex_mone) { blas::axpy(-1.0, x, y); std::cout << y[0] << std::endl; - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*0.0 ); + BOOST_TEST( static_cast(y[0]) == 1.0 + I*0.0 ); { multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [] __host__ __device__ (T ex, T ey) {return -1.0*ex + ey;}); - BOOST_TEST( yy == y , boost::test_tools::per_element() ); + BOOST_TEST( yy == y ); } { multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; using blas::operators::operator-=; yy -= x; - BOOST_REQUIRE( yy == y ); + BOOST_TEST( yy == y ); } { multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; @@ -548,10 +557,10 @@ BOOST_AUTO_TEST_CASE(cublas_axpy_complex_mone) { yy -= x; yy -= y; using blas::operators::norm; - BOOST_REQUIRE( norm(yy) == 0 ); + BOOST_TEST( norm(yy) == 0 ); using blas::operators::operator==; - BOOST_REQUIRE( operator==(yy, 0) ); - BOOST_REQUIRE( yy == 0 ); + BOOST_TEST( operator==(yy, 0) ); + BOOST_TEST( yy == 0 ); } } @@ -567,18 +576,18 @@ BOOST_AUTO_TEST_CASE(cublas_axpy_complex_alpha) { blas::axpy(3.0, x, y); std::cout << y[0] << std::endl; - BOOST_REQUIRE( static_cast(y[0]) == 5.4 + I*0.0 ); + BOOST_TEST( static_cast(y[0]) == 5.4 + I*0.0 ); { multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [aa=3.0] __device__ (T ex, T ey) {return aa*ex + ey;}); - BOOST_TEST( yy == y , boost::test_tools::per_element() ); + BOOST_TEST( yy == y ); } { multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; using blas::operators::operator+=; using blas::operators::operator*; yy += 3.0*x; - BOOST_REQUIRE( yy == y ); + BOOST_TEST( yy == y ); } } @@ -602,22 +611,22 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemv_conj_complex_zero) { multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming std::transform(begin(A), end(A), begin(yy), [&x] (auto const& Ac) {return blas::dot(Ac, x);}); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming yy = blas::gemv(1.0, A, x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { multi::array yy = blas::gemv(1.0, A, x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { @@ -625,9 +634,9 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemv_conj_complex_zero) { multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming yy = A % x; - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } } @@ -652,35 +661,33 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_conj_zero) { std::transform(begin(transposed(A)), end(transposed(A)), begin(yy), [&x] (auto const& Ac) {return blas::dot(Ac, x);}); BOOST_REQUIRE_CLOSE( static_cast(y[0]).real(), static_cast(yy[0]).real(), 1e-7 ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming yy = blas::gemv(1.0, blas::T(A), x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { multi::array yy = blas::gemv(1.0, blas::T(A), x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { using blas::operators::operator%; multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming yy = ~A % x; - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } } -template void what(T&&) = delete; - BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_zero) { namespace blas = multi::blas; using T = complex; @@ -703,22 +710,22 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_zero) { return blas::dot(*Ac, x);} ); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming yy = blas::gemv(1.0, blas::J(A), x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { multi::array yy = blas::gemv(1.0, blas::J(A), x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { @@ -727,9 +734,9 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_zero) { multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming yy = *A % x; - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[0]) == static_cast(yy[0]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } } @@ -790,16 +797,16 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_trans_one) { std::transform(begin(transposed(A)), end(transposed(A)), begin(yy), begin(yy), [&x,aa=3.0 + I*4.0,bb=1.0] (auto const& Ac, complex e) {return aa*blas::dot(Ac, x) + bb*e;}); BOOST_REQUIRE_CLOSE( static_cast(y[0]).real(), static_cast(yy[0]).real(), 1e-7 ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming yy += blas::gemv(3.0 + I*4.0, blas::T(A), x); BOOST_REQUIRE_CLOSE( static_cast(y[0]).real(), static_cast(yy[0]).real(), 1e-7 ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } { multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming @@ -807,8 +814,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_trans_one) { yy += (3.0 + I*4.0)* ~A % x; BOOST_REQUIRE_CLOSE( static_cast(y[0]).real(), static_cast(yy[0]).real(), 1e-7 ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); + BOOST_TEST( static_cast(y[1]) == static_cast(yy[1]) ); + BOOST_TEST( static_cast(y[2]) == static_cast(yy[2]) ); } } @@ -839,8 +846,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_none) { [&B, aa=1.0, bb=0] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr));} ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -855,8 +862,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_none) { } ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -867,8 +874,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_none) { [&A, aa=1.0, bb=1.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -882,8 +889,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_none) { } ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } } @@ -911,8 +918,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_second) { [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -926,8 +933,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_second) { return blas::gemv(aa, B, Ac, bb, std::move(Cr)); }); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -938,8 +945,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_second) { [&A, aa=1.0, bb=1.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -953,8 +960,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_second) { } ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -968,8 +975,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_second) { } ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } } @@ -997,8 +1004,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_first) { [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, blas::T(A), Bc, bb, std::move(Cc));} ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -1012,8 +1019,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_first) { return blas::gemv(aa, blas::T(B), Ac, bb, std::move(Cr)); }); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -1024,8 +1031,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_first) { [&A, aa=1.0, bb=1.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, blas::T(A), Bc, bb, std::move(Cc));} ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -1039,8 +1046,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_first) { } ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -1054,8 +1061,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_first) { } ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } } @@ -1083,8 +1090,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_both) { [&A, aa=1.0, bb=0.0] (auto const& Br, auto&& Cc) {return blas::gemv(aa, blas::T(A), Br, bb, std::move(Cc));} ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -1098,8 +1105,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_both) { return blas::gemv(aa, B, Ac, bb, std::move(Cr)); }); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -1110,8 +1117,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_both) { [&A, aa=1.0, bb=1.0] (auto const& Br, auto&& Cc) {return blas::gemv(aa, blas::T(A), Br, bb, std::move(Cc));} ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -1125,8 +1132,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_both) { } ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } { multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming @@ -1140,8 +1147,8 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_both) { } ); - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); + BOOST_TEST( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); + BOOST_TEST( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); } } @@ -1186,14 +1193,14 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_second) { ), std::move(Cr); }); } - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - BOOST_TEST_REQUIRE( static_cast(C_copy[1][0]).real() == +static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(C_copy[1][0]).imag() == -static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(C_copy[1][0]).real() == +static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(C_copy[1][0]).imag() == -static_cast(C[0][1]).imag() ); } } @@ -1238,14 +1245,14 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_first) { // ), std::move(Cr); // }); // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(C_copy[1][0]).real() == +static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(C_copy[1][0]).imag() == -static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(C_copy[1][0]).real() == +static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(C_copy[1][0]).imag() == -static_cast(C[0][1]).imag() ); } } @@ -1299,21 +1306,21 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_second) { } ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } { multi::array CC({2, 2}, {3.0, 0.0}); CC = blas::gemm({1.0, 0.0}, A, blas::H(B)); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } { multi::array CC({2, 2}, {3.0, 0.0}); @@ -1321,11 +1328,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_second) { using blas::operators::operator~; CC = A* ~*B; - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } } @@ -1357,21 +1364,21 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_second_plus) { } ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } { multi::array CC({2, 2}, {3.0, 0.0}); CC += blas::gemm({1.0, 0.0}, A, blas::H(B)); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } { multi::array CC({2, 2}, {3.0, 0.0}); @@ -1379,11 +1386,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_second_plus) { using blas::operators::operator~; CC += A* ~*B; - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } } @@ -1415,11 +1422,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_first) { // } // } // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } { multi::array CC({2, 2}, {3.0, 0.0}); @@ -1430,21 +1437,21 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_first) { }); return std::move(Cr); }); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } { multi::array CC({2, 2}, {3.0, 0.0}); CC = blas::gemm({1.0, 0.0}, blas::H(A), B); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } { multi::array CC({2, 2}, {3.0, 0.0}); @@ -1452,11 +1459,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_first) { using blas::operators::operator~; CC = ~*A *B; - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } } @@ -1488,11 +1495,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_both) { // } // } // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); @@ -1504,22 +1511,22 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_both) { // }); // return std::move(Cr); // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } { multi::array CC({2, 2}, {3.0, 0.0}); CC = blas::gemm({1.0, 0.0}, blas::H(A), blas::H(B)); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } { multi::array CC({2, 2}, {3.0, 0.0}); @@ -1527,11 +1534,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_both) { using blas::operators::operator~; CC = ~*A * ~*B; - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } } @@ -1563,11 +1570,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_herm) { // } // } // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); @@ -1579,22 +1586,22 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_herm) { // }); // return std::move(Cr); // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } { multi::array CC({2, 2}, {3.0, 0.0}); CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } { multi::array CC({2, 2}, {3.0, 0.0}); @@ -1602,11 +1609,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_herm) { using blas::operators::operator~; CC = ~A * ~*B; - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); } } @@ -1638,11 +1645,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_trans) { // } // } // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); @@ -1654,22 +1661,22 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_trans) { // }); // return std::move(Cr); // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); // CC = blas::gemm({1.0, 0.0}, blas::H(A), blas::T(B)); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); @@ -1677,11 +1684,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_trans) { // using blas::operators::operator~; // CC = ~*A * ~B; - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } } @@ -1713,11 +1720,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_herm) { // } // } // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); @@ -1729,22 +1736,22 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_herm) { // }); // return std::move(Cr); // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); // CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); @@ -1752,11 +1759,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_herm) { // using blas::operators::operator~; // CC = ~A * ~*B; - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } } @@ -1789,11 +1796,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_conj) { // } // } // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); @@ -1805,22 +1812,22 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_conj) { // }); // return std::move(Cr); // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); // CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } // { // multi::array CC({2, 2}, {3.0, 0.0}); @@ -1828,11 +1835,11 @@ BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_conj) { // using blas::operators::operator~; // CC = ~A * ~*B; - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); + // BOOST_TEST( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); + // BOOST_TEST( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); + // BOOST_TEST( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); + // BOOST_TEST( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); // } } @@ -2048,3 +2055,5 @@ BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check } #endif #endif + +return boost::report_errors();} diff --git a/include/multi/adaptors/cuda/cublas/test/axpy.cu b/include/boost/multi/adaptors/cuda/cublas/test/axpy.cu similarity index 68% rename from include/multi/adaptors/cuda/cublas/test/axpy.cu rename to include/boost/multi/adaptors/cuda/cublas/test/axpy.cu index 2e1469d67..102e1eafc 100644 --- a/include/multi/adaptors/cuda/cublas/test/axpy.cu +++ b/include/boost/multi/adaptors/cuda/cublas/test/axpy.cu @@ -1,20 +1,24 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUBLAS gemv" -#include +#include -#include - -#include -#include -#include -#include +#include +#include +#include +#include #include namespace multi = boost::multi; +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +#define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) + +int main() { BOOST_AUTO_TEST_CASE(blas_axpy_complex) { namespace blas = multi::blas; using complex = thrust::complex; @@ -31,7 +35,7 @@ BOOST_AUTO_TEST_CASE(blas_axpy_complex) { blas::axpy(complex{2.0, 0.0}, x, arr[1]); // arr can't be const multi::array arr_copy = arr; - BOOST_REQUIRE(( arr_copy[1][0] == complex{23.0, 0.0} )); + BOOST_TEST(( arr_copy[1][0] == complex{23.0, 0.0} )); } { multi::thrust::cuda::array arr = { @@ -45,7 +49,7 @@ BOOST_AUTO_TEST_CASE(blas_axpy_complex) { arr[1] += blas::axpy(complex{2.0, 0.0}, x); multi::array arr_copy = arr; - BOOST_REQUIRE(( arr_copy[1][0] == complex{23.0, 0.0} )); + BOOST_TEST(( arr_copy[1][0] == complex{23.0, 0.0} )); } { multi::thrust::cuda::array arr = { @@ -60,6 +64,8 @@ BOOST_AUTO_TEST_CASE(blas_axpy_complex) { multi::array arr_copy = arr; std::cout << arr_copy[1][0] << std::endl; - BOOST_REQUIRE(( arr_copy[1][0] == complex{23.0, 0.0} )); + BOOST_TEST(( arr_copy[1][0] == complex{23.0, 0.0} )); } } + +return boost::report_errors();} diff --git a/include/multi/adaptors/cuda/cublas/test/dot.cu b/include/boost/multi/adaptors/cuda/cublas/test/dot.cu similarity index 65% rename from include/multi/adaptors/cuda/cublas/test/dot.cu rename to include/boost/multi/adaptors/cuda/cublas/test/dot.cu index 35a88b0fe..b0a9b0ffb 100644 --- a/include/multi/adaptors/cuda/cublas/test/dot.cu +++ b/include/boost/multi/adaptors/cuda/cublas/test/dot.cu @@ -1,18 +1,18 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUBLAS dot" -#include +#include -#include +#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include -#include +#include #include @@ -20,6 +20,9 @@ namespace multi = boost::multi; +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +int main() { // BOOST_AUTO_TEST_CASE(cublas_dot_out_param_complex_C) { // namespace blas = multi::blas; // using complex = thrust::complex; @@ -30,8 +33,16 @@ namespace multi = boost::multi; // complex res{0.0, 0.0}; // blas::dot(blas::C(x), y, res); -// // BOOST_REQUIRE( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); +// // BOOST_TEST( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); // } + // range for test + { + multi::thrust::universal::array const x = {1, 2, 3}; + for(auto const& elem : x) { + int ee = elem; + BOOST_TEST(ee < 10); + } + } BOOST_AUTO_TEST_CASE(cublas_dot_out_array0D_complex_C) { namespace blas = multi::blas; @@ -47,11 +58,11 @@ BOOST_AUTO_TEST_CASE(cublas_dot_out_array0D_complex_C) { { multi::array res_copy{complex{0.0, 0.0}}; res_copy = res; - BOOST_REQUIRE(( *res_copy.base() == complex{14.0, 4.0} )); + BOOST_TEST(( *res_copy.base() == complex{14.0, 4.0} )); } { multi::array res_copy{res}; - BOOST_REQUIRE(( *res_copy.base() == complex{14.0, 4.0} )); + BOOST_TEST(( *res_copy.base() == complex{14.0, 4.0} )); } } @@ -64,7 +75,7 @@ BOOST_AUTO_TEST_CASE(cublas_dot_out_array0D_complex_C) { // multi::thrust::cuda::array const y = {1.0 + 0.0*I, 2.0 + 2.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming // complex res = blas::dot(blas::C(x), y); -// BOOST_REQUIRE( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); +// BOOST_TEST( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); // } // BOOST_AUTO_TEST_CASE(blas_dot_functional_mutate_complex_C) { @@ -77,5 +88,7 @@ BOOST_AUTO_TEST_CASE(cublas_dot_out_array0D_complex_C) { // complex res; // res = blas::dot(blas::C(x), y); -// BOOST_REQUIRE( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); +// BOOST_TEST( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); // } + +return boost::report_errors();} diff --git a/include/boost/multi/adaptors/cuda/cublas/test/gemm.cu b/include/boost/multi/adaptors/cuda/cublas/test/gemm.cu new file mode 100644 index 000000000..d15f93458 --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas/test/gemm.cu @@ -0,0 +1,174 @@ +// Copyright 2023-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include +#include +#include +#include + +#include + +#include + +#include // for std::iota + +namespace multi = boost::multi; + +#include + +int main() { + // BOOST_AUTO_TEST_CASE(multi_blas_gemv_complex) + { + namespace blas = multi::blas; + using complex = thrust::complex; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::thrust::cuda::array const M_gpu = { + { {9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0}}, + { {4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0}}, + {{14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0}}, + }; + + multi::thrust::cuda::array const X_gpu = { + {1.1, 0.0}, + {2.1, 0.0}, + {3.1, 0.0}, + {4.1, 0.0} + }; + + multi::thrust::cuda::array Y_gpu = { + {4.0, 0.0}, + {5.0, 0.0}, + {6.0, 0.0} + }; + + blas::gemv(/*alpha*/ 1.1, M_gpu, X_gpu, /*beta*/ 1.2, Y_gpu); // y = a*M*x + b*y + + multi::array const Y_copy = Y_gpu; + + using blas::operators::operator-; + BOOST_TEST(+blas::nrm2(Y_copy - + multi::array{ + {214.02, 0.0}, + {106.43, 0.0}, + {188.37, 0.0} + } + ) < 1e-13); + } + + // BOOST_AUTO_TEST_CASE(cublas_gemv_real) + { + namespace blas = multi::blas; + using T = double; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::thrust::cuda::array const M_gpu = { + { 9.0, 24.0, 30.0, 9.0}, + { 4.0, 10.0, 12.0, 7.0}, + {14.0, 16.0, 36.0, 1.0}, + }; + + multi::thrust::cuda::array const X_gpu = {1.1, 2.1, 3.1, 4.1}; + + multi::thrust::cuda::array Y_gpu = {4.0, 5.0, 6.0}; + + blas::gemv(/*alpha*/ 1.1, M_gpu, X_gpu, /*beta*/ 1.2, Y_gpu); // y = a*M*x + b*y + + multi::array const Y_copy = Y_gpu; + + using blas::operators::operator-; + BOOST_TEST(+blas::nrm2(Y_copy - multi::array{214.02, 106.43, 188.37}) < 1e-13); + } + + // BOOST_AUTO_TEST_CASE(cublas_gemm_nh) + { + namespace blas = multi::blas; + + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit + + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::thrust::cuda::array const a = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + + multi::thrust::cuda::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming + blas::gemm({1.0, 0.0}, a, a, {0.0, 0.0}, c); // c=aa†, c†=aa† + + multi::array const c_copy = c; + BOOST_TEST( c_copy[1][0] == 16.0 - 2.0*I ); + BOOST_TEST( c_copy[0][1] == 14.0 - 38.0*I ); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::thrust::cuda::array const a = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + + auto const c = +blas::gemm(complex{1.0, 0.0}, a, a); // c=aa†, c†=aa† + + multi::array const c_copy = c; + BOOST_TEST( c_copy[1][0] == 16.0 - 2.0*I ); + BOOST_TEST( c_copy[0][1] == 14.0 - 38.0*I ); + } + { + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::thrust::cuda::array const a = { + {1.0 - 2.0 * I, 9.0 - 1.0 * I}, + {2.0 + 3.0 * I, 1.0 - 2.0 * I}, + }; + + multi::thrust::cuda::array c({2, 2}, {0.0, 0.0}); + c += blas::gemm(complex{1.0, 0.0}, a, a); // c=aa†, c†=aa† + + multi::array const c_copy = c; + BOOST_TEST( c_copy[1][0] == 16.0 - 2.0*I ); + BOOST_TEST( c_copy[0][1] == 14.0 - 38.0*I ); + } + + // Chris + { + int m = 12; + int n = 10; + int ell = 8; + + multi::array a({m, n, ell}); + multi::array b({n, ell}); + + std::iota(a.elements().begin(), a.elements().end(), 20.0); + std::iota(b.elements().begin(), b.elements().end(), 30.0); + + multi::array c_gold(m, 0.0); + + for(int k = 0; k != m; ++k) { + for(int j = 0; j != n; ++j) { + for(int i = 0; i != ell; ++i) { + c_gold[k] += a[k][j][i] * b[j][i]; + } + } + } + + multi::array c_flat(m, 0.0); + + for(int k = 0; k != m; ++k) { + for(int ji = 0; ji != a[k].elements().size(); ++ji) { + c_flat[k] += a[k].elements()[ji] * b.elements()[ji]; + } + } + + BOOST_TEST( c_gold == c_flat ); + + // gpu::run(m, n, [...] (auto k, auto j) { + //     for (int i =0; i +#include -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include #include namespace multi = boost::multi; +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +#define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) + +int main() { BOOST_AUTO_TEST_CASE(multi_blas_gemv_complex) { namespace blas = multi::blas; using complex = thrust::complex; @@ -34,7 +38,7 @@ BOOST_AUTO_TEST_CASE(multi_blas_gemv_complex) { multi::array const Y_copy = Y_gpu; using blas::operators::operator-; - BOOST_REQUIRE_SMALL( +blas::nrm2(Y_copy - multi::array{ {214.02, 0.0}, {106.43, 0.0}, {188.37, 0.0} }) , 1e-13); + BOOST_REQUIRE_CLOSE( +blas::nrm2(Y_copy - multi::array{ {214.02, 0.0}, {106.43, 0.0}, {188.37, 0.0} }) , 0.0, 1e-13); } BOOST_AUTO_TEST_CASE(multi_blas_gemv_complex_value) { @@ -54,7 +58,7 @@ BOOST_AUTO_TEST_CASE(multi_blas_gemv_complex_value) { multi::array const Y_copy = Y_gpu; using blas::operators::operator-; - BOOST_REQUIRE_SMALL( +blas::nrm2(Y_copy - multi::array{ {209.22, 0.0}, {100.43, 0.0}, {181.17, 0.0} }) , 1e-13); + BOOST_REQUIRE_CLOSE( +blas::nrm2(Y_copy - multi::array{ {209.22, 0.0}, {100.43, 0.0}, {181.17, 0.0} }) , 0.0, 1e-13); } BOOST_AUTO_TEST_CASE(cublas_gemv_real) { @@ -76,5 +80,7 @@ BOOST_AUTO_TEST_CASE(cublas_gemv_real) { multi::array const Y_copy = Y_gpu; using blas::operators::operator-; - BOOST_REQUIRE_SMALL( +blas::nrm2(Y_copy - multi::array{ 214.02, 106.43, 188.37 }) , 1e-13); + BOOST_REQUIRE_CLOSE( +blas::nrm2(Y_copy - multi::array{ 214.02, 106.43, 188.37 }) , 0.0, 1e-13); } + +return boost::report_errors();} diff --git a/include/boost/multi/adaptors/cuda/cublas/test/herk.cu b/include/boost/multi/adaptors/cuda/cublas/test/herk.cu new file mode 100644 index 000000000..5cee79cc2 --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas/test/herk.cu @@ -0,0 +1,47 @@ +// Copyright 2023-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +namespace multi = boost::multi; +namespace blas = multi::blas; + +using complex = thrust::complex; +complex const I{0, 1}; + +int main() { + BOOST_AUTO_TEST_CASE(multi_blas_herk){ + multi::array, 2> const a = { + {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, + {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I} + }; + multi::thrust::cuda::array, 2> const a_gpu = a; + + { + multi::array, 2> c({2, 2}, 9999.0); + blas::herk(1.0, a, c); + BOOST_TEST( c[1][0] == complex(50.0, -49.0) ); + BOOST_TEST( c[0][1] == complex(50.0, +49.0) ); + + multi::array, 2> const c_copy = blas::herk(1.0, a); + BOOST_TEST( c == c_copy ); + } + { + multi::array, 2> c({3, 3}, 9999.0); + blas::herk(1.0, blas::H(a), c); + BOOST_TEST( c[2][1] == complex(41, +2) ); + BOOST_TEST( c[1][2] == complex(41, -2) ); + + multi::array, 2> const c_copy = blas::herk(1., blas::H(a)); + BOOST_TEST( c_copy == c ); + } + } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/cuda/cublas/test/nrm2.cu b/include/boost/multi/adaptors/cuda/cublas/test/nrm2.cu new file mode 100644 index 000000000..da3286179 --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas/test/nrm2.cu @@ -0,0 +1,187 @@ +// Copyright 2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace multi = boost::multi; + +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +int main() { +// BOOST_AUTO_TEST_CASE(cublas_dot_out_param_complex_C) { +// namespace blas = multi::blas; +// using complex = thrust::complex; +// complex const I{0.0, 1.0}; + +// multi::thrust::cuda::array const x = {1.0 + 0.0*I, 2.0 + 0.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming +// multi::thrust::cuda::array const y = {1.0 + 0.0*I, 2.0 + 2.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming + +// complex res{0.0, 0.0}; +// blas::dot(blas::C(x), y, res); +// // BOOST_TEST( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); +// } + +BOOST_AUTO_TEST_CASE(cublas_dot_out_array0D_complex_C) { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; + + multi::thrust::cuda::array const x = {1.0 + 0.0*I, 2.0 + 0.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming + multi::thrust::cuda::array const y = {1.0 + 0.0*I, 2.0 + 2.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming + + multi::thrust::cuda::array res{complex{0.0, 0.0}}; + blas::dot(blas::C(x), y, res); + + { + multi::array res_copy{complex{0.0, 0.0}}; + res_copy = res; + BOOST_TEST(( *res_copy.base() == complex{14.0, 4.0} )); + } + { + multi::array res_copy{res}; + BOOST_TEST(( *res_copy.base() == complex{14.0, 4.0} )); + } +} + +BOOST_AUTO_TEST_CASE(cublas_dot_out_array0D_complex_C) { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; + + multi::thrust::cuda::array const x = {1.0 + 0.0*I, 2.0 + 0.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming + multi::thrust::cuda::array const y = {1.0 + 0.0*I, 2.0 + 2.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming + + // multi::thrust::cuda::array res{complex{0.0, 0.0}}; + + { + double res{0.0}; + multi::array const xx = {1.0, 2.0, 3.0}; + + res = blas::nrm2(xx); + BOOST_TEST( std::abs( res - std::sqrt(1.0*1.0 + 2.0*2.0 + 3.0*3.0) ) < 1e-7 ); + } + { + double res{0.0}; + multi::array const xx = {1.0, 2.0, 3.0}; + + blas::nrm2(xx, res); + BOOST_TEST( std::abs( res - std::sqrt(1.0*1.0 + 2.0*2.0 + 3.0*3.0) ) < 1e-7 ); + } + { + multi::array res{0.0}; + multi::array const xx = {1.0, 2.0, 3.0}; + + blas::nrm2(xx, res); + BOOST_TEST( std::abs( *res.base() - std::sqrt(1.0*1.0 + 2.0*2.0 + 3.0*3.0) ) < 1e-7 ); + } + { + multi::array res{0.0}; + multi::array, 1> const xx = {std::complex{1.0, 2.0}, std::complex{3.0, 4.0}, std::complex{5.0, 6.0}}; + + blas::nrm2(xx, res); + BOOST_TEST( std::abs( *res.base() - std::sqrt(std::norm(std::complex{1.0, 2.0}) + std::norm(std::complex{3.0, 4.0}) + std::norm(std::complex{5.0, 6.0})) ) < 1e-7 ); + } + { + multi::thrust::cuda::array res{0.0}; + multi::thrust::cuda::array const xx = {1.0, 2.0, 3.0}; + + blas::nrm2(xx, res); + BOOST_TEST( *res.base() == std::sqrt(1.0*1.0 + 2.0*2.0 + 3.0*3.0) ); + } + { + multi::thrust::cuda::array res{0.0}; + multi::thrust::cuda::array const xx = {1.0, 2.0, 3.0}; + + res = blas::nrm2(xx); + BOOST_TEST( *res.base() == std::sqrt(1.0*1.0 + 2.0*2.0 + 3.0*3.0) ); + + double res2 = blas::nrm2(xx); + BOOST_TEST( res2 == std::sqrt(1.0*1.0 + 2.0*2.0 + 3.0*3.0) ); + } + { + multi::thrust::cuda::array res{0.0}; + multi::thrust::cuda::array, 1> const xx = {thrust::complex(1.0, 2.0), thrust::complex(3.0, 4.0), thrust::complex(4.0, 5.0)}; + + res = blas::nrm2(xx); + BOOST_TEST( *res.base() == std::sqrt(std::norm(std::complex(1.0, 2.0)) + std::norm(std::complex(3.0, 4.0)) + std::norm(std::complex(4.0, 5.0))) ); + + double res2 = blas::nrm2(xx); + BOOST_TEST( res2 == *res.base() ); + } + { + multi::thrust::cuda::array res{0.0}; + multi::thrust::cuda::array, 1> const xx = {thrust::complex(1.0, 2.0), thrust::complex(3.0, 4.0), thrust::complex(4.0, 5.0)}; + + blas::nrm2_n(xx.begin(), xx.size(), res.base()); + BOOST_TEST( *res.base() == std::sqrt(std::norm(std::complex(1.0, 2.0)) + std::norm(std::complex(3.0, 4.0)) + std::norm(std::complex(4.0, 5.0))) ); + } + { + multi::thrust::cuda::array res{0.0}; + multi::thrust::cuda::array const xx = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + + blas::nrm2_n(xx.begin(), 3, res.base()); + BOOST_TEST( *res.base() == std::sqrt(1.0*1.0 + 2.0*2.0 + 3.0*3.0) ); + } + + // { + // multi::thrust::cuda::array res{0.0}; + // multi::thrust::cuda::array const xx = {1.0, 2.0, 3.0}; + + // blas::nrm2(xx, res); + // BOOST_TEST( *res.base() == std::sqrt(1.0*1.0 + 2.0*2.0 + 3.0*3.0) ); + // } + + // multi::thrust::cuda::array res{complex{0.0, 0.0}}; + // blas::dot(blas::C(x), y, res); + + // { + // multi::array res_copy{complex{0.0, 0.0}}; + // res_copy = res; + // BOOST_TEST(( *res_copy.base() == complex{14.0, 4.0} )); + // } + // { + // multi::array res_copy{res}; + // BOOST_TEST(( *res_copy.base() == complex{14.0, 4.0} )); + // } +} + +// BOOST_AUTO_TEST_CASE(blas_dot_functional_complex_C) { +// namespace blas = multi::blas; +// using complex = thrust::complex; +// complex const I{0.0, 1.0}; + +// multi::thrust::cuda::array const x = {1.0 + 0.0*I, 2.0 + 0.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming +// multi::thrust::cuda::array const y = {1.0 + 0.0*I, 2.0 + 2.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming + +// complex res = blas::dot(blas::C(x), y); +// BOOST_TEST( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); +// } + +// BOOST_AUTO_TEST_CASE(blas_dot_functional_mutate_complex_C) { +// namespace blas = multi::blas; +// using complex = thrust::complex; +// complex const I{0.0, 1.0}; + +// multi::thrust::cuda::array const x = {1.0 + 0.0*I, 2.0 + 0.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming +// multi::thrust::cuda::array const y = {1.0 + 0.0*I, 2.0 + 2.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming + +// complex res; +// res = blas::dot(blas::C(x), y); +// BOOST_TEST( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); +// } + +return boost::report_errors();} diff --git a/include/multi/adaptors/cuda/cublas/test/scal.cu b/include/boost/multi/adaptors/cuda/cublas/test/scal.cu similarity index 54% rename from include/multi/adaptors/cuda/cublas/test/scal.cu rename to include/boost/multi/adaptors/cuda/cublas/test/scal.cu index ed5276027..0d0bcf6b6 100644 --- a/include/multi/adaptors/cuda/cublas/test/scal.cu +++ b/include/boost/multi/adaptors/cuda/cublas/test/scal.cu @@ -1,22 +1,29 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUBLAS gemv" -#include +// #include -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include #include namespace multi = boost::multi; +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +// #define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) + +int main() { BOOST_AUTO_TEST_CASE(cublas_scal_complex_column) { namespace blas = multi::blas; @@ -32,7 +39,7 @@ BOOST_AUTO_TEST_CASE(cublas_scal_complex_column) { multi::array arr_copy = arr; - BOOST_REQUIRE(( (~arr_copy)[1][2] == complex{20.0, 0.0} )); + BOOST_TEST(( (~arr_copy)[1][2] == complex{20.0, 0.0} )); } BOOST_AUTO_TEST_CASE(cublas_scal_complex) { @@ -46,5 +53,7 @@ BOOST_AUTO_TEST_CASE(cublas_scal_complex) { auto const alpha = complex{2.0, 3.0}; blas::scal(alpha, x); // x <- alpha*x - BOOST_REQUIRE(( x[1] == alpha*x_copy[1] )); -} \ No newline at end of file + BOOST_TEST(( x[1] == alpha*x_copy[1] )); +} + +return boost::report_errors();} diff --git a/include/boost/multi/adaptors/cuda/cublas/test/trsm.cu b/include/boost/multi/adaptors/cuda/cublas/test/trsm.cu new file mode 100644 index 000000000..18c8b1cd5 --- /dev/null +++ b/include/boost/multi/adaptors/cuda/cublas/test/trsm.cu @@ -0,0 +1,134 @@ +// Copyright 2023-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // needs to be first? + +#include +#include + +#include + +namespace multi = boost::multi; +namespace blas = multi::blas; + +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +#define BOOST_REQUIRE_CLOSE(X, Y, ToL) BOOST_TEST( std::abs( (X) - (Y) ) < (ToL) ) + +int main() { + BOOST_AUTO_TEST_CASE(unit_trsm_multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 + 0.0 * I, 3.0 + 1.2 * I, 5.0 - 12.0 * I}, + {0.0 + 0.0 * I, 1.0 + 0.0 * I, 2.1 + 1.1 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 1.0 + 0.0 * I}, + }; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + multi::thrust::cuda::array const A_gpu = A; + multi::thrust::cuda::array B_gpu = B; + + using multi::blas::filling; + using multi::blas::hermitized; + using multi::blas::trsm; + + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::unit, complex{1.0, 0.0}, A, blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† + BOOST_REQUIRE_CLOSE(B[1][0].real(), -43.439999999999998, 0.001); + BOOST_REQUIRE_CLOSE(B[1][0].imag(), -13.000000000000002, 0.001); + + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::unit, complex{1.0, 0.0}, A_gpu, blas::H(B_gpu)); + multi::array B_cpy = B_gpu; + + BOOST_REQUIRE_CLOSE(B_cpy[1][0].real(), -43.439999999999998, 0.001); + BOOST_REQUIRE_CLOSE(B_cpy[1][0].imag(), -13.000000000000002, 0.001); + } + + BOOST_AUTO_TEST_CASE(trsm_multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + multi::thrust::cuda::array const A_gpu = A; + multi::thrust::cuda::array B_gpu = B; + + using multi::blas::filling; + using multi::blas::hermitized; + using multi::blas::trsm; + + // B = ConjugateTranspose[Inverse[A] . ConjugateTranspose[B]] + // ConjugateTranspose[B] = Inverse[A] . ConjugateTranspose[B] + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::non_unit, complex{1.0, 0.0}, A, blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† + BOOST_REQUIRE_CLOSE(B[1][0].real(), -0.72562939983295538, 0.001); + BOOST_REQUIRE_CLOSE(B[1][0].imag(), 0.046772461520104877, 0.001); + + BOOST_REQUIRE_CLOSE(real(blas::H(B)[0][1]), -0.72562939983295538, 0.001); + BOOST_REQUIRE_CLOSE(imag(blas::H(B)[0][1]), -0.046772461520104877, 0.001); + + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::non_unit, complex{1.0, 0.0}, A_gpu, blas::H(B_gpu)); + cudaDeviceSynchronize(); + + multi::array B_cpy = B_gpu; + BOOST_REQUIRE_CLOSE(B_cpy[1][0].real(), -0.72562939983295538, 0.001); + BOOST_REQUIRE_CLOSE(B_cpy[1][0].imag(), 0.046772461520104877, 0.001); + } + + BOOST_AUTO_TEST_CASE(default_param_unit_trsm_multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { + namespace blas = multi::blas; + using complex = thrust::complex; + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array const A = { + {1.0 + 4.0 * I, 3.0 + 0.0 * I, 4.0 - 10.0 * I}, + {0.0 + 0.0 * I, 7.0 - 3.0 * I, 1.0 + 0.0 * I}, + {0.0 + 0.0 * I, 0.0 + 0.0 * I, 8.0 - 2.0 * I}, + }; + // NOLINTNEXTLINE(readability-identifier-length) BLAS naming + multi::array B = { + {1.0 + 1.0 * I, 2.0 + 1.0 * I, 3.0 + 1.0 * I}, + {5.0 + 3.0 * I, 9.0 + 3.0 * I, 1.0 - 1.0 * I}, + }; + + multi::thrust::cuda::array const A_gpu = A; + multi::thrust::cuda::array B_gpu = B; + + using multi::blas::filling; + using multi::blas::hermitized; + using multi::blas::trsm; + + // B = ConjugateTranspose[Inverse[A] . ConjugateTranspose[B]] + // ConjugateTranspose[B] = Inverse[A] . ConjugateTranspose[B] + blas::trsm(blas::side::left, blas::filling::upper, complex{1.0, 0.0}, A, blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† + BOOST_REQUIRE_CLOSE(B[1][0].real(), -0.72562939983295538, 0.001); + BOOST_REQUIRE_CLOSE(B[1][0].imag(), 0.046772461520104877, 0.001); + + BOOST_REQUIRE_CLOSE(real(blas::H(B)[0][1]), -0.72562939983295538, 0.001); + BOOST_REQUIRE_CLOSE(imag(blas::H(B)[0][1]), -0.046772461520104877, 0.001); + + blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::non_unit, complex{1.0, 0.0}, A_gpu, blas::H(B_gpu)); + cudaDeviceSynchronize(); + + multi::array B_cpy = B_gpu; + BOOST_REQUIRE_CLOSE(B_cpy[1][0].real(), -0.72562939983295538, 0.001); + BOOST_REQUIRE_CLOSE(B_cpy[1][0].imag(), 0.046772461520104877, 0.001); + } + return boost::report_errors(); +} diff --git a/include/multi/adaptors/cuda/runtime/error.hpp b/include/boost/multi/adaptors/cuda/runtime/error.hpp similarity index 100% rename from include/multi/adaptors/cuda/runtime/error.hpp rename to include/boost/multi/adaptors/cuda/runtime/error.hpp diff --git a/include/boost/multi/adaptors/cufft.hpp b/include/boost/multi/adaptors/cufft.hpp new file mode 100644 index 000000000..0b765e74b --- /dev/null +++ b/include/boost/multi/adaptors/cufft.hpp @@ -0,0 +1,611 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_CUFFT_HPP +#define BOOST_MULTI_ADAPTORS_CUFFT_HPP + +#include +#include +#include + +#include // for raw_pointer_cast + +#include +#include +#include +#include +#include +#include +#include + +#if !defined(__HIP_ROCclr__) +#include +#include +#endif + +namespace boost::multi::cufft { + +// cuFFT API errors +static auto cuda_get_error_enum(cufftResult error) -> char const* { + switch(error) { + case CUFFT_SUCCESS: return "CUFFT_SUCCESS"; + + case CUFFT_ALLOC_FAILED: return "CUFFT_ALLOC_FAILED"; + case CUFFT_EXEC_FAILED: return "CUFFT_EXEC_FAILED"; +#ifdef CUFFT_INCOMPLETE_PARAMETER_LIST + case CUFFT_INCOMPLETE_PARAMETER_LIST: return "CUFFT_INCOMPLETE_PARAMETER_LIST"; +#endif + case CUFFT_INTERNAL_ERROR: return "CUFFT_INTERNAL_ERROR"; + case CUFFT_INVALID_DEVICE: return "CUFFT_INVALID_DEVICE"; + case CUFFT_INVALID_PLAN: return "CUFFT_INVALID_PLAN"; + case CUFFT_INVALID_SIZE: return "CUFFT_INVALID_SIZE"; + case CUFFT_INVALID_TYPE: return "CUFFT_INVALID_TYPE"; + case CUFFT_INVALID_VALUE: return "CUFFT_INVALID_VALUE"; + case CUFFT_NO_WORKSPACE: return "CUFFT_NO_WORKSPACE"; + case CUFFT_NOT_IMPLEMENTED: return "CUFFT_NOT_IMPLEMENTED"; + case CUFFT_NOT_SUPPORTED: return "CUFFT_NOT_SUPPORTED"; + // #if !defined(__HIP_PLATFORM_NVIDIA__) + // case CUFFT_PARSE_ERROR: return "CUFFT_PARSE_ERROR"; + // #endif + case CUFFT_SETUP_FAILED: return "CUFFT_SETUP_FAILED"; + case CUFFT_UNALIGNED_DATA: return "CUFFT_UNALIGNED_DATA"; + // #if !defined(__HIP_PLATFORM_NVIDIA__) + // case CUFFT_LICENSE_ERROR: return "CUFFT_LICENSE_ERROR"; + // #endif + default: assert(0); + } + return ""; +} + +#define cufftSafeCall(err) implcufftSafeCall(err, __FILE__, __LINE__) +inline void implcufftSafeCall(cufftResult err, const char* file, const int line) { + if(CUFFT_SUCCESS != err) { + std::cerr << "CUFFT error in file " << file << ", line " << line << "\nerror " << err << ": " << cuda_get_error_enum(err) << "\n"; + // fprintf(stderr, "CUFFT error in file '%s', line %d\n %s\nerror %d: %s\nterminating!\n", __FILE__, __LINE__, err, + // _cudaGetErrorEnum(err)); + cudaDeviceReset() == cudaSuccess ? void() : assert(0); + } +} + +class sign { + int impl_ = 0; + + public: + sign() = default; + constexpr explicit sign(int impl) : impl_{impl} {} + constexpr operator int() const { return impl_; } + + constexpr auto operator==(sign const& other) const { return impl_ == other.impl_; } + constexpr auto operator!=(sign const& other) const { return impl_ != other.impl_; } +}; + +constexpr sign forward{CUFFT_FORWARD}; +constexpr sign none{0}; +constexpr sign backward{CUFFT_INVERSE}; +// constexpr sign backward{CUFFT_BACKWARD}; + +static_assert(forward != none && none != backward && backward != forward); + +struct cufft_iodim64 { + std::ptrdiff_t n; + std::ptrdiff_t is; + std::ptrdiff_t os; +}; // based on fftw_iodim64 + +template +class plan { + Alloc alloc_; + ::size_t workSize_ = 0; + void* workArea_{}; + cufftHandle h_{}; // TODO(correaa) put this in a unique_ptr + std::array, DD + 1> which_iodims_{}; + int first_howmany_{}; + + // mutable bool used_ = false; + + using complex_type = cufftDoubleComplex; + + public: + using allocator_type = Alloc; + + template< + class ILayout, class OLayout, dimensionality_type D = std::decay_t::rank::value, + class = std::enable_if_t::rank::value>> + plan(std::array which, ILayout const& in, OLayout const& out) : plan(which, in, out, allocator_type{}) {} + + plan() = delete; + plan(plan const&) = delete; + + plan(plan&& other) noexcept + : alloc_{std::move(other.alloc_)}, + workSize_{std::exchange(other.workSize_, {})}, + workArea_{std::exchange(other.workArea_, {})}, + h_{std::exchange(other.h_, {})}, + which_iodims_{std::exchange(other.which_iodims_, {})}, + first_howmany_{std::exchange(other.first_howmany_, {})} { + // other.used_ = true; // moved-from object cannot be used + // used_ = false; + } + + auto operator=(plan const&) = delete; + auto operator=(plan&&) = delete; + + template< + class ILayout, class OLayout, dimensionality_type D = std::decay_t::rank::value, + class = std::enable_if_t::rank::value>> + plan(std::array which, ILayout const& in, OLayout const& out, allocator_type const& alloc) : alloc_{alloc} { + // used_ = false; + assert(in.sizes() == out.sizes()); + + auto const sizes_tuple = in.sizes(); + auto const istride_tuple = in.strides(); + auto const ostride_tuple = out.strides(); + + using boost::multi::detail::get; + auto which_iodims = std::apply([](auto... elems) { + return std::array, sizeof...(elems) + 1>{ + // TODO(correaa) added one element to avoid problem with gcc 13 static analysis (out-of-bounds) + std::pair{ + get<0>(elems), + cufft_iodim64{get<1>(elems), get<2>(elems), get<3>(elems)} + } + ..., + std::pair{} + }; + }, + boost::multi::detail::tuple_zip(which, sizes_tuple, istride_tuple, ostride_tuple)); + + std::stable_sort(which_iodims.begin(), which_iodims.end() - 1, [](auto const& alpha, auto const& omega) { return get<1>(alpha).is > get<1>(omega).is; }); + + auto const part = std::stable_partition(which_iodims.begin(), which_iodims.end() - 1, [](auto elem) { return std::get<0>(elem); }); + + std::array dims{}; + auto const dims_end = std::transform(which_iodims.begin(), part, dims.begin(), [](auto elem) { return elem.second; }); + + // std::array howmany_dims{}; + // auto const howmany_dims_end = std::transform(part, which_iodims.end() -1, howmany_dims.begin(), [](auto elem) {return elem.second;}); + + which_iodims_ = which_iodims; + first_howmany_ = part - which_iodims.begin(); + + //////////////////////////////////////////////////////////////////////// + + std::array istrides{}; + std::array ostrides{}; + std::array ion{}; + + auto const istrides_end = std::transform(dims.begin(), dims_end, istrides.begin(), [](auto elem) { return elem.is; }); + auto const ostrides_end = std::transform(dims.begin(), dims_end, ostrides.begin(), [](auto elem) { return elem.os; }); + auto const ion_end = std::transform(dims.begin(), dims_end, ion.begin(), [](auto elem) { return elem.n; }); + + int istride = *(istrides_end - 1); + auto inembed = istrides; + inembed.fill(0); + int ostride = *(ostrides_end - 1); + auto onembed = ostrides; + onembed.fill(0); + + for(std::ptrdiff_t idx = 1; idx != ion_end - ion.begin(); ++idx) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) TODO(correaa) replace with algorithm + assert(ostrides[idx - 1] >= ostrides[idx]); + assert(ostrides[idx - 1] % ostrides[idx] == 0); + onembed[idx] = ostrides[idx - 1] / ostrides[idx]; + assert(istrides[idx - 1] % istrides[idx] == 0); + inembed[idx] = istrides[idx - 1] / istrides[idx]; + } + + if(dims_end == dims.begin()) { + throw std::runtime_error{"no ffts in any dimension is not supported"}; + } + + while(first_howmany_ < D - 1) { // NOLINT(altera-id-dependent-backward-branch) TODO(correaa) replace with algorithm + int nelems = 1; + + for(int idx = first_howmany_ + 1; idx != D; ++idx) { + nelems *= which_iodims_[idx].second.n; + } // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) TODO(correaa) replace with algorithm + if( + which_iodims_[first_howmany_].second.is == nelems && which_iodims_[first_howmany_].second.os == nelems + ) { + which_iodims_[first_howmany_ + 1].second.n *= which_iodims_[first_howmany_].second.n; + ++first_howmany_; + } else { + break; + } + } + + if(first_howmany_ == D) { + if constexpr(std::is_same_v) { + assert(dims_end - dims.begin() < 4); // cufft cannot do 4D FFT + cufftSafeCall(::cufftPlanMany( + /*cufftHandle *plan*/ &h_, + /*int rank*/ dims_end - dims.begin(), + /*int *n*/ ion.data(), + /*int *inembed*/ inembed.data(), + /*int istride*/ istride, + /*int idist*/ 1, // stride(first), + /*int *onembed*/ onembed.data(), + /*int ostride*/ ostride, + /*int odist*/ 1, // stride(d_first), + /*cufftType type*/ CUFFT_Z2Z, + /*int batch*/ 1 // BATCH + )); + } else { + cufftSafeCall(cufftCreate(&h_)); + cufftSafeCall(cufftSetAutoAllocation(h_, false)); + cufftSafeCall(cufftMakePlanMany( + /*cufftHandle *plan*/ h_, + /*int rank*/ dims_end - dims.begin(), + /*int *n*/ ion.data(), + /*int *inembed*/ inembed.data(), + /*int istride*/ istride, + /*int idist*/ 1, // stride(first), + /*int *onembed*/ onembed.data(), + /*int ostride*/ ostride, + /*int odist*/ 1, // stride(d_first), + /*cufftType type*/ CUFFT_Z2Z, + /*int batch*/ 1, // BATCH + /*size_t **/ &workSize_ + )); + cufftSafeCall(cufftGetSize(h_, &workSize_)); + workArea_ = ::thrust::raw_pointer_cast(alloc_.allocate(workSize_)); + static_assert(sizeof(Alloc) == 1000); + // auto s = cudaMalloc(&workArea_, workSize_); + // if(s != cudaSuccess) {throw std::runtime_error{"L212"};} + cufftSafeCall(cufftSetWorkArea(h_, workArea_)); + } + if(!h_) { + throw std::runtime_error{"cufftPlanMany null"}; + } + return; + } + + std::sort(which_iodims_.begin() + first_howmany_, which_iodims_.begin() + D, [](auto const& alpha, auto const& omega) { return get<1>(alpha).n > get<1>(omega).n; }); + + if(first_howmany_ == D - 1) { + if constexpr(std::is_same_v) { // NOLINT(bugprone-branch-clone) workaround bug in DeepSource + cufftSafeCall(::cufftPlanMany( + /*cufftHandle *plan*/ &h_, + /*int rank*/ dims_end - dims.begin(), + /*int *n*/ ion.data(), + /*int *inembed*/ inembed.data(), + /*int istride*/ istride, + /*int idist*/ which_iodims_[first_howmany_].second.is, + /*int *onembed*/ onembed.data(), + /*int ostride*/ ostride, + /*int odist*/ which_iodims_[first_howmany_].second.os, + /*cufftType type*/ CUFFT_Z2Z, + /*int batch*/ which_iodims_[first_howmany_].second.n + )); + } else { + cufftSafeCall(cufftCreate(&h_)); + cufftSafeCall(cufftSetAutoAllocation(h_, false)); + cufftSafeCall(cufftMakePlanMany( + /*cufftHandle *plan*/ h_, + /*int rank*/ dims_end - dims.begin(), + /*int *n*/ ion.data(), + /*int *inembed*/ inembed.data(), + /*int istride*/ istride, + /*int idist*/ which_iodims_[first_howmany_].second.is, + /*int *onembed*/ onembed.data(), + /*int ostride*/ ostride, + /*int odist*/ which_iodims_[first_howmany_].second.os, + /*cufftType type*/ CUFFT_Z2Z, + /*int batch*/ which_iodims_[first_howmany_].second.n, + /*size_t **/ &workSize_ + )); + cufftSafeCall(cufftGetSize(h_, &workSize_)); + workArea_ = ::thrust::raw_pointer_cast(alloc_.allocate(workSize_)); + cufftSafeCall(cufftSetWorkArea(h_, workArea_)); + } + if(!h_) { + throw std::runtime_error{"cufftPlanMany null"}; + } + ++first_howmany_; + return; + } + + if(first_howmany_ <= D - 2) { + + int nstreams = which_iodims_[first_howmany_].second.n; + std::vector streams(nstreams); + for(auto& s : streams) { + cudaStreamCreate(&s) == cudaSuccess ?0:throw std::runtime_error{"Failed to create CUDA stream"}; + } + std::vector plans(nstreams); + + std::vector<::size_t> worksizes(nstreams); + std::vector workareas(nstreams); + + for(int idx = 0; idx != nstreams; ++idx) { + if constexpr(std::is_same_v) { // NOLINT(bugprone-branch-clone) workaround bug in DeepSource + std::terminate(); + cufftSafeCall(::cufftPlanMany( + /*cufftHandle *plan*/ &plans[idx], + /*int rank*/ dims_end - dims.begin(), + /*int *n*/ ion.data(), + /*int *inembed*/ inembed.data(), + /*int istride*/ istride, + /*int idist*/ which_iodims_[first_howmany_].second.is, + /*int *onembed*/ onembed.data(), + /*int ostride*/ ostride, + /*int odist*/ which_iodims_[first_howmany_].second.os, + /*cufftType type*/ CUFFT_Z2Z, + /*int batch*/ which_iodims_[first_howmany_].second.n + )); + } else { + std::terminate(); + cufftSafeCall(cufftCreate(&plans[idx])); + cufftSafeCall(cufftSetAutoAllocation(plans[idx], false)); + cufftSafeCall(cufftMakePlanMany( + /*cufftHandle *plan*/ plans[idx], + /*int rank*/ dims_end - dims.begin(), + /*int *n*/ ion.data(), + /*int *inembed*/ inembed.data(), + /*int istride*/ istride, + /*int idist*/ which_iodims_[first_howmany_].second.is, + /*int *onembed*/ onembed.data(), + /*int ostride*/ ostride, + /*int odist*/ which_iodims_[first_howmany_].second.os, + /*cufftType type*/ CUFFT_Z2Z, + /*int batch*/ which_iodims_[first_howmany_].second.n, + /*size_t **/ &workSize_ + )); + cufftSafeCall(cufftGetSize(plans[idx], &worksizes[idx])); + workareas[idx] = ::thrust::raw_pointer_cast(alloc_.allocate(worksizes[idx])); + cufftSafeCall(cufftSetWorkArea(plans[idx], workareas[idx])); + } + if(!plans[idx]) { throw std::runtime_error{"cufftPlanMany null"}; } + } + ++first_howmany_; + return; + } + + // throw std::runtime_error{"cufft not implemented yet"}; + } + + private: + template + void ExecZ2Z_(complex_type const* idata, complex_type* odata, int direction) { + // used_ = true; + cufftSafeCall(cufftExecZ2Z(h_, const_cast(idata), odata, direction)); // NOLINT(cppcoreguidelines-pro-type-const-cast) wrap legacy interface + // cudaDeviceSynchronize(); + } + + public: + template + auto execute(IPtr idata, OPtr odata, int direction) const + -> decltype((void)(reinterpret_cast(::thrust::raw_pointer_cast(idata)), + reinterpret_cast(::thrust::raw_pointer_cast(odata)))) { // TODO(correaa) make const + // used_ = true; + if(first_howmany_ == DD) { + ExecZ2Z_(reinterpret_cast(::thrust::raw_pointer_cast(idata)), reinterpret_cast(::thrust::raw_pointer_cast(odata)), direction); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) wrap a legacy interface + return; + } + if(first_howmany_ == DD - 1) { + if(which_iodims_[first_howmany_].first) { + throw std::runtime_error{"logic error"}; + } + + // std::cout << "doing an inefficient loop of " << which_iodims_[first_howmany_].second.n << std::endl; + + for(int idx = 0; idx != which_iodims_[first_howmany_].second.n; ++idx) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + cufftExecZ2Z( + h_, + const_cast(reinterpret_cast(::thrust::raw_pointer_cast(idata + idx * which_iodims_[first_howmany_].second.is))), // NOLINT(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast) legacy interface + reinterpret_cast(::thrust::raw_pointer_cast(odata + idx * which_iodims_[first_howmany_].second.os)), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) legacy interface + direction + ); + } + return; + } + if(first_howmany_ == DD - 2) { + if(which_iodims_[first_howmany_ + 0].first) { + throw std::runtime_error{"logic error0"}; + } + if(which_iodims_[first_howmany_ + 1].first) { + throw std::runtime_error{"logic error1"}; + } + + // std::cout << "doing an inefficient 2D loop of " << which_iodims_[first_howmany_].second.n << " " << which_iodims_[first_howmany_ + 1].second.n << std::endl; + + for(int idx = 0; idx != which_iodims_[first_howmany_].second.n; ++idx) { // NOLINT(altera-unroll-loops,altera-unroll-loops,altera-id-dependent-backward-branch) TODO(correaa) use an algorithm + for(int jdx = 0; jdx != which_iodims_[first_howmany_ + 1].second.n; ++jdx) { // NOLINT(altera-unroll-loops,altera-unroll-loops,altera-id-dependent-backward-branch) TODO(correaa) use an algorithm + throw std::runtime_error{"complicated loop"}; + cufftExecZ2Z( + h_, + const_cast(reinterpret_cast(::thrust::raw_pointer_cast(idata + idx * which_iodims_[first_howmany_].second.is + jdx * which_iodims_[first_howmany_ + 1].second.is))), // NOLINT(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast) legacy interface + reinterpret_cast(::thrust::raw_pointer_cast(odata + idx * which_iodims_[first_howmany_].second.os + jdx * which_iodims_[first_howmany_ + 1].second.os)), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) legacy interface + direction + ); + } + } + return; + } + throw std::runtime_error{"error2"}; + } + + template void execute_forward(IPtr idata, OPtr odata) { execute(idata, odata, cufft::forward); } + template void execute_backward(IPtr idata, OPtr odata) { execute(idata, odata, cufft::backward); } + + template + void operator()(IPtr idata, OPtr odata, int direction) { + // used_ = true; + ExecZ2Z_(reinterpret_cast(::thrust::raw_pointer_cast(idata)), reinterpret_cast(::thrust::raw_pointer_cast(odata)), direction); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) legacy interface + } + template + auto execute_dft(I&& in, O&& out, int direction) const -> O&& { + // used_ = true; + ExecZ2Z_( + const_cast(reinterpret_cast(base(in))), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) legay interface + const_cast(reinterpret_cast(base(out))), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) legay interface + direction + ); + return std::forward(out); + } + + ~plan() { + if constexpr(!std::is_same_v) { + if(workSize_ > 0) { + alloc_.deallocate(typename std::allocator_traits::pointer(reinterpret_cast(workArea_)), workSize_); + } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) legacy interface + } + if(h_ != 0) { + cufftSafeCall(cufftDestroy(h_)); + } + // if(!used_) { + // std::cerr <<"Warning: cufft plan was never used\n"; + // std::terminate(); + // } + } + + using size_type = int; + using ssize_type = int; +}; + +template +class cached_plan { + typename std::map, multi::layout_t, multi::layout_t>, plan>::iterator it_; + + public: + cached_plan(cached_plan const&) = delete; + cached_plan(cached_plan&&) = delete; + + auto operator=(cached_plan const&) -> cached_plan& = delete; + auto operator=(cached_plan&&) -> cached_plan& = delete; + + ~cached_plan() = default; + + cached_plan(std::array which, boost::multi::layout_t in, boost::multi::layout_t out, Alloc const& alloc = {}) { // NOLINT(fuchsia-default-arguments-declarations) + thread_local std::map, multi::layout_t, multi::layout_t>, plan>& LEAKY_cache = *new std::map, multi::layout_t, multi::layout_t>, plan>; + it_ = LEAKY_cache.find(std::tuple, multi::layout_t, multi::layout_t>{which, in, out}); + if(it_ == LEAKY_cache.end()) { + it_ = LEAKY_cache.insert(std::make_pair(std::make_tuple(which, in, out), plan(which, in, out, alloc))).first; + } + } + template + auto execute(IPtr idata, OPtr odata, int direction) + -> decltype((void)(std::declval< + typename std::map, multi::layout_t, multi::layout_t>, plan>::iterator&>() + ->second.execute(idata, odata, direction))) { + // assert(it_ != LEAKY_cache.end()); + it_->second.execute(idata, odata, direction); + } +}; + +// template::value, int> =0, typename = decltype(::thrust::raw_pointer_cast(std::declval().base()))> +// auto dft(std::array which, In const& in, Out&& out, int sgn) +// ->decltype(cufft::cached_plan{which, in.layout(), out.layout()}.execute(in.base(), out.base(), sgn), std::forward(out)) { +// return cufft::cached_plan{which, in.layout(), out.layout()}.execute(in.base(), out.base(), sgn), std::forward(out); +// } + +template // , std::enable_if_t< multi::has_get_allocator::value, int> =0, typename = decltype(raw_pointer_cast(std::declval().base()))> +auto dft(std::array which, In const& in, Out&& out, int sgn) + -> decltype(cufft::cached_plan::rebind_alloc*/>{which, in.layout(), out.layout() /*, i.get_allocator()*/}.execute(in.base(), out.base(), sgn), std::forward(out)) { + if constexpr(D == 4) { + if(which == std::array{true, true, true, true}) { + auto const [is, js, ks, ls] = in.extensions(); + for(auto i : is) + for(auto j : js) { + cufft::dft({true, true}, in[i][j], out[i][j], sgn); + } + for(auto k : ks) + for(auto l : ls) { + cufft::dft({true, true}, out.rotated().rotated()[k][l], out.rotated().rotated()[k][l], sgn); + } + return std::forward(out); + } + } + return cufft::cached_plan::rebind_alloc*/>{which, in.layout(), out.layout() /*, i.get_allocator()*/}.execute(in.base(), out.base(), sgn), std::forward(out); +} + +template //, std::enable_if_t::value, int> =0> +auto dft_forward(std::array which, In const& in, Out&& out) -> Out&& { + //->decltype(cufft::plan{which, i.layout(), o.layout()}.execute(i.base(), o.base(), cufft::forward), std::forward(o)) { + return cufft::cached_plan{which, in.layout(), out.layout()}.execute(in.base(), out.base(), cufft::forward), std::forward(out); +} + +// template::value, int> =0> +// auto dft_forward(std::array which, In const& i, Out&& o) -> Out&& { +// //->decltype(cufft::plan::rebind_alloc >{which, i.layout(), o.layout(), i.get_allocator()}.execute(i.base(), o.base(), cufft::backward), std::forward(o)) { +// return cufft::cached_plan::rebind_alloc*/>{which, i.layout(), o.layout()/*, i.get_allocator()*/}.execute(i.base(), o.base(), cufft::forward), std::forward(o); } + +template //, std::enable_if_t::value, int> =0> +auto dft_backward(std::array which, In const& in, Out&& out) -> Out&& { + //->decltype(cufft::plan{which, i.layout(), o.layout()}.execute(i.base(), o.base(), cufft::backward), std::forward(o)) { + return cufft::cached_plan{which, in.layout(), out.layout()}.execute(in.base(), out.base(), cufft::backward), std::forward(out); +} + +// template::value, int> =0> +// auto dft_backward(std::array which, In const& i, Out&& o) -> Out&& { +// //->decltype(cufft::plan::rebind_alloc >{which, i.layout(), o.layout(), i.get_allocator()}.execute(i.base(), o.base(), cufft::backward), std::forward(o)) { +// return cufft::cached_plan::rebind_alloc*/>{which, i.layout(), o.layout()/*, i.get_allocator()*/}.execute(i.base(), o.base(), cufft::backward), std::forward(o); } + +template()))>> +BOOST_MULTI_NODISCARD("when first argument is const") +auto dft(In const& in, int sgn) -> R { + static_assert(std::is_trivially_default_constructible{}); + R ret(extensions(in), get_allocator(in)); + cufft::dft(in, ret, sgn); + // if(cudaDeviceSynchronize() != cudaSuccess) throw std::runtime_error{"Cuda error: Failed to synchronize"}; + return ret; +} + +template +constexpr auto array_tail_impl(Array const& arr, std::index_sequence /*unused*/) { + return std::array{} - 1>{std::get(arr)...}; +} + +template +constexpr auto array_tail(Array const& arr) + -> decltype(array_tail_impl(arr, std::make_index_sequence{} - 1>())) { + return array_tail_impl(arr, std::make_index_sequence{} - 1>()); +} + +template +BOOST_MULTI_NODISCARD("when passing a const argument") +auto dft(std::array which, In const& in, int sign) -> std::decay_t { return dft(which, in, typename In::decay_type(extensions(in), get_allocator(in)), sign); } + +template +auto dft(std::array which, In&& in, int sign) + -> decltype(dft(which, in, in, sign), std::forward(in)) { + return dft(which, in, in, sign), std::forward(in); +} + +template BOOST_MULTI_NODISCARD("when passing a const argument") +auto dft_forward(Array arr, A const& in) + -> decltype(cufft::dft(arr, in, cufft::forward)) { + return cufft::dft(arr, in, cufft::forward); +} + +// template NODISCARD("when passing a const argument") +// auto dft_forward(Array arr, multi::cuda::array, D>&& a) +// ->decltype(cufft::dft(arr, a, cufft::forward), multi::cuda::array, D>{}){//assert(0); +// return cufft::dft(arr, a, cufft::forward), std::move(a);} + +template BOOST_MULTI_NODISCARD("when passing a const argument") +auto dft_forward(A const& arr) + -> decltype(cufft::dft(arr, cufft::forward)) { + return cufft::dft(arr, cufft::forward); +} + +template auto dft_backward(As&&... as) + -> decltype(cufft::dft(std::forward(as)..., cufft::backward)) { + return cufft::dft(std::forward(as)..., cufft::backward); +} + +template BOOST_MULTI_NODISCARD("when passing a const argument") +auto dft_backward(Array arr, A const& in) + -> decltype(cufft::dft(arr, in, cufft::backward)) { + return cufft::dft(arr, in, cufft::backward); +} + +template BOOST_MULTI_NODISCARD("when passing a const argument") +auto dft_backward(A const& arr) + -> decltype(cufft::dft(arr, cufft::backward)) { + return cufft::dft(arr, cufft::backward); +} + +} // end namespace boost::multi::cufft + +#endif diff --git a/include/multi/adaptors/cufft/CMakeLists.txt b/include/boost/multi/adaptors/cufft/CMakeLists.txt similarity index 55% rename from include/multi/adaptors/cufft/CMakeLists.txt rename to include/boost/multi/adaptors/cufft/CMakeLists.txt index 6d6b1819a..294077656 100644 --- a/include/multi/adaptors/cufft/CMakeLists.txt +++ b/include/boost/multi/adaptors/cufft/CMakeLists.txt @@ -1,4 +1,3 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- cmake_minimum_required(VERSION 3.11) set(CMAKE_VERBOSE_MAKEFILE ON) @@ -9,16 +8,12 @@ project( LANGUAGES CXX ) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - if(ENABLE_CUDA OR DEFINED CXXCUDA) enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --extended-lambda") + # set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --extended-lambda") endif() -find_package(CUDA QUIET) +# find_package(CUDA QUIET) include_directories(${CUDA_INCLUDE_DIRS}) link_libraries(${CUDA_CUFFT_LIBRARIES}) diff --git a/include/boost/multi/adaptors/cufft/test/CMakeLists.txt b/include/boost/multi/adaptors/cufft/test/CMakeLists.txt new file mode 100644 index 000000000..4d1f8b2ad --- /dev/null +++ b/include/boost/multi/adaptors/cufft/test/CMakeLists.txt @@ -0,0 +1,94 @@ +cmake_minimum_required(VERSION 3.11) + +set(CMAKE_VERBOSE_MAKEFILE ON) + +project( + boost-multi-adaptors-blas-test + VERSION 0.1 + LANGUAGES CXX +) + +# set(CMAKE_CXX_STANDARD 17) +# set(CMAKE_CXX_STANDARD_REQUIRED ON) +# set(CMAKE_CXX_EXTENSIONS OFF) + +if(NOT + CMAKE_CXX_COMPILER_ID + STREQUAL + "PGI" +) + find_package(Boost CONFIG) +else() + find_package(Boost CONFIG) + # find_package(Boost REQUIRED) # cmake cannot detect this component with pgi compiler +endif() + +if(ENABLE_CUDA OR DEFINED CXXCUDA) + enable_language(CUDA) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) + endif() + + # set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --extended-lambda") + # find_package(CUDA QUIET) + find_package(CUDAToolkit REQUIRED COMPONENTS cuFFT) + # if(CUDA_FOUND) + # message("CUDA found") + # include_directories(${CUDA_INCLUDE_DIRS}) + # # set(INQ_CUDA_LIBRARIES ${CUDA_CUFFT_LIBRARIES} ${CUDA_cusolver_LIBRARY} ${CUDA_CUBLAS_LIBRARIES}) + # # link_libraries(${INQ_CUDA_LIBRARIES}) + # else() + # message("CUDA not found") + # endif() +endif() + +# FFTW has to go before blas to avoid unscrupulous (i.e. MKL) blas implementations that include FFTW and don't implement it properly +find_package(PkgConfig) + +if(NOT PKG_CONFIG_FOUND) + message(WARNING "pkgconfig not found (for FFTW). cuFFT tests needs FFTW.") + return() +endif() + +pkg_search_module( + FFTW + REQUIRED + fftw3 + IMPORTED_TARGET +) +include_directories(PkgConfig::FFTW) +link_libraries(PkgConfig::FFTW) + +enable_testing() +list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # needs cmake 3.17 +include(CTest) + +include_directories(${CMAKE_BINARY_DIR}) + +# file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) +set(TEST_SRCS cufft.cpp) + +foreach(TEST_FILE ${TEST_SRCS}) + set(TEST_EXE "${TEST_FILE}.x") + add_executable(${TEST_EXE} ${TEST_FILE}) + if(ENABLE_CUDA OR DEFINED CXXCUDA) + set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) + # target_compile_options(${TEST_EXE} PRIVATE -std=c++17) + endif() + # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) + + target_compile_options( + ${TEST_EXE} PRIVATE + $<$>:-Wno-error=terminate> + ) + + target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) + target_include_directories(${TEST_EXE} PRIVATE ${Boost_INCLUDE_DIRS}) + + target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) + target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) + target_link_libraries(${TEST_EXE} PRIVATE multi) + target_link_libraries(${TEST_EXE} PRIVATE CUDA::cufft) + + add_test(NAME ${TEST_EXE} COMMAND $) +endforeach() diff --git a/include/boost/multi/adaptors/cufft/test/cufft.cpp b/include/boost/multi/adaptors/cufft/test/cufft.cpp new file mode 100644 index 000000000..f69087914 --- /dev/null +++ b/include/boost/multi/adaptors/cufft/test/cufft.cpp @@ -0,0 +1,829 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include + +#include +#include +#include + +#if(!(defined(__HIP_PLATFORM_AMD__) || defined(__HIP_PLATFORM_NVIDIA__))) && (!defined(__HIPCC__)) +#include +#else +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace multi = boost::multi; +using complex = thrust::complex; + +template<> +constexpr bool multi::force_element_trivial_default_construction> = true; + +template +__attribute__((always_inline)) inline void DoNotOptimize(T const& value) { // NOLINT(readability-identifier-naming) consistency with Google benchmark + asm volatile("" : "+m"(const_cast(value))); // NOLINT(hicpp-no-assembler,cppcoreguidelines-pro-type-const-cast) hack +} + +class watch : std::chrono::high_resolution_clock { + std::string label_; + time_point start_; + + public: + explicit watch(char const* label) : label_{label} { + cudaDeviceSynchronize() == cudaSuccess ? void() : assert(0); // NOLINT(misc-include-cleaner) the header is included conditionally + start_ = now(); + } + + watch(watch const&) = delete; + watch(watch&&) = delete; + + auto operator=(watch const&) -> watch& = delete; + auto operator=(watch&&) -> watch& = delete; + + watch() : watch("") {} + ~watch() { + cudaDeviceSynchronize() == cudaSuccess ? void() : assert(0); + auto const count = std::chrono::duration(now() - start_).count(); + std::cerr << label_ << ": " << count << " sec\n"; + } +}; + +using complex = thrust::complex; // this can't be std::complex in the gpu + +struct norm_t { + __host__ __device__ auto operator()(complex const& x) const { + return thrust::norm(x); + } +}; + +auto main() -> int try { + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) + + // BOOST_AUTO_TEST_CASE(cufft_2D, *boost::unit_test::tolerance(0.0001)) + { + auto const in_cpu = multi::array{ + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I} + }; + + { + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft_forward({true, true}, in_cpu, fw_cpu); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + + BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); + BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); + + multi::cufft::plan<2>({true, true}, in_gpu.layout(), fw_gpu.layout()) + .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); + + BOOST_TEST( std::abs((complex(fw_gpu[3][2]) - fw_cpu[3][2]).real()) < 1.0e-8 ); + BOOST_TEST( std::abs((complex(fw_gpu[3][2]) - fw_cpu[3][2]).imag()) < 1.0e-8 ); + + // TODO(correaa) test funcional interface for GPU + // auto const& dft = multi::fft::DFT({true, true}, in_cpu, multi::fft::forward); + + // BOOST_TEST( dft.extensions() == in_cpu.extensions() ); + // BOOST_TEST( (*dft.begin()).size() == (*in_cpu.begin()).size() ); + // BOOST_TEST( (*dft.begin()).extensions() == (*in_cpu.begin()).extensions() ); + + // multi::array const fw_cpu_out = multi::fft::DFT({true, true}, in_cpu, multi::fft::forward); + } + } + { + auto const in_cpu = std::invoke([] { + multi::array ret({20, 20, 20, 20}); + auto const [is, js, ks, ls] = ret.extensions(); + for(auto i : is) + for(auto j : js) + for(auto k : ks) + for(auto l : ls) { + ret[i][j][k][l] = complex{ + static_cast(i + j + k + l), + static_cast(i - j + k - l), + }; + } + return ret; + }); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + + auto const nrm = thrust::transform_reduce( + in_gpu.elements().begin(), in_gpu.elements().end(), + norm_t{}, 0.0, thrust::plus<>{} + ); + + auto fw_gpu = multi::thrust::cuda::array(in_gpu.extensions()); + fw_gpu = in_gpu; + // multi::cufft::plan<4>({true, true, true, true}, in_gpu.layout(), fw_gpu.layout()) + // .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); + + // cudaDeviceSynchronize() == cudaSuccess ? void() : throw std::runtime_error{"cuda error"}; + + // auto const nrm_fwd = thrust::transform_reduce( + // fw_gpu.elements().begin(), fw_gpu.elements().end(), + // norm_t{}, 0.0, thrust::plus<>{} + // ); + // std::cout << "norm: " << nrm*20.0*20.0 << ", norm forward: " << nrm_fwd << '\n'; + // BOOST_TEST( nrm_fwd == nrm*20.0*20.0 ); + } + { + auto const in_cpu = multi::array{ + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I} + }; + + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft({false, true}, in_cpu, fw_cpu, multi::fftw::forward); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + + BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); + BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); + + multi::cufft::plan<2>({false, true}, in_gpu.layout(), fw_gpu.layout()) + .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); + + BOOST_TEST( thrust::abs(complex(fw_gpu[3][2]) - fw_cpu[3][2]) < 1e-12 ); + } + { + auto const in_cpu = multi::array{ + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I} + }; + + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft({false, true}, in_cpu, fw_cpu, multi::fftw::forward); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + + BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); + BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); + + for(int i = 0; i != in_gpu.size(); ++i) { + multi::cufft::plan<1>({true}, in_gpu[i].layout(), fw_gpu[i].layout()) + .execute(in_gpu[i].base(), fw_gpu[i].base(), multi::cufft::forward); + } + + BOOST_TEST( thrust::abs(complex(fw_gpu[3][2]) - fw_cpu[3][2]) < 1.0e-12 ); + } + { + auto const in_cpu = multi::array{ + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I} + }; + + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft({false, true}, in_cpu, fw_cpu, multi::fftw::forward); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + auto fw_gpu2 = multi::thrust::cuda::array(extensions(in_gpu)); + auto fw_gpu3 = multi::thrust::cuda::array(extensions(in_gpu)); + + BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); + BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); + + for(int i = 0; i != in_gpu.size(); ++i) { + multi::cufft::plan<1>({true}, in_gpu[i].layout(), fw_gpu[i].layout()) + .execute(in_gpu[i].base(), fw_gpu[i].base(), multi::cufft::forward); + } + + multi::cufft::plan<2>({false, true}, in_gpu.layout(), fw_gpu2.layout()) + .execute(in_gpu.base(), fw_gpu2.base(), multi::cufft::forward); + + BOOST_TEST( abs(complex(fw_gpu[3][2]) - fw_cpu[3][2]) < 1e-10 ); + BOOST_TEST( abs(complex(fw_gpu[3][2]) - complex(fw_gpu2[3][2])) < 1e-10 ); + } + + { + auto const in_cpu = multi::array{ + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I} + }; + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft({false, true}, in_cpu, fw_cpu, multi::fftw::forward); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto const fw_gpu = multi::cufft::dft({false, true}, in_gpu, multi::cufft::forward); + + BOOST_TEST( abs(fw_cpu[3][2]) != 0.0 ); + + BOOST_TEST( abs(complex(fw_gpu[3][2]) - fw_cpu[3][2]) < 1e-10 ); + } + { + auto const in_cpu = multi::array{ + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I} + }; + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft({true, false}, in_cpu, fw_cpu, multi::fftw::forward); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto const fw_gpu = multi::cufft::dft({true, false}, in_gpu, multi::cufft::forward); + + BOOST_TEST( fw_cpu.extensions() == in_cpu.extensions() ); + BOOST_TEST( abs(fw_cpu[3][2]) != 0.0 ); + + BOOST_TEST( fw_gpu.extensions() == in_gpu.extensions() ); + BOOST_TEST( abs(complex(fw_gpu[3][2]) - fw_cpu[3][2]) < 1e-10 ); + BOOST_TEST( abs(complex(fw_gpu[2][1]) - fw_cpu[2][1]) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(cufft_1D_combinations, *boost::unit_test::tolerance(0.0001)) + { + using complex = thrust::complex; // this can't be std::complex in the gpu + + auto const in_cpu = std::invoke([] { + multi::array ret({128}, complex{}); + std::default_random_engine generator; + std::uniform_real_distribution distribution(1.0, 88.0); + + std::generate( + reinterpret_cast(ret.data_elements()), + reinterpret_cast(ret.data_elements() + ret.num_elements()), [&] { return distribution(generator); } + ); + return ret; + }); + + for(auto c : std::vector>{ + {true} //, + // {false}, + }) { + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + + BOOST_TEST( complex(in_gpu[31]).real() == in_cpu[31].real() ); + BOOST_TEST( complex(in_gpu[31]).imag() == in_cpu[31].imag() ); + + auto fw_cpu = multi::array(extensions(in_cpu)); + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + + auto p_cpu = multi::fftw::plan::forward(c, in_cpu.base(), in_cpu.layout(), fw_cpu.base(), fw_cpu.layout()); + auto p_gpu = multi::cufft::plan<1>(c, in_gpu.layout(), fw_gpu.layout()); + + BOOST_TEST( abs(complex(in_gpu[31]) - in_cpu[31]) < 1e-10 ); + + p_cpu.execute(in_cpu.base(), fw_cpu.base()); + p_gpu.execute_forward(in_gpu.base(), fw_gpu.base()); + + BOOST_TEST( abs(fw_cpu[31]) != 0.0 ); + + BOOST_TEST( abs( complex(in_gpu[31]) - in_cpu[31]) < 1e-10 ); + BOOST_TEST( abs( complex(fw_gpu[31]) - fw_cpu[31]) < 1e-10 ); + } + } + + // BOOST_AUTO_TEST_CASE(cufft_2D_combinations, *boost::unit_test::tolerance(0.0001)) + { + + using complex = thrust::complex; // this can't be std::complex in the gpu + + auto const in_cpu = std::invoke([] { + multi::array ret({10, 20}); + std::default_random_engine generator; + std::uniform_real_distribution distribution(-1.0, 1.0); + + std::generate( + reinterpret_cast(ret.data_elements()), + reinterpret_cast(ret.data_elements() + ret.num_elements()), [&] { return distribution(generator); } + ); + return ret; + }); + + for(auto c : std::vector>{ + { true, true}, + { true, false}, + {false, true}, // {false, false} + }) { + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft(c, in_cpu, fw_cpu, multi::fftw::forward); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + + BOOST_TEST( abs(fw_cpu[2][1]) != 0.0 ); + + multi::cufft::plan<2>(c, in_gpu.layout(), fw_gpu.layout()) + .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); + + BOOST_TEST( abs(complex(fw_gpu[2][1]) - fw_cpu[2][1]) < 1e-10 ); + } + } + + // BOOST_AUTO_TEST_CASE(cufft_2D_combinations_inplace, *boost::unit_test::tolerance(0.0001)) + { + + using complex = thrust::complex; // this can't be std::complex in the gpu + + auto const in_cpu = std::invoke([] { + multi::array ret({10, 20}); + std::default_random_engine generator; + std::uniform_real_distribution distribution(-1.0, 1.0); + + std::generate( + reinterpret_cast(ret.data_elements()), + reinterpret_cast(ret.data_elements() + ret.num_elements()), [&] { return distribution(generator); } + ); + return ret; + }); + + for(auto c : std::vector>{ + { true, true}, + { true, false}, + {false, true} //, + // {false, false} + }) { + auto fw_cpu = in_cpu; + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + + multi::fftw::dft(c, fw_cpu, multi::fftw::forward); + + auto fw_gpu = in_gpu; + + BOOST_TEST( abs(fw_cpu[2][1]) != 0.0 ); + + multi::cufft::plan<2>(c, fw_gpu.layout(), fw_gpu.layout()) + .execute(fw_gpu.base(), fw_gpu.base(), multi::cufft::forward); + + BOOST_TEST( abs(complex(fw_gpu[2][1]) - fw_cpu[2][1]) < 1e-10 ); + } + } + + // BOOST_AUTO_TEST_CASE(cufft_3D, *boost::unit_test::tolerance(0.0001)) + { + + using complex = thrust::complex; // this can't be std::complex in the gpu + + auto const in_cpu = std::invoke([] { + multi::array ret({10, 20, 30}); + std::default_random_engine generator; + std::uniform_real_distribution distribution(-1.0, 1.0); + + std::generate( + reinterpret_cast(ret.data_elements()), + reinterpret_cast(ret.data_elements() + ret.num_elements()), [&] { return distribution(generator); } + ); + return ret; + }); + + for(auto c : std::vector>{ + { true, true, true}, + { true, true, false}, + { true, false, true}, + { true, false, false}, + {false, true, true}, + {false, true, false}, + {false, false, true} //, + // {false, false, false} + }) { + auto fw_cpu = multi::array(extensions(in_cpu)); + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + + multi::fftw::dft(c, in_cpu, fw_cpu, multi::fftw::forward); + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + + multi::cufft::dft(c, in_gpu, fw_gpu, multi::cufft::forward); + + BOOST_TEST( abs(fw_cpu[3][2][1]) != 0.0 ); + + BOOST_TEST( abs(complex(fw_gpu[3][2][1]) - fw_cpu[3][2][1]) < 1e-10 ); + } + } + + // BOOST_AUTO_TEST_CASE(cufft_3D_inplace, *boost::unit_test::tolerance(0.0001)) + { + + using complex = thrust::complex; // this can't be std::complex in the gpu + + auto const in_cpu = std::invoke([] { + multi::array ret({10, 20, 30}); + std::default_random_engine generator; + std::uniform_real_distribution distribution(-1.0, 1.0); + + std::generate( + reinterpret_cast(ret.data_elements()), + reinterpret_cast(ret.data_elements() + ret.num_elements()), [&] { return distribution(generator); } + ); + return ret; + }); + + for(auto c : std::vector>{ + { true, true, true}, + { true, true, false}, + { true, false, true}, + { true, false, false}, + {false, true, true}, + {false, true, false}, + {false, false, true} //, + // {false, false, false} + }) { + auto fw_cpu = in_cpu; + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + + multi::fftw::dft(c, fw_cpu, multi::fftw::forward); + auto fw_gpu = in_gpu; + + multi::cufft::plan<3>(c, fw_gpu.layout(), fw_gpu.layout()) + .execute(fw_gpu.base(), fw_gpu.base(), multi::cufft::forward); + + BOOST_TEST( abs(fw_cpu[3][2][1]) != 0.0 ); + + // std::cerr << "case " << c[0] << " " << c[1] << " " << c[2] << std::endl; + // std::cerr << complex(fw_gpu[3][2][1]) - fw_cpu[3][2][1] << std::endl; + // BOOST_TEST( abs(complex(fw_gpu[3][2][1]) - fw_cpu[3][2][1]) < 1e-10 ); + // TODO(correaa), these two cases are failing + // case 1 1 1 * (-34.154,-39.0958) + // case 1 1 0 (0,-1.77636e-15) + // case 1 0 1 * (-12.6338,0.299744) + // case 1 0 0 * (4.44089e-16,-4.44089e-16) + // case 0 1 1 (20.1121,-10.8888) + // case 0 1 0 * (0,-2.22045e-16) + // case 0 0 1 (-0.348103,4.32914) + } + } + + // BOOST_AUTO_TEST_CASE(cufft_4D, *boost::unit_test::tolerance(0.0001) + { + + using complex = thrust::complex; // this can't be std::complex in the gpu + + auto const in_cpu = std::invoke([] { + multi::array ret({10, 20, 30, 40}); + std::default_random_engine generator; + std::uniform_real_distribution distribution(-1.0, 1.0); + + std::generate( + reinterpret_cast(ret.data_elements()), + reinterpret_cast(ret.data_elements() + ret.num_elements()), [&] { return distribution(generator); } + ); + return ret; + }); + + for(auto c : std::vector>{ + {true , true , true , true }, + { true, true, true, false}, + { true, true, false, true}, + { true, true, false, false}, + { true, false, true, true}, + { true, false, true, false}, + { true, false, false, true}, + { true, false, false, false}, + {false, true, true, true}, + {false, true, true, false}, + {false, true, false, true}, + {false, true, false, false}, + {false, false, true, true}, + {false, false, true, false}, + {false, false, false, true} //, + // {false, false, false, false} + }) { + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft(c, in_cpu, fw_cpu, multi::fftw::forward); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + + BOOST_TEST( abs(fw_cpu[4][3][2][1]) != 0 ); + + multi::cufft::dft(c, in_gpu, fw_gpu, multi::cufft::forward); + + std::cerr << "Case " << c[0] << " " << c[1] << " " << c[2] << " " << c[3] << ": " << complex(fw_gpu[4][3][2][1]) - fw_cpu[4][3][2][1] << '\n'; + + BOOST_TEST( abs(complex(fw_gpu[4][3][2][1]) - fw_cpu[4][3][2][1]) < 1e-10 ); + } + } + + // SO 4D intermediate answer: + { + using complex = thrust::complex; // this can't be std::complex in the gpu + + auto const in_cpu = std::invoke([] { + multi::array ret({12, 128, 128, 4}); + std::default_random_engine generator; + std::uniform_real_distribution distribution(-1.0, 1.0); + + std::generate( + ret.elements().begin(), ret.elements().end(), [&] { return distribution(generator); } + ); + return ret; + }); + + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft({false, true, true, false}, in_cpu, fw_cpu, multi::fftw::forward); + + auto const in_gpu = multi::thrust::cuda::array{in_cpu}; + auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); + + BOOST_TEST( abs(fw_cpu[4][3][2][1]) != 0.0 ); + + multi::cufft::dft({false, true, true, false}, in_gpu, fw_gpu, multi::cufft::forward); + + BOOST_TEST( abs(complex(fw_gpu[4][3][2][1]) - fw_cpu[4][3][2][1]) < 1e-10 ); + } + // small case 99 + { + using complex = thrust::complex; // this can't be std::complex in the gpu + + auto const in_cpu = std::invoke([] { + multi::array ret({12, 128, 128, 4}); + std::default_random_engine generator; + std::uniform_real_distribution distribution(-1.0, 1.0); + + std::generate( + ret.elements().begin(), ret.elements().end(), [&] { return distribution(generator); } + ); + return ret; + }); + + multi::thrust::cuda::array in({12, 128, 128, 4}); + in = in_cpu; + + multi::thrust::cuda::array ou({12, 128, 128, 4}, 0.0); + + multi::cufft::dft_forward({false, true, true, false}, in, ou); + + std::cout << "small case : " << ou[4][3][2][1] << '\n'; + } + + return boost::report_errors(); +} catch(...) { + throw; + return 1; +} + +// #if 0 + +// } + +// BOOST_AUTO_TEST_CASE(check_thrust_complex_vs_std_complex, *boost::unit_test::tolerance(0.0001)){ + +// multi::array, 1> const s_in = {1.0 + I*2.0, 2.0 + I*3.0, 3.0 + I*4.0}; +// multi::array, 1> const t_in = {1.0 + I*2.0, 2.0 + I*3.0, 3.0 + I*4.0}; + +// multi::array, 1> s_out(s_in.extensions()); +// multi::array, 1> t_out(t_in.extensions()); + +// multi::fftw::plan::forward({true}, s_in.base(), s_in.layout(), s_out.base(), s_out.layout()).execute(s_in.base(), s_out.base()); +// multi::fftw::plan::forward({true}, t_in.base(), t_in.layout(), t_out.base(), t_out.layout()).execute(t_in.base(), t_out.base()); + +// BOOST_REQUIRE( std::equal(s_out.begin(), s_out.end(), t_out.begin()) ); +// } + +// BOOST_AUTO_TEST_CASE(small_1D_cpu_vs_cpu, *boost::unit_test::tolerance(0.0001)){ + +// multi::array, 1> const cpu_in = {1.0 + I*2.0, 2.0 + I*3.0, 3.0 + I*4.0}; +// multi::thrust::cuda::array, 1> const gpu_in = {1.0 + I*2.0, 2.0 + I*3.0, 3.0 + I*4.0}; + +// multi::array, 1> cpu_out(cpu_in.extensions()); +// multi::thrust::cuda::array, 1> gpu_out(gpu_in.extensions()); + +// multi::fftw::plan::forward({true}, cpu_in.base(), cpu_in.layout(), cpu_out.base(), cpu_out.layout()).execute (cpu_in.base(), cpu_out.base()); +// multi::cufft::plan<1> ({true}, gpu_in.layout(), gpu_out.layout()).execute_forward(gpu_in.base(), gpu_out.base()); +// } + +// BOOST_AUTO_TEST_CASE(cufft_3D_timing, *boost::unit_test::tolerance(0.0001)){ + +// auto x = multi::extensions_t<3>{300, 300, 300}; +// { +// auto const in_cpu = multi::array(x, 10.0); +// BOOST_ASSERT( in_cpu.num_elements()*sizeof(complex) < 2e9 ); +// auto fw_cpu = multi::array(extensions(in_cpu), 99.0); +// { +// // boost::timer::auto_cpu_timer t; // 1.041691s wall, 1.030000s user + 0.000000s system = 1.030000s CPU (98.9%) +// multi::fftw::dft_forward({true, true}, in_cpu, fw_cpu); +// BOOST_TEST( fw_cpu[8][9][10] != 99.0 ); +// } + +// auto const in_gpu = multi::thrust::cuda::array{in_cpu}; // (x, 10.0); +// cudaDeviceSynchronize()==cudaSuccess?void():assert(0); +// { +// auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu), 99.0); +// cudaDeviceSynchronize()==cudaSuccess?void():assert(0); +// // boost::timer::auto_cpu_timer t; // 0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%) +// boost::multi::cufft::dft({true, true}, in_gpu, fw_gpu, multi::cufft::forward); +// cudaDeviceSynchronize()==cudaSuccess?void():assert(0); +// BOOST_TEST( (static_cast(fw_gpu[8][9][10]) - fw_cpu[8][9][10]).real() == 0.0 ); +// BOOST_TEST( (static_cast(fw_gpu[8][9][10]) - fw_cpu[8][9][10]).imag() == 0.0 ); +// } +// { +// // boost::timer::auto_cpu_timer t; // 0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%) +// auto const fw_gpu2 = boost::multi::cufft::dft({true, true}, in_gpu, multi::cufft::forward); +// cudaDeviceSynchronize()==cudaSuccess?void():assert(0); +// BOOST_TEST( (static_cast(fw_gpu2[8][9][10]) - fw_cpu[8][9][10]).real() == 0.0 ); +// BOOST_TEST( (static_cast(fw_gpu2[8][9][10]) - fw_cpu[8][9][10]).imag() == 0.0 ); +// } +// } + +// #if 1 +// { +// multi::thrust::cuda::universal_array const in_gpu(x, 10.); +// multi::thrust::cuda::universal_array fw_gpu(extensions(in_gpu), 99.); + +// // multi::cuda::managed::array const in_gpu(x, 10.); +// // multi::cuda::managed::array fw_gpu(extensions(in_gpu), 99.); +// { +// // boost::timer::auto_cpu_timer t; // 0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%) +// multi::cufft::dft({true, true}, in_gpu, fw_gpu, multi::cufft::forward); +// // BOOST_TEST( fw_gpu[8][9][10].operator complex() != 99. ); +// } +// { +// // boost::timer::auto_cpu_timer t; // 0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%) +// multi::cufft::dft({true, true}, in_gpu, fw_gpu, multi::cufft::forward); +// // BOOST_TEST( fw_gpu[8][9][10].operator complex() != 99. ); +// } +// } +// #endif +// } + +// #if 0 + +// BOOST_AUTO_TEST_CASE(cufft_combinations, *utf::tolerance(0.00001)){ + +// auto const in = []{ +// multi::array ret({32, 90, 98, 96}); +// std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), +// [](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};} +// ); +// return ret; +// }(); +// std::clog<<"memory size "<< in.num_elements()*sizeof(complex)/1e6 <<" MB\n"; + +// multi::thrust::cuda::universal_array const in_gpu = in; +// multi::thrust::cuda::universal_array const in_mng = in; + +// using std::clog; +// for(auto c : std::vector>{ +// {false, true , true , true }, +// {false, true , true , false}, +// {true , false, false, false}, +// {true , true , false, false}, +// {false, false, true , false}, +// {false, false, false, false}, +// }){ +// std::clog<<"case "; copy(begin(c), end(c), std::ostream_iterator{std::clog,", "}); std::clog< out = in; +// multi::array in_rw = in; +// [&, _ = watch{"cpu_opl "}]{ +// multi::fftw::dft_forward(c, in, out); +// }(); +// [&, _ = watch{"cpu_ipl "}]{ +// multi::fftw::dft(c, in_rw, multi::fftw::forward); +// // BOOST_TEST( abs( static_cast>(in_rw[5][4][3][1]) - multi::complex(out[5][4][3][1]) ) == 0. ); +// }(); +// { +// multi::array in_rw2 = in; +// [&, _ = watch{"cpu_mov "}]{ +// multi::array const out_mov = multi::fftw::dft_forward(c, std::move(in_rw2)); +// // what(out_mov); +// // BOOST_TEST( abs( static_cast>(out_mov[5][4][3][1]) - multi::complex(out[5][4][3][1]) ) == 0. ); +// BOOST_REQUIRE( is_empty(in_rw2) ); +// BOOST_REQUIRE( extensions(out_mov) == extensions(in) ); +// }(); +// } + +// [&, _ = watch{"cpu_new "}]{ +// auto const out_cpy = multi::fftw::dft_forward(c, in); +// BOOST_TEST( abs( static_cast>(out_cpy[5][4][3][1]) - std::complex(out[5][4][3][1]) ) == 0. ); +// }(); +// multi::thrust::cuda::array out_gpu(extensions(in_gpu)); +// [&, _ = watch{"gpu_opl "}]{ +// multi::cufft::dft(c, in_gpu , out_gpu, multi::cufft::forward); +// BOOST_TEST( abs( static_cast(out_gpu[5][4][3][1]) - out[5][4][3][1] ) == 0. ); +// }(); +// { +// multi::thrust::cuda::array in_rw_gpu = in_gpu; +// [&, _ = watch{"gpu_ipl "}]{ +// multi::cufft::dft(c, in_rw_gpu, multi::cufft::forward); +// BOOST_TEST( abs( static_cast(in_rw_gpu[5][4][3][1]) - out[5][4][3][1] ) == 0. ); +// }(); +// } +// { +// multi::thrust::cuda::array in_rw_gpu = in_gpu; +// [&, _ = watch{"gpu_mov "}]{ +// multi::thrust::cuda::array const out_mov = multi::cufft::dft_forward(c, std::move(in_rw_gpu)); +// // BOOST_REQUIRE( in_rw_gpu.empty() ); +// // BOOST_TEST( abs( static_cast(out_mov[5][4][3][1]) - out[5][4][3][1] ) == 0. ); +// }(); +// } +// { +// multi::thrust::cuda::array in_rw_gpu = in_gpu; +// [&, _ = watch{"gpu_mov "}]{ +// multi::thrust::cuda::array out_mov = std::move(in_rw_gpu); +// multi::cufft::dft(c, out_mov, multi::cufft::forward); +// // BOOST_REQUIRE( in_rw_gpu.empty() ); +// // BOOST_TEST( abs( static_cast(out_mov[5][4][3][1]) - out[5][4][3][1] ) == 0. ); +// }(); +// } +// cudaDeviceSynchronize(); +// [&, _ = watch{"gpu_new "}]{ +// multi::thrust::cuda::array const out_cpy = multi::cufft::dft(c, in_gpu, multi::cufft::forward); +// }(); +// multi::thrust::cuda::universal_array out_mng(extensions(in_mng)); +// [&, _ = watch{"mng_cld "}]{ +// multi::cufft::dft(c, in_mng, out_mng, multi::cufft::forward); +// BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. ); +// }(); +// [&, _ = watch{"mng_hot "}]{ +// multi::cufft::dft(c, in_mng , out_mng, multi::cufft::forward); +// BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. ); +// }(); +// [&, _ = watch{"mng_new "}]{ +// auto const out_mng = multi::cufft::dft(c, in_mng, multi::cufft::forward); +// BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. ); +// }(); +// } +// // std::clog<<"cache size " +// // << multi::cufft::plan::cache<1>().size() <<' ' +// // << multi::cufft::plan::cache<2>().size() <<' ' +// // << multi::cufft::plan::cache<3>().size() <<' ' +// // << multi::cufft::plan::cache<4>().size() <<' ' +// // < ret({45, 18, 32, 16}); +// std::generate( +// ret.data_elements(), ret.data_elements() + ret.num_elements(), +// [](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};} +// ); +// return ret; +// }(); + +// multi::thrust::cuda::array const in = in_cpu; +// multi::thrust::cuda::array out(extensions(in)); + +// #if 0 +// multi::cufft::many_dft(begin(unrotated(in)), end(unrotated(in)), begin(unrotated(out)), +1); + +// multi::array out_cpu(extensions(in)); +// multi::fft::many_dft(begin(unrotated(in_cpu)), end(unrotated(in_cpu)), begin(unrotated(out_cpu)), +1); + +// BOOST_TEST( imag( static_cast(out[5][4][3][2]) - out_cpu[5][4][3][2]) == 0. ); +// #endif +// } + +// #if 0 +// BOOST_AUTO_TEST_CASE(cufft_4D, *utf::tolerance(0.00001) ){ +// auto const in = []{ +// multi::array ret({10, 10, 10}); +// std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), +// [](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};} +// ); +// return ret; +// }(); + +// multi::array out(extensions(in)); +// // multi::fftw::dft({true, false, true}, in, out, multi::fftw::forward); +// multi::fftw::many_dft(begin(in.rotated()), end(in.rotated()), begin(out.rotated()), multi::fftw::forward); + +// multi::thrust::cuda::array in_gpu = in; +// multi::thrust::cuda::array out_gpu(extensions(in)); + +// // multi::cufft::dft({true, false, true}, in_gpu, out_gpu, multi::fft::forward);//multi::cufft::forward); +// // multi::cufft::many_dft(begin(in_gpu.rotated()), end(in_gpu.rotated()), begin( out_gpu.rotated() ), multi::fftw::forward); +// // BOOST_TEST( ( static_cast(out_gpu[5][4][3]) - out[5][4][3]).imag() == 0. ); +// } +// #endif +// #endif + +// #endif diff --git a/include/boost/multi/adaptors/fft.hpp b/include/boost/multi/adaptors/fft.hpp new file mode 100644 index 000000000..084106549 --- /dev/null +++ b/include/boost/multi/adaptors/fft.hpp @@ -0,0 +1,254 @@ +// Copyright 2020-2025 Alfredo A. Correa + +#ifndef BOOST_MULTI_ADAPTORS_FFT_HPP +#define BOOST_MULTI_ADAPTORS_FFT_HPP + +#include +#include // for extensions_t, get, array + +#if defined(__CUDA__) || defined(__NVCC__) +#include +#elif defined(__HIPCC__) +#include +#endif + +// #include // for extensions_t, const_subarray, get + +#include // for array +#include // for size_t +#include // for random_access_iterator_tag +#include // for apply +#include // for decay_t, conditional_t, true_type +#include // for forward +// IWYU pragma: no_include // for get + +#define BOOST_MULTI_DECLRETURN_(ExpR) \ + ->decltype(ExpR) { return ExpR; } // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing +#define BOOST_MULTI_JUSTRETURN_(ExpR) \ + ->decltype(auto) { return ExpR; } // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing + +namespace boost::multi::fft { + +static inline int const forward = static_cast(fftw::forward); +static inline int const none = static_cast(fftw::none); +static inline int const backward = static_cast(fftw::backward); + +// static_assert( forward != none && none != backward && backward != forward ); + +template struct priority : std::conditional_t> {}; + +template auto dft_aux(priority<0> /*unused*/, Args&&... args) BOOST_MULTI_DECLRETURN_(fftw::dft(std::forward(args)...)) +#if defined(__CUDA__) || defined(__NVCC__) || defined(__HIPCC__) + template + auto dft_aux(priority<1> /*unused*/, Args&&... args) BOOST_MULTI_DECLRETURN_(::boost::multi::cufft ::dft(std::forward(args)...)) +#endif + template + auto dft(Args&&... args) BOOST_MULTI_DECLRETURN_(dft_aux_(priority<1>{}, std::forward(args)...)) + + template + auto dft(std::array::dimensionality> which, In&& in, Args&&... args) BOOST_MULTI_DECLRETURN_(dft_aux(priority<1>{}, which, std::forward(in), std::forward(args)...)) + + template + auto dft_forward_aux(priority<0> /*unused*/, Args&&... args) BOOST_MULTI_DECLRETURN_(fftw::dft_forward(std::forward(args)...)) +#if defined(__CUDA__) || defined(__NVCC__) || defined(__HIPCC__) + template + auto dft_forward_aux(priority<1> /*unused*/, Args&&... args) BOOST_MULTI_DECLRETURN_(cufft ::dft_forward(std::forward(args)...)) +#endif + template + auto dft_forward(std::array::dimensionality> which, In&& in, Args&&... args) BOOST_MULTI_DECLRETURN_(dft_forward_aux(priority<1>{}, which, std::forward(in), std::forward(args)...)) + + template + auto dft_backward_aux(priority<0> /*unused*/, Args&&... args) BOOST_MULTI_DECLRETURN_(fftw::dft_backward(std::forward(args)...)) +#if defined(__CUDA__) || defined(__NVCC__) || defined(__HIPCC__) + template + auto dft_backward_aux(priority<1> /*unused*/, Args&&... args) BOOST_MULTI_DECLRETURN_(cufft ::dft_backward(std::forward(args)...)) +#endif + template + auto dft_backward(std::array which, In const& in, Args&&... args) -> decltype(auto) { + return dft_backward_aux(priority<1>{}, which, in, std::forward(args)...); +} + +template +class dft_range { + public: + static constexpr auto dimensionality = std::decay_t::dimensionality; + auto operator+() const { + multi::array::element_type, dimensionality> ret = *this; + return ret; + } + + private: + std::array which_; + In in_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + Direction dir_; + + struct const_iterator : private std::decay_t::const_iterator { // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + // static constexpr auto dimensionality = In::const_iterator::dimensionality; + + private: + bool do_; + std::array sub_which_; + Direction dir_; + + public: + const_iterator() = default; + const_iterator( + typename std::decay_t::const_iterator it, + bool doo, std::array::dimensionality - 1> sub_which, + Direction dir + ) : std::decay_t::const_iterator{it}, do_{doo}, sub_which_{sub_which}, dir_{dir} {} + + using typename std::decay_t::const_iterator::difference_type; + using typename std::decay_t::const_iterator::value_type; + using pointer = void; // void*; + using reference = dft_range::const_iterator::reference, Direction>; + using iterator_category = std::random_access_iterator_tag; + + auto operator+(difference_type n) const { return const_iterator{static_cast::const_iterator const&>(*this) + n, do_, sub_which_, dir_}; } + friend auto operator-(const_iterator const& lhs, const_iterator const& rhs) { + return static_cast::const_iterator const&>(lhs) - static_cast::const_iterator const&>(rhs); + } + + auto operator++() -> const_iterator&; + auto operator--() -> const_iterator&; + + auto operator++(int) -> const_iterator; + auto operator--(int) -> const_iterator; + + auto operator==(const_iterator const& other) const -> bool; + auto operator!=(const_iterator const& other) const -> bool; + + // auto operator*() const -> reference; + + class fake_array { + multi::extensions_t extensions_; + + public: + explicit fake_array(multi::extensions_t ext) : extensions_{ext} {} + auto extensions() const { return extensions_; } + // multi::size_t size_; + auto extension() const { + using std::get; + return get<0>(extensions()); + } + auto size() const { return extension().size(); } + }; + + auto operator*() const -> fake_array { + fake_array fa{(*static_cast::const_iterator const&>(*this)).extensions()}; + return fa; + } + + private: + template + auto copy_(const_iterator const& last, It const& first_d) const -> decltype(auto) { + auto const count = last - *this; + dft( + std::apply([doo = do_](auto... es) { return std::array{doo, es...}; }, sub_which_), + multi::const_subarray::const_iterator::element, dimensionality, typename std::decay_t::const_iterator::element_ptr>( + static_cast::const_iterator const&>(*this), + static_cast::const_iterator const&>(last) + ), + multi::subarray::element, dimensionality, typename It::element_ptr>( + first_d, first_d + count + ), + fftw::sign{dir_} + ); + return first_d + count; + } + + public: + template + auto capy(const_iterator const& last, It const& first_d) const -> decltype(auto) { + return copy_(last, first_d); + } + + template + friend auto copy(const_iterator const& first, const_iterator const& last, It const& first_d) -> decltype(auto) { + return first.copy_(last, first_d); + } + + template + friend auto copy_n(const_iterator const& first, Size const& count, It const& first_d) -> decltype(auto) { + return first.copy_(first + count, first_d); + } + + template + friend auto uninitialized_copy_n(const_iterator const& first, Size const& count, It const& first_d) -> decltype(auto) { + return copy_n(first, count, first_d); + } + + template + friend auto uninitialized_copy(const_iterator const& first, const_iterator const& last, It const& first_d) -> decltype(auto) { + return first.copy_(last, first_d); + } + }; + + public: + template + dft_range(std::array::dimensionality> which, In2&& in, Direction dir) : which_{which}, in_(std::forward(in)), dir_{dir} {} + auto begin() const { + return const_iterator(in_.begin(), which_[0], std::apply([](auto /*e0*/, auto... es) { return std::array{es...}; }, which_), dir_); + } + auto end() const { + return const_iterator(in_.end(), which_[0], std::apply([](auto /*e0*/, auto... es) { return std::array{es...}; }, which_), dir_); + } + + auto extensions() const { return in_.extensions(); } + auto size() const { return in_.size(); } +}; + +template +auto dft(std::array::dimensionality> which, In&& in, Direction dir) { + return dft_range(which, std::forward(in), dir); +} + +template +auto dft(std::array::dimensionality> which, In&& in) { + return dft(which, std::forward(in), fft::forward); +} + +// #if defined(__clang__) +// #pragma clang diagnostic push +// #pragma clang diagnostic ignored "-Wunused-value" +// #elif defined(__GNUC__) +// #pragma GCC diagnostic push +// #pragma GCC diagnostic ignored "-Wunused-value" +// #endif +template +auto dft_all(In&& in) { + auto const all_true = std::apply([](auto... es) { return std::array{((void)es, true)...}; }, std::array::dimensionality>{}); + return dft(all_true, std::forward(in), fft::forward); +} + +template +auto idft_all(In&& in) { + auto const all_true = std::apply([](auto... es) { return std::array{(es, true)...}; }, std::array::dimensionality>{}); + return dft(all_true, std::forward(in), fft::backward); +} +// #if defined(__clang__) +// #pragma clang diagnostic pop +// #elif defined(__GNUC__) +// #pragma GCC diagnostic pop +// #endif +template +auto idft(std::array which, In&& in) { + return dft(which, std::forward(in), fft::forward); +} + +template +auto dft_forward(std::array::dimensionality> which, In&& in) { + return dft(which, std::forward(in), fft::forward); +} + +template +auto dft_backward(std::array which, In&& in) { + return dft(which, std::forward(in), fft::backward); +} + +} // end namespace boost::multi::fft + +#undef BOOST_MULTI_DECLRETURN_ +#undef BOOST_MULTI_JUSTRETURN_ + +#endif // BOOST_MULTI_ADAPTORS_FFT_HPP diff --git a/include/boost/multi/adaptors/fftw.hpp b/include/boost/multi/adaptors/fftw.hpp new file mode 100644 index 000000000..ac34d5ec4 --- /dev/null +++ b/include/boost/multi/adaptors/fftw.hpp @@ -0,0 +1,679 @@ +// Copyright 2018-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_FFTW_HPP +#define BOOST_MULTI_ADAPTORS_FFTW_HPP + +#include + +#include // IWYU pragma: export + +#include // sort +#include +#include +#include // accumulate + +#ifdef HAVE_FFTW3_THREADS +#include +#endif + +#include // external fftw3 library + +namespace boost::multi { +namespace fftw { + +using std::as_const; + +struct flags { + using underlying_type = decltype(FFTW_PRESERVE_INPUT); // NOLINT(hicpp-signed-bitwise) : macro definition in external library + + private: + underlying_type underlying_; + + public: + constexpr explicit flags(underlying_type underlying) : underlying_{underlying} {} + constexpr explicit operator underlying_type() const { return underlying_; } + friend constexpr auto operator|(flags f1, flags f2) { return flags{f1.underlying_ | f2.underlying_}; } +}; + +constexpr flags estimate{FFTW_ESTIMATE}; // NOLINT(hicpp-signed-bitwise) : defined in an external lib 1U << 6 +constexpr flags measure{FFTW_MEASURE}; + +constexpr flags preserve_input{FFTW_PRESERVE_INPUT}; // NOLINT(hicpp-signed-bitwise) : defined in an external lib 1U << 4 +// // NOLINT(): this is a defect in FFTW https://github.com/FFTW/fftw3/issues/246 + +} // end namespace fftw + +// template +// auto fftw_plan_dft_1d( +// Size N, +// std::complex const* in, std::complex* out, int sign, +// unsigned flags = FFTW_ESTIMATE +// ){ +// #ifndef NDEBUG +// auto check = in[N/3]; // check that const data will not been overwritten +// #endif +// assert( fftw::alignment_of(in) == fftw::alignment_of(out) ); +// auto ret=::fftw_plan_dft_1d(N, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT ); +// assert(check == in[N/3]); // check that const data has not been overwritten +// return ret; +// } + +// template +// auto fftw_plan_dft_1d( +// Size N, +// std::complex* in, std::complex* out, int sign, +// unsigned flags = FFTW_ESTIMATE +// ){ +// assert( fftw::alignment_of(in) == fftw::alignment_of(out) ); +// return ::fftw_plan_dft_1d(N, (fftw_complex*)in, (fftw_complex*)out, sign, flags); +// } + +// template +// auto fftw_plan_dft_2d( +// Size N1, Size N2, +// std::complex const* in, std::complex* out, int sign, +// unsigned flags = FFTW_ESTIMATE +// ){ +// assert( fftw::alignment_of(in) == fftw::alignment_of(out) ); +// #ifndef NDEBUG +// auto check = in[N1*N2/3]; // check that const data will not been overwritten +// #endif +// auto ret = ::fftw_plan_dft_2d(N1, N2, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT); +// assert( check == in[N1*N2/3] ); // check that const data has not been overwritten +// return ret; +// } + +// template +// auto fftw_plan_dft_2d( +// Size N1, Size N2, +// std::complex* in, std::complex* out, int sign, +// unsigned flags = FFTW_ESTIMATE +// ){ +// assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out)); +// return ::fftw_plan_dft_2d(N1, N2, (fftw_complex*)in, (fftw_complex*)out, sign, flags); +// } + +// template +// auto fftw_plan_dft_3d( +// Size N1, Size N2, Size N3, +// std::complex* in, std::complex* out, int sign, +// unsigned flags = FFTW_ESTIMATE +// ){ +// assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out)); +// return ::fftw_plan_dft_3d(N1, N2, N3, (fftw_complex*)in, (fftw_complex*)out, sign, flags); +// } +// template +// auto fftw_plan_dft_3d( +// Size N1, Size N2, Size N3, +// std::complex const* in, std::complex* out, int sign, +// unsigned flags = FFTW_ESTIMATE +// ){ +// assert( flags & FFTW_PRESERVE_INPUT ); +// assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out)); +// return ::fftw_plan_dft_3d(N1, N2, N3, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT); +// } + +// template +// auto fftw_plan_dft( +// Rank r, int* ns, +// std::complex* in, std::complex* out, +// int sign, unsigned flags = FFTW_ESTIMATE +// ){ +// assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out)); +// return ::fftw_plan_dft(r, ns, (fftw_complex*)in, (fftw_complex*)out, sign, flags); +// } +// template +// auto fftw_plan_dft( +// RankType r, int* ns, +// std::complex const* in, std::complex* out, +// int sign, unsigned flags = FFTW_ESTIMATE | FFTW_PRESERVE_INPUT +// ){ +// assert( flags & FFTW_PRESERVE_INPUT ); +// assert(fftw::alignment_of(in) == fftw::alignment_of(out)); +// #ifndef NDEBUG +// size_t ne = 1; for(RankType i = 0; i != r; ++i) ne*=ns[i]; +// auto check = in[ne/3]; // check that const data will not been overwritten +// #endif +// auto ret=::fftw_plan_dft(r, ns, (fftw_complex*)in, (fftw_complex*)out, sign, flags); +// assert(check == in[ne/3]); // check that const data has not been overwritten +// return ret; +// } + +// template +// auto fftw_plan_dft_1d( +// In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE +// ){ +// static_assert(in.dimensionality == 1, "!"); assert(size(in) == size(out)); +// assert( in.is_compact() ); assert( out.is_compact() ); +// return multi::fftw_plan_dft_1d(size(in), data_elements(in), data_elements(out), sign, flags); +// } + +// template +// auto fftw_plan_dft_2d( +// In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE +// ){ +// static_assert(in.dimensionality == 2, "!"); assert(in.sizes() == out.sizes()); +// assert( in.is_compact() ); assert( out.is_compact() ); +// return multi::fftw_plan_dft_2d( +// sizes(in)[0], sizes(in)[1], +// data_elements(in), data_elements(out), sign, flags +// ); +// } + +// template +// auto fftw_plan_dft_3d( +// In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE +// ){ +// static_assert(in.dimensionality == 3, "!"); assert(in.sizes() == out.sizes()); +// assert( in.is_compact() ); assert( out.is_compact() ); +// return multi::fftw_plan_dft_3d( +// sizes(in)[0], sizes(in)[1], sizes(in)[2], +// data(in), data(out), +// sign, flags +// ); +// } + +// template +// constexpr auto to_array(Tpl const& tpl) { +// return std::apply( +// [](auto const&... elems) { return std::array::value>{static_cast(elems)...}; }, +// tpl +// ); +// } + +// template< +// typename It1, class It2, +// std::enable_if_t{} || std::is_convertible*>{}, int> = 0> +// auto fftw_plan_many_dft(It1 first, It1 last, It2 d_first, int sign, fftw::flags flags) +// -> fftw_plan { + +// static_assert(sizeof(*base(first)) == sizeof((*base(first)).real()) + sizeof((*base(first)).imag()), "input must have complex pod layout"); +// static_assert(sizeof(*base(first)) == sizeof(fftw_complex), "input must have complex pod layout"); +// static_assert(sizeof(*base(d_first)) == sizeof((*base(d_first)).real()) + sizeof((*base(d_first)).imag()), "output must have complex pod layout"); +// static_assert(sizeof(*base(d_first)) == sizeof(fftw_complex), "output must have complex pod layout"); + +// assert(strides(*first) == strides(*last)); +// assert(sizes(*first) == sizes(*d_first)); + +// auto const ssn_tuple = multi::detail::tuple_zip(strides(*first), strides(*d_first), sizes(*first)); +// auto ssn = std::apply([](auto... ssn) { +// using boost::multi::detail::get; +// return std::array, sizeof...(ssn)>{ +// boost::multi::detail::mk_tuple(static_cast(get<0>(ssn)), static_cast(get<1>(ssn)), static_cast(get<2>(ssn)))...}; +// }, +// ssn_tuple); +// std::sort(ssn.begin(), ssn.end(), std::greater<>{}); + +// auto const istrides = [&]() { +// std::array::rank::value> istrides{}; +// using boost::multi::detail::get; +// std::transform(ssn.begin(), ssn.end(), istrides.begin(), [](auto elem) { return get<0>(elem); }); +// return istrides; +// }(); + +// auto const ostrides = [&]() { +// std::array::rank::value> ostrides{}; +// using boost::multi::detail::get; +// std::transform(ssn.begin(), ssn.end(), ostrides.begin(), [](auto elem) { return get<1>(elem); }); +// return ostrides; +// }(); +// assert(std::is_sorted(ostrides.begin(), ostrides.end(), std::greater<>{})); // otherwise ordering is incompatible + +// auto const ion = [&]() { +// std::array::rank::value> ion{}; +// using boost::multi::detail::get; +// std::transform(ssn.begin(), ssn.end(), ion.begin(), [](auto elem) { return get<2>(elem); }); +// return ion; +// }(); + +// auto const inembed = [&]() { +// std::array::rank::value + 1> inembed{}; +// std::adjacent_difference( +// istrides.rbegin(), istrides.rend(), inembed.rbegin(), [](auto alpha, auto omega) {assert(omega != 0 && alpha%omega == 0); return alpha/omega; } +// ); +// return inembed; +// }(); + +// auto const onembed = [&]() { +// std::array::rank::value + 1> onembed{}; +// std::adjacent_difference( +// ostrides.rbegin(), ostrides.rend(), onembed.rbegin(), [](auto alpha, auto omega) {assert(omega != 0 && alpha%omega == 0); return alpha/omega; } +// ); +// return onembed; +// }(); + +// auto ret = ::fftw_plan_many_dft( +// /*int rank */ ion.size(), +// /*const int* n */ ion.data(), +// /*int howmany */ last - first, +// /*fftw_complex* in */ reinterpret_cast(const_cast*>(static_cast const*>(base(first)))), // NOLINT(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast) input data +// /*const int* inembed */ inembed.data(), +// /*int istride */ istrides.back(), +// /*int idist */ stride(first), +// /*fftw_complex* out */ reinterpret_cast(static_cast*>(base(d_first))), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) adapt types +// /*const int* onembed */ onembed.data(), +// /*int ostride */ ostrides.back(), +// /*int odist */ stride(d_first), +// /*int */ sign, +// /*unsigned */ static_cast(flags) +// ); +// assert(ret); // if you get null here it could be because your library doesn't support this fftw call mode +// return ret; +// } + +// template< +// typename It1, class It2, +// std::enable_if_t{} || std::is_convertible*>{}, int> = 0> +// auto fftw_plan_many_dft(It1 first, It1 last, It2 d_first, int sign) +// -> fftw_plan { +// return fftw_plan_many_dft(first, last, d_first, sign, fftw::estimate); +// } + +template +auto fftw_plan_dft(std::array which, InPtr in_base, In const& in_layout, OutPtr out_base, Out const& out_layout, int sign, fftw::flags /*flags*/) -> fftw_plan { + assert(in_layout.extensions() == out_layout.extensions()); + + auto const sizes_tuple = in_layout.sizes(); + + auto const istride_tuple = in_layout.strides(); + auto const ostride_tuple = out_layout.strides(); + + using boost::multi::detail::get; + auto which_iodims = std::apply( + [](auto... elems) { + // clang-format off + return std::array, sizeof...(elems) + 1>{ // added one element to avoid problem with gcc 13 static analysis (out-of-bounds) + std::pair{get<0>(elems), fftw_iodim64{get<1>(elems), get<2>(elems), get<3>(elems)}}..., {}, // added one element to avoid problem with gcc 13 static analysis (out-of-bounds) + }; + // clang-format on + }, + boost::multi::detail::tuple_zip(which, sizes_tuple, istride_tuple, ostride_tuple) + ); + auto const part = std::stable_partition(which_iodims.begin(), std::prev(which_iodims.end()), [](auto elem) { return std::get<0>(elem); }); + + std::array dims{}; + std::array howmany_dims{}; + + auto const dims_end = std::transform(which_iodims.begin(), part, dims.begin(), [](auto elem) { return elem.second; }); + auto const howmany_dims_end = std::transform(part, std::prev(which_iodims.end()), howmany_dims.begin(), [](auto elem) { return elem.second; }); + + assert(in_base); + assert(out_base); + + assert((sign == -1) || (sign == +1)); + + decltype(FFTW_ESTIMATE) flags = FFTW_MEASURE; + if(out_base || (!std::is_rvalue_reference_v)) { + flags = FFTW_ESTIMATE; + } + if(in_base && std::is_rvalue_reference_v) { + flags |= FFTW_DESTROY_INPUT; + } else { + flags |= FFTW_PRESERVE_INPUT; + } + + // auto* const out_base_digested = [](auto&& ref) { return &ref; }(out_base); // workaround to take address of out_base when it returns an rvalue + auto* const out_base_digested = &out_base; + + fftw_plan ret = fftw_plan_guru64_dft( + /*int rank */ static_cast(dims_end - dims.begin()), + /*const fftw_iodim64 *dims */ dims.data(), + /*int howmany_rank */ static_cast(howmany_dims_end - howmany_dims.begin()), + /*const fftw_iodim *howmany_dims */ howmany_dims.data(), + /*fftw_complex *in */ const_cast(reinterpret_cast(/*static_cast const *>*/ (in_base))), // NOLINT(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast) //NOSONAR FFTW is taken as non-const while it is really not touched + /*fftw_complex *out */ (reinterpret_cast(/*static_cast *>*/ out_base_digested)), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + sign, flags // FFTW_ESTIMATE | FFTW_PRESERVE_INPUT + ); + + assert(ret && "fftw lib returned a null plan, if you are using MKL check the limitations of their fftw interface"); + // https://software.intel.com/content/www/us/en/develop/documentation/mkl-developer-reference-c/top/appendix-d-fftw-interface-to-intel-math-kernel-library/fftw3-interface-to-intel-math-kernel-library/using-fftw3-wrappers.html + return ret; +} + +template(detail::implicit_cast*>(base(std::declval()))))> // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : interact with legacy code +auto fftw_plan_dft(In const& in, Out&& out, int dir) { + return fftw_plan_dft(in, std::forward(out), dir, fftw::estimate); +} + +namespace fftw { + +inline auto initialize_threads() -> bool { +#ifdef HAVE_FFTW3_THREADS + return fftw_init_threads(); +#else + return false; +#endif +} + +// enum class sign : decltype(FFTW_FORWARD) { // NOLINT(performance-enum-size) +// backward = FFTW_BACKWARD, +// none = 0, +// forward = FFTW_FORWARD, +// }; + +class sign { + decltype(FFTW_FORWARD) value_; + + public: + constexpr explicit sign(decltype(FFTW_FORWARD) value) noexcept : value_{value} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + + static sign const backward; + static sign const none ; + static sign const forward ; + + constexpr explicit operator decltype(FFTW_FORWARD)() const noexcept { return value_; } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) +}; + +inline sign const sign::backward{FFTW_BACKWARD}; +inline sign const sign::none {0}; +inline sign const sign::forward {FFTW_FORWARD}; + +inline auto const backward = sign::backward; +inline auto const none = sign::none; +inline auto const forward = sign::forward; + +// static_assert(forward != none && none != backward && backward != forward); + +enum class direction : decltype(FFTW_FORWARD) { // NOLINT(performance-enum-size) + backward = FFTW_BACKWARD, + none = 0, + forward = FFTW_FORWARD +}; + +class plan; + +class environment { + static void cleanup_() { ::fftw_cleanup(); } + static void set_timelimit_(std::chrono::duration limit) { + ::fftw_set_timelimit(limit.count()); + } + static void unset_timelimit_() { ::fftw_set_timelimit(FFTW_NO_TIMELIMIT); } + + public: + environment() = default; + + environment(environment const&) = delete; + environment(environment&&) = delete; + + auto operator=(environment const&) -> environment& = delete; + auto operator=(environment&&) -> environment& = delete; + + template + auto make_plan_forward(std::array which, In const& in, Out&& out); + template + auto make_plan_backward(std::array which, In const& in, Out&& out); + + ~environment() { cleanup_(); } +}; + +class plan { + plan() : impl_{nullptr, &fftw_destroy_plan} {} + // std::shared_ptr const> impl_; + std::unique_ptr, decltype(&fftw_destroy_plan)> impl_; + + public: + plan(plan const&) = delete; + plan(plan&&) = default; + ~plan() = default; + + template + explicit plan( + std::array which, + InPtr in_base, In in_layout, + OutPtr out_base, Out out_layout, sign ss + ) : impl_{fftw_plan_dft(which, in_base, in_layout, out_base, out_layout, static_cast(ss), fftw::estimate), &fftw_destroy_plan} { + assert(impl_); + } + + template + static auto forward(std::array which, InPtr in_base, In in_layout, OutPtr out_base, Out out_layout) { + return plan(which, in_base, in_layout, out_base, out_layout, fftw::forward); + } + template + static auto backward(std::array which, InPtr in_base, In in_layout, OutPtr out_base, Out out_layout) { + return plan(which, in_base, in_layout, out_base, out_layout, fftw::backward); + } + + template + void execute(In* in, Out* out) const { // this is `const` because https://github.com/FFTW/fftw3/pull/314#issuecomment-1712818399 + static_assert(sizeof(in->imag()) == sizeof(double)); + static_assert(sizeof(out->imag()) == sizeof(double)); + + static_assert(sizeof(*in) == sizeof(fftw_complex)); + static_assert(sizeof(*out) == sizeof(fftw_complex)); + + ::fftw_execute_dft( + const_cast(impl_.get()), // NOLINT(cppcoreguidelines-pro-type-const-cast) https://www.fftw.org/fftw3_doc/Thread-safety.html + const_cast(reinterpret_cast(in)), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) //NOSONAR to interface with legacy fftw + reinterpret_cast(out) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) : to interface with legacy fftw + ); + } + + // template void execute(I&& in, O&& out) const { execute_dft(std::forward(in), std::forward(out)); } + // friend void execute(plan const& self) { self.execute(); } + + auto operator=(plan&&) -> plan& = default; + auto operator=(plan const&) -> plan& = delete; + + [[nodiscard]] auto cost() const -> double { return fftw_cost(const_cast(impl_.get())); } // NOLINT(cppcoreguidelines-pro-type-const-cast) + [[nodiscard]] auto flops() const { + double add; // NOLINT(cppcoreguidelines-init-variables) + double mul; // NOLINT(cppcoreguidelines-init-variables) + double fma; // NOLINT(cppcoreguidelines-init-variables) + + fftw_flops(const_cast(impl_.get()), &add, &mul, &fma); + + return add + mul + fma; // <- if FMA supported, otherwise: add + mul + 2*fma; + } + +#ifdef HAVE_FFTW3_THREADS + public: + static void make_thread_safe() { + fftw_make_planner_thread_safe(); // needs linking to -lfftw3_threads, requires FFTW-3.3.6 or greater + is_thread_safe_ = true; + } + static int with_nthreads(int n) { + fftw_plan_with_nthreads(n); + nthreads_ = n; + return n; + } + static int with_nthreads() { + int n = std::thread::hardware_concurrency(); + return with_nthreads(n ? n : 2); + } + static bool is_thread_safe() { return is_thread_safe_; } + static bool nthreads() { return nthreads_; } + + private: + static bool is_thread_safe_; + static int nthreads_; + static bool initialized_threads_; +#else + static constexpr auto is_thread_safe() -> bool { return false; } + static constexpr auto nthreads() -> bool { return true; } + static constexpr auto with_nthreads() -> int { return 1; } +#endif +}; + +template +class io_zip_iterator { + InIt in_; + OutIt out_; + std::shared_ptr planP_; + + public: + io_zip_iterator(std::array which, InIt in, OutIt out, sign ss) + : in_{in}, out_{out}, + planP_{std::make_shared( + which, + in_.base(), in_->layout(), + out_.base(), out_->layout(), + ss + )} + {} + + auto operator++() -> io_zip_iterator& { + ++in_; + ++out_; + return *this; + } + + auto operator+=(std::ptrdiff_t n) -> io_zip_iterator& { + in_ += n; + out_ += n; + return *this; + } + auto operator==(io_zip_iterator const& other) const { + assert(planP_ == other.planP_); // provenance + return in_ == other.in_ && out_ == other.out_; + } + auto operator!=(io_zip_iterator const& other) const { return !(*this == other); } + + void execute() const { + auto out_base = [](auto&& ref) {return &ref; }(*out_.base()); + planP_->execute(in_.base(), out_base); + } + auto operator*() const -> decltype(*out_) { execute(); return *out_; } + + io_zip_iterator(io_zip_iterator const&) = default; + io_zip_iterator(io_zip_iterator&&) noexcept = default; + + ~io_zip_iterator() = default; + + auto operator=(io_zip_iterator const&) -> io_zip_iterator& = default; + auto operator=(io_zip_iterator&&) noexcept -> io_zip_iterator& = default; +}; + +template +io_zip_iterator(std::array, InIt, OutIt, sign) +-> io_zip_iterator; + +template +auto environment::make_plan_forward(std::array which, In const& in, Out&& out) { + return plan::forward(which, in, std::forward(out)); +} + +template +auto environment::make_plan_backward(std::array which, In const& in, Out&& out) { + return plan::backward(which, in, std::forward(out)); +} + +#ifdef HAVE_FFTW3_THREADS +bool plan::is_thread_safe_ = (plan::make_thread_safe(), true); +int plan::nthreads_ = (initialize_threads(), with_nthreads()); +#endif + +using std::decay_t; + +template +auto dft(std::array which, In const& in, Out&& out, sign dir) + -> decltype(plan{which, in.base(), in.layout(), out.base(), out.layout(), dir}.execute(in.base(), out.base()), std::forward(out)) { + return plan{which, in.base(), in.layout(), out.base(), out.layout(), dir}.execute(in.base(), out.base()), std::forward(out); +} + +template::rank::value, + std::enable_if_t().base()), typename std::decay_t::element>, int> = 0> // NOLINT(modernize-use-constraints) for C++20 +auto dft(std::array which, In&& in, sign dir) + -> decltype(dft(which, in, in, dir), std::forward(in)) { + return dft(which, in, in, dir), std::forward(in); +} + +template +auto dft_forward(std::array which, A const& in, O&& out) + -> decltype(fftw::dft(which, in, std::forward(out), fftw::forward)) { + return fftw::dft(which, in, std::forward(out), fftw::forward); +} + +template +auto dft_backward(std::array which, A const& in, O&& out) + -> decltype(fftw::dft(which, in, std::forward(out), fftw::backward)) { + return fftw::dft(which, in, std::forward(out), fftw::backward); +} + +template auto dft_backward(A&&... args) + -> decltype(dft(std::forward(args)..., fftw::backward)) { + return dft(std::forward(args)..., fftw::backward); +} + +// template::decay_type> +// auto move(In&& in) { +// if(in.is_compact()) { +// multi::array_ref Ref( +// in.base(), extensions(in) +// ); +// copy(in, Ref); +// return R( +// multi::array_ref>(std::make_move_iterator(in.mbase()), ((in.mbase()=0), extensions(Ref))) +// ); +// } +// return copy(std::forward(in)); +// } + +template +using static_array = ::boost::multi::static_array>; + +template +using array = ::boost::multi::array>; + +template> +auto copy(multi::subarray>&& array) -> R { + if(array.is_compact()) { + return fftw::copy( + array.template static_array_cast(), + multi::array_ref(array.base().base(), array.extensions()) + ) + .template static_array_cast>(); + } + return fftw::copy(std::move(array).template static_array_cast()); +} + +template +auto transpose(Array& array) + -> decltype(fftw::copy(transposed(array), array.reshape(extensions(layout(array).transpose())))) { + multi::array_ref const ref_aux(array.base(), extensions(array)); + return fftw::copy(ref_aux.transposed(), array.reshape(layout(array).transpose().extensions())); +} + +} // end namespace fftw +} // end namespace boost::multi + +namespace boost::multi::fftw { + +template +class fft_iterator { + MDIterator base_; + + std::array which_ = {}; + + public: + using iterator_type = MDIterator; + + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = std::nullptr_t; + + class reference { + typename MDIterator::reference::extensions_type x_; + explicit reference(typename MDIterator::reference const& ref) : x_{ref.extensions()} {} + friend class fft_iterator; + + public: + using extensions_type = typename MDIterator::reference::extensions_type; + auto extensions() const -> extensions_type { return x_; } + }; + + using iterator_category = std::random_access_iterator_tag; // using iterator_category = std::input_iterator_tag; + + explicit fft_iterator(iterator_type base, std::array which) noexcept : base_{std::move(base)}, which_{which} {} + + friend auto operator-(fft_iterator const& self, fft_iterator const& other) -> difference_type { + return self.base_ - other.base_; + } + + auto operator*() const { return reference{*base_}; } +}; + +} // end namespace boost::multi::fftw +#endif diff --git a/include/boost/multi/adaptors/fftw/CMakeLists.txt b/include/boost/multi/adaptors/fftw/CMakeLists.txt new file mode 100644 index 000000000..f9fd41828 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.11) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +add_library(multi_fftw3 INTERFACE) + +find_package(PkgConfig) +if(PKG_CONFIG_FOUND) + pkg_search_module( + FFTW + fftw3 + IMPORTED_TARGET + ) + if(FFTW_FOUND) + # include_directories(PkgConfig::FFTW) this seems to be always incorrect + link_libraries(PkgConfig::FFTW) + target_link_libraries(multi_fftw3 INTERFACE PkgConfig::FFTW) + + include_directories(${CMAKE_BINARY_DIR}) + + add_subdirectory(test) +# add_subdirectory(mpi) + else() + message(WARNING "Cannot find FFTW, FFTW-adaptor will not be tested. If you want this feature install FFTW, for example please run:" + "\n sudo apt install pkg-config libfftw3-dev" + "\n sudo dnf install fftw-devel # in Fedora" + "\n brew install fftw pkg-config") + endif() +else() + message(WARNING "Cannot find PkgConfig and/or FFTW, FFTW-adaptor will not be tested. If you want this feature install PkgConfig and FFTW, for example please run:" + "\n sudo apt install pkg-config libfftw3-dev" + "\n sudo dnf install fftw-devel # in Fedora") + + find_package(FFTW3 COMPONENTS fftw3) # or find_package(FFTW3 COMPONENTS fftw3f fftw3) for specific components + + if(FFTW3_FOUND) + message(WARNING "Found FFTW3") + target_link_libraries(multi_fftw3 INTERFACE FFTW3::fftw3) + add_subdirectory(test) + + else() + message(WARNING "Cannot find FFTW3, FFTW3-adaptor will not be tested. If you want this feature install PkgConfig and FFTW, for example please run:" + "\n sudo apt install libfftw3-dev" + "\n sudo dnf install fftw-devel # in Fedora" + "\n vcpkg install fftw3 # on Windows") + endif() +endif() diff --git a/include/boost/multi/adaptors/fftw/README.md b/include/boost/multi/adaptors/fftw/README.md new file mode 100644 index 000000000..a5b8c9584 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/README.md @@ -0,0 +1,74 @@ + +# [Boost.]MultiAdaptors.FFTW + +> **Disclosure: This is not an official or accepted Boost library and is unrelated to the std::mspan proposal.** + +_© Alfredo A. Correa, 2018-2023_ + +`Multi` is a modern C++ library that provides access and manipulation of data in multidimensional arrays. +Algorithms on multidimensional array data structures are fundamental to several branches of computing. +Multiple libraries implement these algorithms, and some are specially tuned to specific systems and hardware. + +Linear algebra and Fourier transforms are some examples of operations with algorithms on regularly contiguous (strided) multidimensional array data structures. +Although not generic, these libraries are the best options in specific systems for specific element types. + +## Contents +[[_TOC_]] + +## FFTW + +FFTW is a C library for computing the discrete Fourier transform (DFT) in one or more dimensions for real and complex data. +It is the defacto interface for many implementations, including Intel's MKL FFT. + +The FFTW adaptor provides two ways to use the library: one is through plan objects, and the other is through functions. + +Plans are runtime-optimized algorithms that tune the DFT operation for specific array sizes and layouts known in advance. +Plans reserve resources and precalculate parameters utilized during the execution. + +Plans are created from array layouts with dimensionality `D` that sample the input and output. + +```cpp +auto p = multi::fftw::plan::[forward|backward]({which...}, in_layout, out_layout); +``` + +Input and output layout must have the same associated sizes. +`{which...}` is a set of (at most D) boolean value that determined which dimensions are transformed; for example `{true, true, ...}` performs the FFT on all directions, ``{false, true, false, ...}` for the second dimension only and `{false, false, ...}` doesn't perform any Fourier transform, effectively performing a element-wise copy or transposition. + +The plans can be later executed (many times if necessary) as: +```cpp +p.execute(in_base, out_base); +``` + +Executions of the same plan (or over the same data) are not thread-safe (the plan has internal buffers that are modified), so the `.execute` function is not marked `const`. + +The use pattern of the FFTW adaptor (and the original FFTW) interface is somewhat entangled. +The plan construction takes the arrays, and the execute takes the internal pointers to array data. + +There is a convenience function that generates and executes the plan consistently: +```cpp +template +auto&& multi::fftw::dft::forward({which, ...}, In const& in, Out&& out) { + multi::fftw::plan::forward(in.layout(), out.layout()).execute(in.base(), out.base()); + return std::forward(out); +} +``` + + diff --git a/include/boost/multi/adaptors/fftw/benchmark/memory.cpp b/include/boost/multi/adaptors/fftw/benchmark/memory.cpp new file mode 100644 index 000000000..c35b5a000 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/benchmark/memory.cpp @@ -0,0 +1,148 @@ +#ifdef COMPILATION// sudo cpupower frequency-set --governor performance && sudo apt install libbenchmark-dev +${CXX:-c++} -Ofast -DNDEBUG -march=native `#-DNOEXCEPT_ASSIGNMENT` -I../../../../../include/ $0 -o $0x `pkg-config --libs benchmark fftw3`&&$0x&&rm $0x;exit +#endif + +#include + +#include +#include +#include + +#include + +namespace multi = boost::multi; + +using complex = std::complex; + +template +static void Allocation(benchmark::State& state){ + + multi::array in({state.range(0), state.range(0)*2}, 1.2); + multi::array out(extensions(in), 3.1); + + std::vector v(state.range(0)*3.14); + benchmark::DoNotOptimize(v); + + benchmark::DoNotOptimize(in); + benchmark::DoNotOptimize(out); + + benchmark::ClobberMemory(); + + multi::fftw::plan p(std::array{true, true}, in, out, multi::fftw::forward, multi::fftw::estimate); + for(auto _ : state){ + benchmark::DoNotOptimize(in); + benchmark::DoNotOptimize(out); + // benchmark::ClobberMemory(); + + p(); + } + + benchmark::DoNotOptimize(in); + benchmark::DoNotOptimize(out); + benchmark::ClobberMemory(); +} + +BENCHMARK(Allocation>)->DenseRange(100, 500, 28); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +BENCHMARK(Allocation>)->DenseRange(100, 500, 28); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +template +static void Allocation1D(benchmark::State& state){ + + multi::array in({state.range(0)}, 1.2); + multi::array out(extensions(in), 3.1); + + std::vector v(state.range(0)*3.14); + benchmark::DoNotOptimize(v); + + benchmark::DoNotOptimize(in); + benchmark::DoNotOptimize(out); + + benchmark::ClobberMemory(); + + multi::fftw::plan p(std::array{true}, in, out, multi::fftw::forward, multi::fftw::estimate); + for(auto _ : state){ + benchmark::DoNotOptimize(in); + benchmark::DoNotOptimize(out); + // benchmark::ClobberMemory(); + + p(); + } + + benchmark::DoNotOptimize(in); + benchmark::DoNotOptimize(out); + benchmark::ClobberMemory(); +} + +BENCHMARK(Allocation1D>)->RangeMultiplier(2)->Range(128, 128*1024); +BENCHMARK(Allocation1D>)->RangeMultiplier(2)->Range(128, 128*1024); + +BENCHMARK_MAIN(); + +/* +$ sh ./memory.cpp +2023-09-08T10:11:42-07:00 +Running ./memory.cppx +Run on (12 X 4000.06 MHz CPU s) +CPU Caches: + L1 Data 32 KiB (x6) + L1 Instruction 32 KiB (x6) + L2 Unified 256 KiB (x6) + L3 Unified 12288 KiB (x1) +Load Average: 4.28, 3.36, 2.76 +***WARNING*** Library was built as DEBUG. Timings may be affected. +----------------------------------------------------------------------------------------------- +Benchmark Time CPU Iterations +----------------------------------------------------------------------------------------------- +Allocation>/100 106167 ns 105362 ns 6801 +Allocation>/128 186345 ns 186233 ns 3701 +Allocation>/156 445409 ns 445387 ns 1609 +Allocation>/184 1524822 ns 1524758 ns 423 +Allocation>/212 6334445 ns 6334253 ns 116 +Allocation>/240 1639634 ns 1639581 ns 454 +Allocation>/268 7807228 ns 7806998 ns 97 +Allocation>/296 13261983 ns 13261620 ns 45 +Allocation>/324 3183764 ns 3183686 ns 200 +Allocation>/352 4003407 ns 4003311 ns 174 +Allocation>/380 7525147 ns 7524610 ns 106 +Allocation>/408 10952047 ns 10951038 ns 51 +Allocation>/436 42780039 ns 42778256 ns 16 +Allocation>/464 19106268 ns 19104952 ns 36 +Allocation>/492 50939459 ns 50933888 ns 13 +Allocation>/100 161494 ns 161399 ns 4491 +Allocation>/128 242198 ns 242146 ns 2874 +Allocation>/156 661061 ns 660975 ns 1024 +Allocation>/184 2139026 ns 2138641 ns 322 +Allocation>/212 9557704 ns 9556300 ns 77 +Allocation>/240 2313975 ns 2313203 ns 285 +Allocation>/268 11401940 ns 11399821 ns 64 +Allocation>/296 19879090 ns 19874211 ns 35 +Allocation>/324 3994153 ns 3990909 ns 176 +Allocation>/352 5923172 ns 5923003 ns 120 +Allocation>/380 9400210 ns 9398525 ns 73 +Allocation>/408 9982523 ns 9981809 ns 67 +Allocation>/436 42858298 ns 42854934 ns 17 +Allocation>/464 20109321 ns 20104686 ns 32 +Allocation>/492 54813030 ns 54097910 ns 13 +Allocation1D>/128 307 ns 307 ns 2482984 +Allocation1D>/256 597 ns 597 ns 1167097 +Allocation1D>/512 1325 ns 1325 ns 446234 +Allocation1D>/1024 2833 ns 2833 ns 226483 +Allocation1D>/2048 7819 ns 7817 ns 88305 +Allocation1D>/4096 23896 ns 23894 ns 27796 +Allocation1D>/8192 64528 ns 64520 ns 10695 +Allocation1D>/16384 122521 ns 122508 ns 5928 +Allocation1D>/32768 294487 ns 294375 ns 2577 +Allocation1D>/65536 635923 ns 635821 ns 1090 +Allocation1D>/131072 1399999 ns 1399604 ns 486 +Allocation1D>/128 285 ns 285 ns 2578003 +Allocation1D>/256 604 ns 604 ns 1140959 +Allocation1D>/512 1206 ns 1206 ns 473758 +Allocation1D>/1024 2761 ns 2759 ns 246249 +Allocation1D>/2048 7240 ns 7235 ns 89839 +Allocation1D>/4096 20633 ns 20611 ns 31992 +Allocation1D>/8192 56934 ns 56813 ns 12723 +Allocation1D>/16384 121458 ns 121206 ns 5358 +Allocation1D>/32768 281074 ns 280545 ns 2503 +Allocation1D>/65536 606937 ns 606822 ns 1098 +Allocation1D>/131072 1582752 ns 1582109 ns 478 +*/ diff --git a/include/boost/multi/adaptors/fftw/memory.hpp b/include/boost/multi/adaptors/fftw/memory.hpp new file mode 100644 index 000000000..690a4689d --- /dev/null +++ b/include/boost/multi/adaptors/fftw/memory.hpp @@ -0,0 +1,54 @@ +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_FFTW_MEMORY_HPP +#define BOOST_MULTI_ADAPTORS_FFTW_MEMORY_HPP + +#include + +#include // for std:::size_t + +namespace boost::multi::fftw { + +template struct allocator; + +template<> struct allocator{}; + +template +struct allocator { + using value_type = T; + using size_type = std::size_t; + + static auto allocate(size_type n) -> T* { + if(n == 0) { + return nullptr; + } + void* ptr = fftw_malloc(sizeof(T) * n); + if(ptr == nullptr) { + throw std::bad_alloc{}; + } + return static_cast(ptr); + } + static void deallocate(T* ptr, size_type n) { + if(n != 0) { + fftw_free(ptr); + } + } + + // constexpr auto operator==(allocator const& /*other*/) const -> bool { return true; } + // constexpr auto operator!=(allocator const& /*other*/) const -> bool { return false; } + constexpr friend auto operator==(allocator const& /*lhs*/, allocator const& /*rhs*/) { return true; } + constexpr friend auto operator!=(allocator const& /*lhs*/, allocator const& /*rhs*/) { return false; } + + static constexpr auto max_size() noexcept { return std::numeric_limits::max() / sizeof(T); } +}; + +template +constexpr auto operator==(allocator const& /*a*/, allocator const& /*b*/) noexcept -> bool { return true; } + +template +constexpr auto operator!=(allocator const& /*a*/, allocator const& /*b*/) noexcept -> bool { return false; } + +} // namespace boost::multi::fftw +#endif // BOOST_MULTI_ADAPTORS_FFTW_MEMORY_HPP diff --git a/include/multi/adaptors/fftw/mpi.hpp b/include/boost/multi/adaptors/fftw/mpi.hpp similarity index 98% rename from include/multi/adaptors/fftw/mpi.hpp rename to include/boost/multi/adaptors/fftw/mpi.hpp index a3f0085da..ba8aa52aa 100644 --- a/include/multi/adaptors/fftw/mpi.hpp +++ b/include/boost/multi/adaptors/fftw/mpi.hpp @@ -45,7 +45,7 @@ struct array{ using element_type = T; mutable bmpi3::communicator comm_; - Alloc alloc_; + Alloc alloc_; typename std::allocator_traits::size_type local_count_; array_ptr::pointer> local_ptr_; @@ -53,7 +53,9 @@ struct array{ static std::pair::size_type, multi::extensions_t<2>> local_2d(multi::extensions_t<2> ext, boost::mpi3::communicator const& comm){ - ptrdiff_t local_n0, local_0_start; + ptrdiff_t local_n0; + ptrdiff_t local_0_start; + auto count = fftw_mpi_local_size_2d(std::get<0>(ext).size(), std::get<1>(ext).size(), comm.get(), &local_n0, &local_0_start); assert( count >= local_n0*std::get<1>(ext).size() ); return {count, {{local_0_start, local_0_start + local_n0}, std::get<1>(ext)}}; @@ -84,7 +86,7 @@ struct array{ { local_cutout() = other.local_cutout(); } - array(array&& other) : + array(array&& other) noexcept : comm_ {std::move(other.comm_)}, alloc_ {std::move(other.alloc_)}, local_count_{std::exchange(other.local_count_, 0)}, diff --git a/include/multi/adaptors/fftw/mpi/CMakeLists.txt b/include/boost/multi/adaptors/fftw/mpi_/CMakeLists.txt similarity index 100% rename from include/multi/adaptors/fftw/mpi/CMakeLists.txt rename to include/boost/multi/adaptors/fftw/mpi_/CMakeLists.txt diff --git a/include/multi/adaptors/fftw/mpi/distribution.hpp b/include/boost/multi/adaptors/fftw/mpi_/distribution.hpp similarity index 100% rename from include/multi/adaptors/fftw/mpi/distribution.hpp rename to include/boost/multi/adaptors/fftw/mpi_/distribution.hpp diff --git a/include/multi/adaptors/fftw/mpi/environment.hpp b/include/boost/multi/adaptors/fftw/mpi_/environment.hpp similarity index 100% rename from include/multi/adaptors/fftw/mpi/environment.hpp rename to include/boost/multi/adaptors/fftw/mpi_/environment.hpp diff --git a/include/multi/adaptors/fftw/mpi/scattered_array.hpp b/include/boost/multi/adaptors/fftw/mpi_/scattered_array.hpp similarity index 100% rename from include/multi/adaptors/fftw/mpi/scattered_array.hpp rename to include/boost/multi/adaptors/fftw/mpi_/scattered_array.hpp diff --git a/include/multi/adaptors/fftw/mpi/test/CMakeLists.txt b/include/boost/multi/adaptors/fftw/mpi_/test/CMakeLists.txt similarity index 88% rename from include/multi/adaptors/fftw/mpi/test/CMakeLists.txt rename to include/boost/multi/adaptors/fftw/mpi_/test/CMakeLists.txt index 75585be46..f38ae5843 100644 --- a/include/multi/adaptors/fftw/mpi/test/CMakeLists.txt +++ b/include/boost/multi/adaptors/fftw/mpi_/test/CMakeLists.txt @@ -6,9 +6,9 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_VERBOSE_MAKEFILE ON) #project( -# boost-multi-adaptors-fftw-test -# VERSION 0.1 -# LANGUAGES CXX +# boost-multi-adaptors-fftw-test +# VERSION 0.1 +# LANGUAGES CXX #) if((NOT @@ -22,7 +22,7 @@ if((NOT "nvcc" ) ) -# find_package(Boost REQUIRED COMPONENTS unit_test_framework timer) +# find_package(Boost REQUIRED COMPONENTS unit_test_framework timer) else() link_libraries("-lboost_unit_test_framework") # -lboost_timer") endif() @@ -33,7 +33,7 @@ endif() if(ENABLE_CUDA OR DEFINED CXXCUDA) enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") + # set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") endif() enable_testing() @@ -57,11 +57,10 @@ foreach(TEST_FILE ${TEST_SRCS}) add_executable(${TEST_EXE} ${TEST_FILE}) if(ENABLE_CUDA OR DEFINED CXXCUDA) set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) - # set_property(TARGET ${TEST_EXE} PROPERTY "${CUDA_ARCH_LIST}") + # set_property(TARGET ${TEST_EXE} PROPERTY "${CUDA_ARCH_LIST}") target_compile_options(${TEST_EXE} PRIVATE -std=c++17) endif() - # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") + # target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) @@ -82,7 +81,7 @@ foreach(TEST_FILE ${TEST_SRCS}) "nvcc" ) ) - if(NOT DEFINED ENABLE_CIRCLE) + if(NOT DEFINED ENABLE_CIRCLE) target_compile_options( ${TEST_EXE} PRIVATE -Werror diff --git a/include/multi/adaptors/fftw/mpi/test/array_2d.cpp b/include/boost/multi/adaptors/fftw/mpi_/test/array_2d.cpp similarity index 100% rename from include/multi/adaptors/fftw/mpi/test/array_2d.cpp rename to include/boost/multi/adaptors/fftw/mpi_/test/array_2d.cpp diff --git a/include/boost/multi/adaptors/fftw/test/CMakeLists.txt b/include/boost/multi/adaptors/fftw/test/CMakeLists.txt new file mode 100644 index 000000000..c3a9b50b6 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/CMakeLists.txt @@ -0,0 +1,157 @@ +cmake_minimum_required(VERSION 3.10) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") + +#project( +# boost-multi-adaptors-fftw-test +# VERSION 0.1 +# LANGUAGES CXX +#) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +option(CMAKE_COMPILE_WARNING_AS_ERROR "Compile warnings as errors" ON) + +if(ENABLE_CUDA OR DEFINED CXXCUDA) + enable_language(CUDA) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) + endif() +endif() + +if(ENABLE_CIRCLE) +find_package(BLAS REQUIRED) +find_path( + BLAS_INCLUDE_DIRS + cblas.h + /usr/include + /usr/local/include + $ENV{BLAS_HOME}/include +) +include_directories(${TEST_EXE} PRIVATE ${BLAS_INCLUDE_DIRS}) +link_libraries(${BLAS_LIBRARIES}) +endif() + +enable_testing() + +include(CTest) + +include_directories(${CMAKE_BINARY_DIR}) + +# file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) +set(TEST_SRCS + combinations.cpp +# copy.cpp + core.cpp + direct_vs_transpose.cpp + fft.cpp + moved.cpp + shift.cpp + so_shift.cpp +# thrust.cpp + transpose.cpp + transpose_square.cpp +) + +foreach(TEST_FILE ${TEST_SRCS}) + + set(TEST_EXE "${TEST_FILE}.x") + add_executable(${TEST_EXE} ${TEST_FILE}) + if(ENABLE_CUDA OR DEFINED CXXCUDA) + set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) + # target_compile_options(${TEST_EXE} PRIVATE -std=c++17) + endif() + + target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) + + target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) + + target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) + target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) + + target_link_libraries(${TEST_EXE} PUBLIC multi) + target_link_libraries(${TEST_EXE} PRIVATE multi_fftw3) # PkgConfig::FFTW) + + if(NOT DEFINED ENABLE_CIRCLE) + target_compile_options( + ${TEST_EXE} + PRIVATE + $<$,$>,$>>: + -Werror + -Wall + -Wextra + -fno-common + -Wpedantic + -Wformat-truncation + -Wparentheses + -fstack-usage + > + $<$,$>: + -Wall + -Wextra + -Wpedantic + #-fno-common + #-Wmove + #-Wno-error=\#warnings + > + $<$: + -wd161 + -diag-disable=remark + -Warray-bounds + -Wchar-subscripts + -Wcomment + -Wenum-compare + -Wformat + -Wuninitialized + -Wmaybe-uninitialized + -Wmain + -Wnarrowing + -Wnonnull + -Wparentheses + -Wpointer-sign + -Wreorder + -Wno-return-type + -Wsign-compare + -Wsequence-point + -Wtrigraphs + -Wunused-function + -Wunused-but-set-variable + -Wunused-variable + -Wwrite-strings + -Werror + -diag-error:3846 + > + $<$: + #-Xcompiler=-Werror,-Wall,-Wextra,-Wcast-align,-Wcast-qual,-Wdouble-promotion,-Wduplicated-branches,-Wduplicated-cond,-Wformat-truncation,-Wformat=2,-Wlogical-op,-Wmisleading-indentation,-Wno-missing-include-dirs,-Wnon-virtual-dtor,-Wno-missing-declarations,-Wnon-virtual-dtor,-Wnull-dereference,-Woverloaded-virtual,-Wpointer-arith,-Wno-redundant-decls,-Wno-shadow,-Wno-switch-enum,-Wno-unknown-pragmas,-Wtrampolines,-Wuninitialized,-Wunused,-Wunused-but-set-variable,-Wunused-result,-Wno-zero-as-null-pointer-constant + #--Werror=cross-execution-space-call + #--Werror ext-lambda-captures-this + #--use_fast_math + #--extra-device-vectorization + #--restrict + -Xcudafe=--display_error_number -Xcudafe=--diag_suppress=20011 -Xcudafe=--diag_suppress=20014 -Xcudafe=--diag_suppress=20011 -Xcudafe=--diag_error=incompatible_assignment_operands -Xcudafe=--diag_error=returning_ptr_to_local_variable -Xcudafe=--diag_error=subscript_out_of_range -Xcudafe=--diag_error=used_before_set -Xcudafe=--diag_error=undefined_preproc_id -Xcudafe=--diag_error=implicit_func_decl -Xcudafe=--diag_error=implicit_return_from_non_void_function -Xcudafe=--diag_error=missing_type_specifier + > + $<$: + /GS # Enables buffer security check (can help detect stack-based buffer overflows) + /Wall # /volatile:iso /WX /Zc:wchar_t /Zc:forScope /Zc:inline /W4 + /wd4371 # layout of class may have changed from a previous version of the compiler due to better packing of member + /wd4514 # unreferenced inline function has been removed + /wd4623 # default constructor was implicitly defined as deleted #TODO(correaa) investigate + /wd4625 # copy constructor was implicitly defined as deleted + /wd4626 # assignment operator was implicitly defined as deleted #TODO(correaa) investigate + /wd4710 # function not inlined + /wd4711 # function selected for automatic inline expansion + /wd4820 # bytes padding added after base class + /wd4866 # compiler may not enforce left-to-right evaluation order for call + /wd4848 # support for attribute [[msvc::no_unique_address]] in C++17 and earlier is a vendor extension + /wd4868 # compiler may not enforce left-to-right evaluation order in braced initializer list + /wd5026 # move constructor was implicitly defined as deleted + /wd5027 # move assignment operator was implicitly defined as deleted + /wd5045 # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified + # /wd5246 # '_Elems': the initialization of a subobject should be wrapped in braces + > + ) + endif() + + add_test(NAME ${TEST_EXE} COMMAND $) +endforeach() diff --git a/include/boost/multi/adaptors/fftw/test/combinations.cpp b/include/boost/multi/adaptors/fftw/test/combinations.cpp new file mode 100644 index 000000000..dafb38aa1 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/combinations.cpp @@ -0,0 +1,193 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include +#include + +#include // for generate, for_each +#include // for array +#include // for operator-, duration // NOLINT(build/c++11) +#include // for complex, operator== +#include // for operator<<, basic_os... +#include // for iota +#include // for linear_congruential_... +#include // for operator""s, operator+ +#include // for operator""s, operator+ +#include // for vector + +namespace multi = boost::multi; + +template<> +inline constexpr bool multi::force_element_trivial_default_construction> = true; + +class watch : private std::chrono::high_resolution_clock { // NOSONAR(cpp:S4963) this class will report timing on destruction + std::string label_; + time_point start_ = now(); + + public: + explicit watch(std::string_view label) : label_{label} {} + + watch(watch const&) = delete; + watch(watch&&) = default; + + auto operator=(watch const&) = delete; + auto operator=(watch&&) = delete; + + auto elapsed_sec() const { return std::chrono::duration(now() - start_).count(); } + ~watch() { std::cerr << label_ << ": " << elapsed_sec() << " sec" << '\n'; } +}; + + +template using marray = multi::array; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + constexpr auto exts = multi::extensions_t<4>({6, 12, 24, 12}); + + multi::fftw::environment const env; + + // BOOST_AUTO_TEST_CASE(fft_combinations) + { + using complex = std::complex; + { + multi::static_array, 4> ret(multi::extensions_t<4>({6, 12, 24, 12})); + ret[1][2][3][4] = std::complex{1.0, 2.0}; + BOOST_TEST(( ret[1][2][3][4] == std::complex{1.0, 2.0} )); + } + { + multi::array, 4> ret(multi::extensions_t<4>({6, 12, 24, 12})); + ret[1][2][3][4] = std::complex{1.0, 2.0}; + BOOST_TEST(( ret[1][2][3][4] == std::complex{1.0, 2.0} )); + } + + auto const in = [&] { + // marray ret(exts); + multi::array ret(multi::extensions_t<4>({6, 12, 24, 12})); + std::generate( + ret.elements().begin(), ret.elements().end(), + [eng = std::default_random_engine{std::random_device{}()}, + uniform_01 = std::uniform_real_distribution<>{}]() mutable { + return complex{uniform_01(eng), uniform_01(eng)}; + } + ); + return ret; + }(); + + // NOLINTNEXTLINE(fuchsia-default-arguments-calls) + std::vector> const which_cases = { + {{false, true, true, true}}, + {{false, true, true, false}}, + {{ true, false, false, false}}, + {{ true, true, false, false}}, + {{false, false, true, false}}, + {{false, false, false, false}}, + }; + + using std::cout; + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + + for(auto which : which_cases) { // NOLINT(altera-unroll-loops) + cout << "case "; + std::for_each(which.begin(), which.end(), [](auto elem) { std::cout << elem << ", "; }); // NOLINT(modernize-use-ranges) for C++20 + + marray out = in; + { + auto const pln = multi::fftw::plan::forward(which, in.base(), in.layout(), out.base(), out.layout()); + watch const unnamed("cpu_oplac planned %ws wall, CPU (%p%)\n"s); // NOLINT(misc-include-cleaner) bug in clang-tidy 18 + pln.execute(in.base(), out.base()); + } + { + auto in_rw = in; + + watch const unnamed{"cpu_iplac %ws wall, CPU (%p%)\n"s}; + multi::fftw::dft_forward(which, in_rw, in_rw); + } + { + auto in_rw = in; + + auto const pln = multi::fftw::plan::forward(which, in_rw.base(), in_rw.layout(), in_rw.base(), in_rw.layout()); + + watch const unnamed{"cpu_iplac planned %ws wall, CPU (%p%)\n"s}; + pln.execute(in_rw.base(), in_rw.base()); + } + { + auto in_rw = in; + auto const pln = multi::fftw::plan::forward(which, in_rw.base(), in_rw.layout(), in_rw.base(), in_rw.layout()); + watch const unnamed{"cpu_iplac planned measured %ws wall, CPU (%p%)\n"s}; + pln.execute(in_rw.base(), in_rw.base()); + } + } + } + + // BOOST_AUTO_TEST_CASE(fftw_4D_power_benchmark) + { + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + + using complex = std::complex; + namespace fftw = multi::fftw; + + marray in(exts); + std::iota(in.elements().begin(), in.elements().end(), 1.2); + + BOOST_TEST(in[0][0][0][0] == 1.2); + std::array which = {false, true, true, true}; + [&, unnamed = watch{"fftw_4D_power_benchmark inplace FTTT"s}] { + fftw::dft(which, in, fftw::forward); + }(); + [&, unnamed = watch{"fftw_4D_power_benchmark inplace FTTT"s}] { + fftw::dft(which, in, fftw::forward); + }(); + auto in0000 = in[0][0][0][0]; + BOOST_TEST(in0000 != 1.2); + + marray out(exts); + [&, unnamed = watch{"fftw_4D_power_benchmark outofplace FTTT"s}] { + fftw::dft(which, in, out, fftw::forward); + }(); + [&, unnamed = watch{"fftw_4D_power_benchmark outofplace FTTT"s}] { + fftw::dft(which, in, out, fftw::forward); + }(); + [&, unnamed = watch{"fftw_4D_power_benchmark outofplace FTTT"s}] { + fftw::dft(which, in, out, fftw::forward); + }(); + [&, unnamed = watch{"fftw_4D_power_benchmark outofplace+alloc FTTT"s}] { + marray out2(exts); + fftw::dft(which, in, out2, fftw::forward); + }(); + [&, unnamed = watch{"fftw_4D_power_benchmark outofplace+alloc FTTT"s}] { + marray out2(exts); + fftw::dft(which, in, out2, fftw::forward); + }(); + BOOST_TEST(in0000 == in[0][0][0][0]); // cppcheck-suppress knownConditionTrueFalse ; + } + + // BOOST_AUTO_TEST_CASE(fftw_4D_power_benchmark_syntax) + { + // NOLINTNEXTLINE(fuchsia-default-arguments-calls) use of std::vector + // std::vector> const which_cases = { + // {{false, true, true, true}}, + // {{false, true, true, false}}, + // {{ true, false, false, false}}, + // {{ true, true, false, false}}, + // {{false, false, true, false}}, + // {{false, false, false, false}}, + // }; + using complex = std::complex; + + auto const in = [&] { + marray ret(exts); + std::generate( + ret.elements().begin(), ret.elements().end(), + [eng = std::default_random_engine{std::random_device{}()}, + uniform_01 = std::uniform_real_distribution<>{}]() mutable { + return complex{uniform_01(eng), uniform_01(eng)}; + } + ); + return ret; + }(); + } + + return boost::report_errors(); +} diff --git a/include/multi/adaptors/fftw/test/copy.cpp b/include/boost/multi/adaptors/fftw/test/copy.cpp similarity index 91% rename from include/multi/adaptors/fftw/test/copy.cpp rename to include/boost/multi/adaptors/fftw/test/copy.cpp index 25c34a768..278dfae08 100644 --- a/include/multi/adaptors/fftw/test/copy.cpp +++ b/include/boost/multi/adaptors/fftw/test/copy.cpp @@ -1,11 +1,9 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4-*- -$CXXX $CXXFLAGS -O3 $0 -o $0x -DHAVE_FFTW3_THREADS -lfftw3 -lfftw3_threads -lboost_unit_test_framework -lboost_timer&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020 +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +sass -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW copy" -#define BOOST_TEST_DYN_LINK -#include #include #include "../../fftw.hpp" @@ -76,7 +74,7 @@ BOOST_AUTO_TEST_CASE(fftw_copy){ rotated(out) = out; } BOOST_REQUIRE( out[1][2][3][4] == in[2][3][4][1] ); - // BOOST_REQUIRE( rotated(out) == in ); + // BOOST_REQUIRE( rotated(out) == in ); } { multi::array out = in; diff --git a/include/boost/multi/adaptors/fftw/test/core.cpp b/include/boost/multi/adaptors/fftw/test/core.cpp new file mode 100644 index 000000000..a5f1e4e4a --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/core.cpp @@ -0,0 +1,311 @@ +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include // for for_each, tra... +#include // std::abs +#include // for operator* +#include // for begin, end +#include // for accumulate, iota +#include // std::mt19937_64 +#include // for operator""s +#include // for decay_t, enab... +#include // for forward + +namespace { + +namespace multi = boost::multi; +namespace fftw = multi::fftw; + +template auto power(M const& elem) -> decltype(std::norm(elem)) { return std::norm(elem); } + +template= 1), int> = 0> // NOLINT(modernize-use-constraints) for C++20 +auto power(M const& array) { + return accumulate(begin(array), end(array), 0.0, [](auto const& alpha, auto const& omega) { return alpha + power(omega); }); +} + +// struct sum_power { +// template auto operator()(A const& alpha, B const& omega) const { return alpha + power(omega); } +// }; + +} // end anonymous namespace + +template class randomizer { + std::mt19937_64 gen_; // NOSONAR rng good enough for the test + + public: + explicit randomizer(unsigned int seed) : gen_(seed) {} + + template::reference> void operator()(M&& arr) { + std::for_each(std::begin(std::forward(arr)), std::end(std::forward(arr)), [self = this](R elem) { self->operator()(elem); }); + } + void operator()(T& elem) { // NOLINT(runtime/references) passing by reference + std::normal_distribution gauss; + elem = gauss(gen_); + } +}; + +template class randomizer> { + std::mt19937_64 gen_; // NOSONAR rng good enough for the test + + public: + explicit randomizer(unsigned int seed) : gen_(seed) {} + + template::reference> void operator()(M&& arr) { + std::for_each( + std::begin(std::forward(arr)), std::end(std::forward(arr)), // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + [self = this](R elem) { self->operator()(elem); } + ); + } + void operator()(std::complex& zee) { // NOLINT(runtime/references) : passing by reference + std::normal_distribution gauss; + zee = std::complex(gauss(gen_), gauss(gen_)); + } +}; + +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + fftw::environment const env; + + BOOST_AUTO_TEST_CASE(fftw_2D_identity_2) { //, *boost::unit_test::tolerance(0.0001)) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array const in = { + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + }; + multi::array out(extensions(in)); + + multi::fftw::dft_forward({ + {false, false} + }, + in, out); // out = in; + + BOOST_TEST( std::abs( in[2][2].real() - out[2][2].real() ) < 1e-10 ); // cppcheck-suppress + BOOST_TEST( std::abs( in[2][2].imag() - out[2][2].imag() ) < 1e-10 ); + + BOOST_TEST( out == in ); + } + + BOOST_AUTO_TEST_CASE(fftw_2D_many) { + using complex = std::complex; + + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array const in = { + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + }; + multi::array out(extensions(in)); + + using multi::fftw::dft_forward; + + multi::fftw::dft_forward({ + {false, false} + }, + in.rotated(), out.rotated()); + BOOST_TEST( in == out ); + } + + BOOST_AUTO_TEST_CASE(fftw_many1_from_2) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + std::random_device dev; + multi::array in({3, 10}); + randomizer{dev()}(in); + multi::array out({3, 10}); + fftw::dft_forward({ + {false, true} + }, + in, out); + + multi::array out2({3, 10}); + std::transform(in.begin(), in.end(), out2.begin(), out2.begin(), [](auto const& in_elem, auto&& out2_elem) { // NOLINT(modernize-use-ranges) for C++20 + fftw::dft_forward({{true}}, in_elem, out2_elem); + return std::forward(out2_elem); + }); + + BOOST_TEST(out2 == out); + } + + BOOST_AUTO_TEST_CASE(fftw_many2_from_3) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + std::random_device dev; + multi::array in({3, 5, 6}); + randomizer{dev()}(in); + multi::array out({3, 5, 6}); + fftw::dft_forward({ + {false, true, true} + }, + in, out); + + multi::array out2({3, 5, 6}); + std::transform(in.begin(), in.end(), out2.begin(), out2.begin(), [](auto const& in_elem, auto&& out2_elem) { // NOLINT(modernize-use-ranges) for C++20 + fftw::dft_forward({true, true}, in_elem, out2_elem); + return std::forward(out2_elem); + }); + + BOOST_TEST(out2 == out); + } + + BOOST_AUTO_TEST_CASE(fftw_2D_power_plan) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in({16, 16}); + std::iota(in.elements().begin(), in.elements().end(), 1.2); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic): test code + multi::array out(extensions(in)); + + auto const pln = multi::fftw::plan::forward({ + {true, true} + }, + in.base(), in.layout(), out.base(), out.layout()); + pln.execute(in.base(), out.base()); + + BOOST_TEST( power(in) - (power(out)/static_cast(num_elements(out))) < 1e-7 ); + } + + BOOST_AUTO_TEST_CASE(fftw_2D_power_plan_modern) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in({16, 16}); + std::iota(in.elements().begin(), in.elements().end(), 1.2); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic): test code + multi::array out(extensions(in)); + auto const pln = multi::fftw::plan::forward({ + {true, true} + }, + in.base(), in.layout(), out.base(), out.layout()); + pln.execute(in.base(), out.base()); + BOOST_TEST( power(in) - (power(out)/static_cast(num_elements(out))) < 1e-8 ); + } + + BOOST_AUTO_TEST_CASE(fftw_2D_power_plan_modern_measure) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in({16, 16}); + std::iota(in.elements().begin(), in.elements().end(), 1.2); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic): test code + multi::array out(extensions(in)); + auto const pln = multi::fftw::plan::forward({ + {true, true} + }, + in.base(), in.layout(), out.base(), out.layout()); + pln.execute(in.base(), out.base()); + BOOST_TEST( power(in) - (power(out)/static_cast(num_elements(out))) < 1e-8 ); + } + + BOOST_AUTO_TEST_CASE(fftw_2D_power_dft) { + using complex = std::complex; + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in({16, 16}); + std::iota(in.elements().begin(), in.elements().end(), 1.2); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic): test code + multi::array out(extensions(in)); + multi::fftw::dft_forward({ + {true, true} + }, + in, out); + BOOST_TEST( power(in) - (power(out)/static_cast(num_elements(out))) < 1e-8 ); + } + + BOOST_AUTO_TEST_CASE(fftw_3D_power_in_place_over_ref_inplace) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array io({4, 4, 4}); + std::iota(io.elements().begin(), io.elements().end(), 1.2); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic): test code + auto const powerin = power(io); + + // fftw::dft_inplace(multi::array_ref(io.data(), io.extensions()), fftw::forward); + + fftw::dft_forward( + { + {true, true, true} + }, + multi::array_ref(data_elements(io), extensions(io)), + multi::array_ref(data_elements(io), extensions(io)) + ); + BOOST_TEST( powerin - (power(io)/static_cast(num_elements(io))) < 1e-10 ); + } + + BOOST_AUTO_TEST_CASE(fftw_2D_const_range_ref) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array const in = { + {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + }; + } + + BOOST_AUTO_TEST_CASE(fftw_2D_const_range_ref_transposed_naive_square) { + using complex = std::complex; + + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in = { + {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + }; + multi::array const in_transpose = in.transposed(); + in = in.transposed(); + BOOST_TEST( in != in_transpose ); + } + + BOOST_AUTO_TEST_CASE(fftw_2D_const_range_ref_transposed_nonpod) { + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + multi::array in = { + {"100.0 + 2.0*I"s, "9.0 - 1.0*I"s, "2.0 + 4.0*I"s}, // NOLINT(misc-include-cleaner) bug in clang-tidy 18 + { "3.0 + 3.0*I"s, "7.0 - 4.0*I"s, "1.0 + 9.0*I"s}, + { "4.0 + 1.0*I"s, "5.0 + 3.0*I"s, "2.0 + 4.0*I"s}, + { "3.0 - 1.0*I"s, "8.0 + 7.0*I"s, "2.0 + 1.0*I"s}, + { "31.0 - 1.0*I"s, "18.0 + 7.0*I"s, "2.0 + 10.0*I"s}, + }; + multi::array const in_transpose = in.transposed(); + in = in.transposed(); + BOOST_TEST( in != in_transpose ); + } + + BOOST_AUTO_TEST_CASE(fftw_2D_const_range_ref_transposed_nonpod_square) { + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + + multi::array in = { + {"100.0 + 2.0*I"s, "9.0 - 1.0*I"s, "2.0 + 4.0*I"s}, + { "3.0 + 3.0*I"s, "7.0 - 4.0*I"s, "1.0 + 9.0*I"s}, + { "4.0 + 1.0*I"s, "5.0 + 3.0*I"s, "2.0 + 4.0*I"s}, + }; + multi::array const in_transpose = in.transposed(); + in = in.transposed(); + BOOST_TEST( in != in_transpose ); + } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/fftw/test/direct_vs_transpose.cpp b/include/boost/multi/adaptors/fftw/test/direct_vs_transpose.cpp new file mode 100644 index 000000000..5d0c7987a --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/direct_vs_transpose.cpp @@ -0,0 +1,102 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include // for is_const_v + +namespace multi = boost::multi; + +template<> +inline constexpr bool multi::force_element_trivial_default_construction> = true; + +class watch : private std::chrono::high_resolution_clock { // NOSONAR(cpp:S4963) this class will report timing on destruction + std::string label_; + time_point start_ = now(); + + public: + explicit watch(std::string_view label) : label_{label} {} + + watch(watch const&) = delete; + watch(watch&&) = default; + + auto operator=(watch const&) = delete; + auto operator=(watch&&) = delete; + + auto elapsed_sec() const { return std::chrono::duration(now() - start_).count(); } + ~watch() { std::cerr << label_ << ": " << elapsed_sec() << " sec" << '\n'; } +}; + +namespace { +// clang-format off +template +inline +#ifdef _MSC_VER +__forceinline +#else +__attribute__((always_inline)) +#endif +void DoNotOptimize(Tp& value) { // NOLINT(readability-identifier-naming) +#ifdef _MSC_VER + _ReadWriteBarrier(); (void)value; +#else +#if defined(__clang__) || defined(__circle_build__) +if constexpr(!std::is_const_v) { // NOLINT(bugprone-branch-clone) + asm volatile("" : "+r,m"(value) : : "memory"); // NOLINT(hicpp-no-assembler) +} else { // NOLINT(bugprone-branch-clone) + asm volatile("" : "+m,r"(value) : : "memory"); // NOLINT(hicpp-no-assembler) +} +#else + asm volatile("" : "+m,r"(value) : : "memory"); // NOLINT(hicpp-no-assembler) +#endif +#endif +} +// clang-format on +} // end namespace + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + using complex = std::complex; + + auto in = [] { +#ifndef NDEBUG + auto ret = multi::array({100, 100, 100}); +#else + auto ret = multi::array({10, 10, 10}); +#endif + std::generate( + ret.elements().begin(), ret.elements().end(), + [eng = std::default_random_engine{std::random_device{}()}, + dist = std::uniform_real_distribution<>{}]() mutable { + return complex{dist(eng), dist(eng)}; + } + ); + return ret; + }(); + + auto const pn = multi::fftw::plan::forward({{false, false, false}}, in.base(), in.layout(), in.base(), in.layout()); + + DoNotOptimize(in); + + std::cout << pn.flops() << "FLOPS\n"; + + [&, unnamed = watch{"3D *100x100x100"}] { + for(int i = 0; i != 100; ++i) { // NOLINT(altera-unroll-loops) + pn.execute(in.base(), in.base()); + } + }(); + + DoNotOptimize(in); + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/fftw/test/fft.cpp b/include/boost/multi/adaptors/fftw/test/fft.cpp new file mode 100644 index 000000000..82b2427e6 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/fft.cpp @@ -0,0 +1,256 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +#include // external fftw3 library + +#include + +// IWYU pragma: no_include +#include +// IWYU pragma: no_include // for forward +// IWYU pragma: no_include + +namespace multi = boost::multi; +using complex = std::complex; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-variable-declarations" +#endif +template<> +constexpr bool multi::force_element_trivial_default_construction> = true; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace { +// #if !defined(_MSC_VER) +// template +// __attribute__((always_inline)) inline void DoNotOptimize(T const& value) { // NOLINT(readability-identifier-naming) consistency with Google benchmark +// asm volatile("" : "+m"(const_cast(value))); // NOLINT(hicpp-no-assembler,cppcoreguidelines-pro-type-const-cast) hack +// } +// #else +// template +// inline void DoNotOptimize(T const& value) { /*nothihg*/ } +// #endif +} // end namespace + +namespace { +void zip_iterator_test(multi::array const& in_cpu) { + multi::array fw_cpu_out(in_cpu.extensions()); + auto zit = multi::fftw::io_zip_iterator( + {{true}}, + in_cpu.begin(), + fw_cpu_out.begin(), + multi::fftw::forward + ); + + zit.execute(); + zit += 1; + auto zit2 = zit; + BOOST_TEST(zit2 == zit); // cppcheck-suppress knownConditionTrueFalse ; for testing + + for(int i = 1; i != in_cpu.size(); ++i) { // NOLINT(altera-unroll-loops) + zit.execute(); + ++zit; + } + + multi::array const fw_cpu = multi::fft::dft_forward({ + {false, true} + }, + in_cpu); + + BOOST_TEST( fw_cpu_out == fw_cpu ); +} +} // end namespace + +auto main() -> int { // NOLINT(bugprone-exception-escape,readability-function-cognitive-complexity) + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) + + auto const in_cpu = multi::array{ + { 1.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + {31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + }; + + auto fw_cpu = multi::array(extensions(in_cpu)); + multi::fftw::dft_forward({ + {true, true} + }, + in_cpu, fw_cpu); + + BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); + BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); + + // check properties + { + auto const& dft = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + + BOOST_TEST( dft.extensions() == in_cpu.extensions() ); + BOOST_TEST( (*dft.begin()).size() == (*in_cpu.begin()).size() ); + BOOST_TEST( (*dft.begin()).extensions() == (*in_cpu.begin()).extensions() ); + } + // assignment with right size + { + multi::array fw_cpu_out(in_cpu.extensions()); + complex const* const persistent_base = fw_cpu_out.base(); + + fw_cpu_out = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + + BOOST_TEST( fw_cpu_out == fw_cpu ); + BOOST_TEST( persistent_base == fw_cpu_out.base() ); + } + // assignment with incorrect size (need reallocation) + { + multi::array fw_cpu_out({2, 2}); + + fw_cpu_out = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // assignment to empty + { + multi::array fw_cpu_out; + + fw_cpu_out = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // constructor + { + multi::array const fw_cpu_out = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // check properties + { + auto const& dft = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + + BOOST_TEST( dft.extensions() == in_cpu.extensions() ); + BOOST_TEST( (*dft.begin()).size() == (*in_cpu.begin()).size() ); + BOOST_TEST( (*dft.begin()).extensions() == (*in_cpu.begin()).extensions() ); + } + // assignment with right size + { + multi::array fw_cpu_out(in_cpu.extensions()); + complex const* const persistent_base = fw_cpu_out.base(); + + fw_cpu_out = multi::fft::dft( + { + {true, true} + }, + in_cpu, multi::fft::forward + ); + + BOOST_TEST( fw_cpu_out == fw_cpu ); + BOOST_TEST( persistent_base == fw_cpu_out.base() ); + } + // assignment with incorrect size (need reallocation) + { + multi::array fw_cpu_out({2, 2}); + + fw_cpu_out = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // assignment to empty + { + multi::array fw_cpu_out; + + fw_cpu_out = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // constructor + { + multi::array const fw_cpu_out = multi::fft::dft({ + {true, true} + }, + in_cpu, multi::fft::forward); + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // constructor forward + { + multi::array const fw_cpu_out = multi::fft::dft_forward({ + {true, true} + }, + in_cpu); + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // constructor forward default + { + multi::array const fw_cpu_out = multi::fft::dft({ + {true, true} + }, + in_cpu); + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // constructor all default + { + multi::array const fw_cpu_out = multi::fft::dft_all(in_cpu); + BOOST_TEST( fw_cpu_out == fw_cpu ); + } + // constructor none + { + multi::array const fw_cpu_out = multi::fft::dft({ + {false, false} + }, + in_cpu); + BOOST_TEST( fw_cpu_out == in_cpu ); + } + // constructor none + { + multi::array const fw_cpu_out = multi::fft::dft({}, in_cpu); + BOOST_TEST( fw_cpu_out == in_cpu ); + } + { + auto const fw_cpu_out = +multi::fft::dft({}, in_cpu); + BOOST_TEST( fw_cpu_out == in_cpu ); + } + // constructor none + { + multi::array const fw_cpu_out = multi::fft::dft({}, in_cpu()); + BOOST_TEST( fw_cpu_out == in_cpu ); + } + // transposed + { + multi::array const fw_cpu_out = in_cpu.transposed(); + BOOST_TEST( fw_cpu_out == in_cpu.transposed() ); + } + + zip_iterator_test(in_cpu); + + fftw_cleanup(); + + return boost::report_errors(); +} diff --git a/include/multi/adaptors/fftw/test/core.cpp b/include/boost/multi/adaptors/fftw/test/memory.cpp similarity index 96% rename from include/multi/adaptors/fftw/test/core.cpp rename to include/boost/multi/adaptors/fftw/test/memory.cpp index 75d2ed604..7c0e257bf 100644 --- a/include/multi/adaptors/fftw/test/core.cpp +++ b/include/boost/multi/adaptors/fftw/test/memory.cpp @@ -1,51 +1,19 @@ // -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa +// Copyright 2023 Alfredo A. Correa -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW core" -#include +#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW memory" +// #include #include #include -#include -#include -#include +#include +#include +#include +#include #include -namespace { - -namespace multi = boost::multi; -namespace fftw = multi::fftw; - -template auto power(M const& elem) -> decltype(std::norm(elem)) {return std::norm(elem);} - -template= 1)> > // DELETE((M::rank::value < 1))> -auto power(M const& array) { - return accumulate(begin(array), end(array), 0.0, [](auto const& alpha, auto const& omega) {return alpha + power(omega);}); -} - -struct sum_power{ - template auto operator()(A const& alpha, B const& omega) const {return alpha + power(omega);} -}; - -} // end anonymous namespace - -class watch : private std::chrono::high_resolution_clock{ - std::string label; - time_point start = now(); - - public: - explicit watch(std::string label) : label{std::move(label)} {} - watch(watch const&) = delete; - watch(watch&&) = default; - auto operator=(watch const&) = delete; - auto operator=(watch&&) -> watch& = default; // NOLINT(fuchsia-trailing-return): - ~watch(){ - std::cerr<< label<<": "<< std::chrono::duration(now() - start).count() <<" sec"< struct randomizer { template void operator()(M&& arr) const { std::for_each(arr.begin(), arr.end(), [&self=*this](auto&& elem) {self.operator()(elem);}); diff --git a/include/boost/multi/adaptors/fftw/test/moved.cpp b/include/boost/multi/adaptors/fftw/test/moved.cpp new file mode 100644 index 000000000..da75a78ec --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/moved.cpp @@ -0,0 +1,294 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include + +// IWYU pragma: no_include // for fill_n, equal +#include +// IWYU pragma: no_include // for vector +#include // for vector + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + #if !defined(__CUDACC_VER_MAJOR__) || (__CUDACC_VER_MAJOR__ < 13) + multi::fftw::environment const env; + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_move) + { + using complex = std::complex; + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in = { + {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + }; + BOOST_TEST( in[1][1] == 7.0 - 4.0*I ); + + auto const in_copy = in; + auto* const in_base = in.base(); + BOOST_TEST( in_base == in.base() ); + + // in = multi::fftw::ref(in); + + // BOOST_TEST( in == in_copy ); + // BOOST_TEST( in_base == in.base() ); // prove no allocation + } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed) + { + using complex = std::complex; + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in = { + {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + }; + BOOST_TEST( in[1][1] == 7.0 - 4.0*I ); + + auto const in_copy = in; + auto* const in_base = in.base(); + BOOST_TEST( in_base == in.base() ); + BOOST_TEST( in.size() == 5 ); + + // in = multi::fftw::ref(in).transposed(); + + // BOOST_TEST( in.size() == 3 ); + // BOOST_TEST( in == in_copy.transposed() ); // prove correctness + // BOOST_TEST( in_base == in.base() ); // prove no allocation + } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_naive) + { + using complex = std::complex; + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in = { + {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + }; + BOOST_TEST( in[1][1] == 7.0 - 4.0*I ); + + auto const in_copy = in; + auto* const in_base = in.base(); + BOOST_TEST( in_base == in.base() ); + BOOST_TEST( in.size() == 5 ); + + in = in.transposed(); // this is UB + + BOOST_TEST( in.size() == 3 ); + // BOOST_TEST( in != in_copy.transposed() ); // prove it is incorrect + BOOST_TEST( in_base == in.base() ); // prove no allocation + } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_naive_copy) + { + using complex = std::complex; + [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + multi::array in = { + {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + }; + BOOST_TEST( in[1][1] == 7.0 - 4.0*I ); + + auto const in_copy = in; + auto* const in_base = in.base(); + BOOST_TEST( in_base == in.base() ); + BOOST_TEST( in.size() == 5 ); + + in = +in.transposed(); + + BOOST_TEST( in.size() == 3 ); + BOOST_TEST( in == in_copy.transposed() ); // prove correctness + BOOST_TEST( in_base != in.base() ); // prove no allocation + } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_fft_copy) { + // using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // multi::array in = { + // {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + // { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + // { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + // { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + // { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + // }; + + // auto const in_copy = in; + // auto* const in_base = in.base(); + + // multi::array in2 = multi::fftw::fft(in); + + // BOOST_TEST( power(in2)/num_elements(in2) - power(in_copy) < 1e-8 ); + // BOOST_TEST( in2.base() != in_base ); + // BOOST_TEST( not in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + // } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_copyconstruct) { + // using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // multi::array in = { + // {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + // { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + // { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + // { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + // { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + // }; + + // auto const in_copy = in; + // auto* const in_base = in.base(); + + // multi::array in2 = multi::fftw::ref(in).transposed(); + + // BOOST_TEST( in2 == in_copy.transposed() ); + // BOOST_TEST( in2.base() != in_base ); + // BOOST_TEST( in .base() == in_base ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + // } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_moveconstruct) { + // using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // multi::array in = { + // {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + // { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + // { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + // { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + // { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + // }; + + // auto const in_copy = in; + // auto* const in_base = in.base(); + + // multi::array in2 = multi::fftw::ref(std::move(in)).transposed(); + + // BOOST_TEST( in2 == in_copy.transposed() ); + // BOOST_TEST( in2.base() == in_base ); + // BOOST_TEST( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + // } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_moveconstruct_implicit) { + // using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // #if not defined(__INTEL_COMPILER) // TODO(correaa) problem with icpc 2022.3.0.8751 + // multi::array in = { + // {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + // { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + // { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + // { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + // { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + // }; + + // auto const in_copy = in; + // auto* const in_base = in.base(); + + // auto in2 = +multi::fftw::ref(std::move(in)).transposed(); + + // BOOST_TEST( in2 == in_copy.transposed() ); + // #if not defined(__NVCOMPILER) // these tests fail with nvc++ 22.9, 23.1 + // BOOST_TEST( in2.base() == in_base ); + // BOOST_TEST( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + // #endif + // #endif + // } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_moveassign_from_temp) { + // using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // #if not defined(__INTEL_COMPILER) // TODO(correaa) problem with icpc 2022.3.0.8751 + // multi::array in = { + // {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + // { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + // { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + // { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + // { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + // }; + + // auto const in_copy = in; + // auto* const in_base = in.base(); + + // multi::array in2; + // in2 = static_cast>(multi::fftw::ref(std::move(in)).transposed()); + + // BOOST_TEST( in2 == in_copy.transposed() ); + // #if not defined(__NVCOMPILER) // these tests fail with nvc++ 22.9, 23.1 + // BOOST_TEST( in2.base() == in_base ); + // BOOST_TEST( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + // #endif + // #endif + // } + + // BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_moveassign) { + // using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit + + // #if not defined(__INTEL_COMPILER) // TODO(correaa) problem with icpc 2022.3.0.8751 + // multi::array in = { + // {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, + // { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, + // { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, + // { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, + // { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, + // }; + + // auto const in_copy = in; + // auto* const in_base = in.base(); + + // multi::array in2; + // in2 = multi::fftw::ref(std::move(in)).transposed(); + + // BOOST_TEST( in2 == in_copy.transposed() ); + // #if not defined(__NVCOMPILER) // these tests fail with nvc++ 22.9, 23.1 + // BOOST_TEST( in2.base() == in_base ); + // BOOST_TEST( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + // #endif + // #endif + // } + + { + multi::array A1 = { + {{1, 2}, + {3, 4}}, + {{5, 6}, + {7, 8}}, + }; + + auto&& R1 = A1[1]; + R1 = A1[0]; + + BOOST_TEST( A1[0] == A1[1] ); + } + { + // NOLINTBEGIN(fuchsia-default-arguments-calls) + multi::array, 3> A1 = { + {{std::vector(1, 0), std::vector(2, 0)}, + {std::vector(3, 0), std::vector(4, 0)}}, + {{std::vector(5, 0), std::vector(6, 0)}, + {std::vector(7, 0), std::vector(8, 0)}}, + }; + + auto&& R1 = A1[1]; + R1 = A1[0].move(); + + BOOST_TEST( A1[1][0][0] == std::vector(1, 0) ); + // BOOST_TEST( A1[0][0][0].empty() ); // TODO(correaa) make moved elements work + // NOLINTEND(fuchsia-default-arguments-calls) + } + #endif + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/fftw/test/shift.cpp b/include/boost/multi/adaptors/fftw/test/shift.cpp new file mode 100644 index 000000000..41c3f6901 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/shift.cpp @@ -0,0 +1,103 @@ +// Copyright 2022-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include // for for_each, rotate +#include // NOLINT(build/c++11) +#include // for complex +#include // for size_t, ptrdiff_t +#include // for char_traits, basic_ostream +#include + +template +class n_random_complex { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + std::size_t n_ = 0; + + mutable std::mt19937 gen_{std::random_device{}()}; // NOLINT(whitespace/braces) cpplint 1.6 bug + mutable std::uniform_real_distribution<> dist_{-1.0, 1.0}; + + public: + n_random_complex() = default; + n_random_complex(n_random_complex const&); // = delete; + auto operator=(n_random_complex const&) -> n_random_complex&; // = delete; + + constexpr auto operator==(n_random_complex const&) const; + constexpr auto operator!=(n_random_complex const&) const; + + explicit n_random_complex(std::size_t n) : n_{n} {} + + class iterator : public boost::multi::detail::random_access_iterator, std::complex, void> { // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + n_random_complex const* ptr_; // NOLINT(cppcoreguidelines-pro-type-member-init) + std::size_t n_; // NOLINT(cppcoreguidelines-pro-type-member-init) + + public: // NOLINT(whitespace/indent) cpplint 1.6 bug + using difference_type = std::ptrdiff_t; + + iterator() = default; + + iterator(n_random_complex const* ptr, std::size_t n) : ptr_{ptr}, n_{n} {} + + // cppcheck-suppress duplInheritedMember ; to ovewrite + auto operator*() const { return std::complex{ptr_->dist_(ptr_->gen_), ptr_->dist_(ptr_->gen_)}; } + auto operator++() -> iterator& { + ++n_; + return *this; + } + auto operator++(int) -> iterator { iterator ret{*this}; ++(*this); return ret; } + // auto operator--(int) -> iterator { iterator ret{*this}; --(*this); return ret; } + + auto operator==(iterator const& other) const { return n_ == other.n_; } + auto operator!=(iterator const& other) const { return n_ != other.n_; } + + auto operator-(iterator const& other) const { return static_cast(n_ - other.n_); } + + auto operator+(std::ptrdiff_t delta) const { return iterator{ptr_, n_ + delta}; } // mmm, needed by culang? + }; + + auto begin() const { return iterator{this, 0}; } + auto end() const { return iterator{this, n_}; } + + auto size() const { return n_; } +}; + +namespace multi = boost::multi; +namespace fftw = multi::fftw; + +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + fftw::environment const env; + BOOST_AUTO_TEST_CASE(fftw_shift) { + class watch : std::chrono::steady_clock { + time_point start_ = now(); + + public: // NOLINT(whitespace/indent) cpplint 1.6 bug + auto elapsed_sec() const { return std::chrono::duration(now() - start_).count(); } + }; + + multi::array, 1> const arr = n_random_complex(static_cast(19586)); + BOOST_TEST(arr.size() == 19586); + multi::array, 1> res(arr.extensions()); + BOOST_TEST(res.size() == 19586); + + auto fdft = fftw::plan::forward({{true}}, arr.base(), arr.layout(), res.base(), res.layout()); + // fftw::plan fdft({{true}}, arr.layout(), res.layout(), multi::fftw::forward); + + [&, unnamed = watch{}] { + auto const repeat = 40; + std::for_each( + multi::extension_t{0, repeat}.begin(), multi::extension_t{0, repeat}.end(), // cppcheck-suppress mismatchingContainerExpression ; + [&fdft, &arr, &res](auto /*idx*/) { + fdft.execute(arr.base(), res.base()); + std::rotate(res.begin(), res.begin() + res.size() / 2, res.end()); // NOLINT(modernize-use-ranges) for C++20 + } + ); + std::cout << "FFTW shift " << unnamed.elapsed_sec() / repeat << " sec\n"; // prints 0.000882224 sec + }(); + } + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/fftw/test/so_shift.cpp b/include/boost/multi/adaptors/fftw/test/so_shift.cpp new file mode 100644 index 000000000..e3b4a26fa --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/so_shift.cpp @@ -0,0 +1,42 @@ +// Copyright 2022-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +// #include // includes fftw3.hpp + +#include +#include +#include // for std::iota + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(bugprone-exception-escape) + using complex = std::complex; + + // input array + auto const x = std::invoke([] () noexcept { // NOLINT(readability-identifier-length) + multi::static_array const ret2(8); (void)ret2; + + multi::array ret(8); + // fill the first array with some numbers + std::iota(ret.begin(), ret.end(), 1.0); + return ret; + }); + + // output array + // multi::array y(x.size()); // NOLINT(readability-identifier-length) + // compute the FFT of x and store results in y + // auto y = +multi::fftw::dft_forward(x); // NOLINT(readability-identifier-length) + + // display the results + // std::cout << "FFT =" << std::endl; + // std::copy(y.begin(), y.end(), std::ostream_iterator(std::cout, "\n")); + + // "shifted" results + // std::rotate(y.begin(), y.begin() + y.size() / 2 + y.size() % 2, y.end()); + + // std::cout << "FFT shifted =" << std::endl; + // std::copy(y.begin(), y.end(), std::ostream_iterator(std::cout, "\n")); +} diff --git a/include/boost/multi/adaptors/fftw/test/thrust.cpp b/include/boost/multi/adaptors/fftw/test/thrust.cpp new file mode 100644 index 000000000..b10a25084 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/thrust.cpp @@ -0,0 +1,32 @@ +// © Alfredo A. Correa 2020-2024 + +#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW adaptor (cpu) with thrust complex" +#define BOOST_TEST_DYN_LINK + +// #include + +#include "../../fftw.hpp" + +#include +#include + +namespace multi = boost::multi; + +BOOST_AUTO_TEST_CASE(const fftw_2D_identity){ + using complex = thrust::complex; complex const I{0.0, 1.0}; + + multi::array const in = { + { 1.0 + 2.0*I, 9.0 - 1.0*I, 2.0 + 4.0*I}, + { 3.0 + 3.0*I, 7.0 - 4.0*I, 1.0 + 9.0*I}, + { 4.0 + 1.0*I, 5.0 + 3.0*I, 2.0 + 4.0*I}, + { 3.0 - 1.0*I, 8.0 + 7.0*I, 2.0 + 1.0*I}, + { 31.0 - 1.0*I, 18.0 + 7.0*I, 2.0 + 10.0*I} + }; + auto fwd = multi::fftw::dft({true, true}, in, multi::fftw::forward); + + multi::array, 2> const in_t = in; + + auto fwd_t = multi::fftw::dft({true, true}, in_t, multi::fftw::forward); + + BOOST_REQUIRE( fwd == fwd_t ); +} diff --git a/include/boost/multi/adaptors/fftw/test/transpose.cpp b/include/boost/multi/adaptors/fftw/test/transpose.cpp new file mode 100644 index 000000000..8ccd55b29 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/transpose.cpp @@ -0,0 +1,65 @@ +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include +#include + +#include // for generate +#include // for operator-, duration, system... // NOLINT(build/c++11) +#include // for operator==, complex +#include // for invoke // IWYU pragma: keep +#include // for operator<<, basic_os... +#include // for linear_congruential_... +#include // for operator<<, operator""s +#include // for move + +namespace multi = boost::multi; + +class watch // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) +: private std::chrono::high_resolution_clock { + std::string label_; + time_point start_ = now(); + + public: + explicit watch(std::string label) : label_{std::move(label)} {} // NOLINT(fuchsia-default-arguments-calls) + + watch(watch const&) = delete; + + auto operator=(watch const&) -> watch& = delete; + + auto elapsed_sec() const { return std::chrono::duration(now() - start_).count(); } + ~watch() { std::cerr << label_ << ": " << elapsed_sec() << " sec" << '\n'; } // NOLINT(cpp:S4963) +}; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + multi::fftw::environment const env; + + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + + using complex = std::complex; + + auto const in = std::invoke([] () noexcept { + multi::array ret({101, 99}); // ({1013, 997}); // ({10137, 9973}); + std::generate( + ret.elements().begin(), ret.elements().end(), + [eng = std::default_random_engine{std::random_device{}()}, uniform_01 = std::uniform_real_distribution<>{}]() mutable { + return complex{uniform_01(eng), uniform_01(eng)}; + } + ); + return ret; + }); + + multi::array out = in; + + watch const unnamed{"transposition with aux %ws wall, CPU (%p%)\n"s}; // NOLINT(misc-include-cleaner) bug in clang-tidy 18 + + multi::array aux{~out}; + + out = std::move(aux); + BOOST_TEST( out[35][79] == in[79][35] ); + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/fftw/test/transpose_square.cpp b/include/boost/multi/adaptors/fftw/test/transpose_square.cpp new file mode 100644 index 000000000..6ceb48641 --- /dev/null +++ b/include/boost/multi/adaptors/fftw/test/transpose_square.cpp @@ -0,0 +1,102 @@ +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for initialize_threads, environ... +#include // for array, subarray, layout_t + +#include // for for_each, generate +#include // for duration, operator-, high_r... // NOLINT(build/c++11) +#include // for operator==, complex +#include // for invoke // IWYU pragma: keep +#include // for basic_ostream, operator<< +#include // for uniform_real_distribution +#include // for char_traits, operator""s +#include +#include // for move, swap + +namespace multi = boost::multi; + +using complex = std::complex; + +class watch : private std::chrono::high_resolution_clock { // NOSONAR(cpp:S4963) this class will report timing on destruction + std::string label_; + time_point start_ = now(); + + public: + explicit watch(std::string_view label) : label_{label} {} + + watch(watch const&) = delete; + watch(watch&&) = delete; + + auto operator=(watch const&) = delete; + auto operator=(watch&&) = delete; + + auto elapsed_sec() const { return std::chrono::duration(now() - start_).count(); } + ~watch() { std::cerr << label_ << ": " << elapsed_sec() << " sec" << '\n'; } +}; + +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) +multi::fftw::environment const env; +BOOST_AUTO_TEST_CASE(fftw_transpose) { + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + + multi::fftw::initialize_threads(); + { + auto const in = std::invoke([] () noexcept { + // multi::array ret({819, 819}); + multi::array ret({ 81, 81 }); + std::generate( + ret.elements().begin(), ret.elements().end(), + [eng = std::default_random_engine{ std::random_device{}() }, + uniform_01 = std::uniform_real_distribution<>{}]() mutable { + return complex{ uniform_01(eng), uniform_01(eng) }; + } + ); + return ret; + }); + { + multi::array out = in; + multi::array aux(out.extensions()); + { + watch const unnamed("auxiliary copy %ws wall, CPU (%p%)\n"s); // NOLINT(misc-include-cleaner) bug in clang-tidy 18 + aux = ~out; + out = std::move(aux); + BOOST_TEST( out[35][79] == in[79][35] ); + } + BOOST_TEST( out == ~in ); + } + { + multi::array out = in; + { + watch const unnamed{ "transposition with loop %ws wall, CPU (%p%)\n"s }; + std::for_each(extension(out).begin(), extension(out).end(), [&out](auto idx) { + auto ext = multi::extension_t(0L, idx); + std::for_each(ext.begin(), ext.end(), [&out, idx](auto jdx) { + std::swap(out[idx][jdx], out[jdx][idx]); + }); + }); + BOOST_TEST( out[35][79] == in[79][35] ); + } + BOOST_TEST( out == ~in ); + } + { + multi::array out = in; + { + watch const unnamed{ "transposition with loop 2 %ws wall, CPU (%p%)\n"s }; + std::for_each(extension(out).begin(), extension(out).end(), [&out](auto idx) { + auto ext = multi::extension_t(idx + 1, out.size()); + std::for_each(ext.begin(), ext.end(), [&out, idx](auto jdx) { + std::swap(out[idx][jdx], out[jdx][idx]); + }); + }); + BOOST_TEST( out[35][79] == in[79][35] ); + } + BOOST_TEST( out == ~in ); + } + } +} +return boost::report_errors();} diff --git a/include/boost/multi/adaptors/hipfft.hpp b/include/boost/multi/adaptors/hipfft.hpp new file mode 100644 index 000000000..07e63177d --- /dev/null +++ b/include/boost/multi/adaptors/hipfft.hpp @@ -0,0 +1,64 @@ +// Copyright 2020-2024 Alfredo A. Correa + +#ifndef BOOST_MULTI_ADAPTORS_HIPFFT_HPP +#define BOOST_MULTI_ADAPTORS_HIPFFT_HPP + +#include +#include + +using cudaError_t = hipError_t; + +constexpr static auto const& cudaDeviceReset = hipDeviceReset; +constexpr static auto const& cudaDeviceSynchronize = hipDeviceSynchronize; +constexpr static auto const& cudaSuccess = hipSuccess; + +#define cu2hip_fft(TypeleafnamE) using cufft ## TypeleafnamE = hipfft ## TypeleafnamE + cu2hip_fft(Handle); + cu2hip_fft(DoubleComplex); + cu2hip_fft(Result); +#undef cu2hip_fft + +#define cu2hip_fft(FunctionleafnamE) constexpr static auto const& cufft ## FunctionleafnamE = hipfft ## FunctionleafnamE + cu2hip_fft(Create); + cu2hip_fft(Destroy); + cu2hip_fft(GetSize); + cu2hip_fft(ExecZ2Z); + cu2hip_fft(SetAutoAllocation); + cu2hip_fft(SetWorkArea); + cu2hip_fft(PlanMany); +#undef cu2hip_fft + +#define CU2HIPFFT_(NamE) constexpr static auto const& CUFFT_ ## NamE = HIPFFT_ ## NamE + +CU2HIPFFT_(ALLOC_FAILED); +CU2HIPFFT_(BACKWARD); + +constexpr static auto const& CUFFT_INVERSE = HIPFFT_BACKWARD; + +CU2HIPFFT_(EXEC_FAILED); +CU2HIPFFT_(FORWARD); +CU2HIPFFT_(INCOMPLETE_PARAMETER_LIST); +CU2HIPFFT_(INTERNAL_ERROR); +CU2HIPFFT_(INVALID_DEVICE); +CU2HIPFFT_(INVALID_SIZE); +CU2HIPFFT_(INVALID_TYPE); +CU2HIPFFT_(INVALID_VALUE); +CU2HIPFFT_(INVALID_PLAN); +CU2HIPFFT_(NO_WORKSPACE); +CU2HIPFFT_(NOT_IMPLEMENTED); +CU2HIPFFT_(NOT_SUPPORTED); +CU2HIPFFT_(UNALIGNED_DATA); +CU2HIPFFT_(PARSE_ERROR); +CU2HIPFFT_(SETUP_FAILED); +CU2HIPFFT_(SUCCESS); +CU2HIPFFT_(Z2Z); + +#undef CU2HIPFFT_ + +#include "cufft.hpp" + +// namespace boost::multi{ +// namespace cufft = hipfft; +// } + +#endif // BOOST_MULTI_ADAPTORS_HIPFFT_HPP diff --git a/include/boost/multi/adaptors/hipfft/CMakeLists.txt b/include/boost/multi/adaptors/hipfft/CMakeLists.txt new file mode 100644 index 000000000..ecd6d90e6 --- /dev/null +++ b/include/boost/multi/adaptors/hipfft/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.11) + +project( + boost-multi-adaptors-hipfft + VERSION 0.1 + LANGUAGES CXX +) + +enable_language(HIP) +find_package(hipFFT REQUIRED) +# find_package(rocthrust REQUIRED) + +# if(ENABLE_CUDA OR DEFINED CXXCUDA) +# enable_language(CUDA) +# set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --extended-lambda") +# endif() + +# find_package(CUDA QUIET) +#include_directories(${CUDA_INCLUDE_DIRS}) +#link_libraries(${CUDA_CUFFT_LIBRARIES}) + +# include_directories(${CMAKE_BINARY_DIR}) + +add_subdirectory(test) diff --git a/include/boost/multi/adaptors/hipfft/test/CMakeLists.txt b/include/boost/multi/adaptors/hipfft/test/CMakeLists.txt new file mode 100644 index 000000000..bb082a839 --- /dev/null +++ b/include/boost/multi/adaptors/hipfft/test/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.11) + +project( + boost-multi-adaptors-hipfft-test + VERSION 0.1 + LANGUAGES CXX +) + +enable_language(HIP) +find_package(hipFFT REQUIRED) + +# FFTW has to go before blas to avoid unscrupulous (i.e. MKL) blas implementations that include FFTW and don't implement it properly +find_package(PkgConfig REQUIRED) +pkg_search_module( + FFTW + REQUIRED + fftw3 + IMPORTED_TARGET +) +include_directories(PkgConfig::FFTW) +link_libraries(PkgConfig::FFTW) + +enable_testing() +include(CTest) + +# include_directories(${CMAKE_BINARY_DIR}) + +find_package(Boost COMPONENTS unit_test_framework) + +add_executable(hipfft.cpp.x hipfft.cpp) +set_source_files_properties(hipfft.cpp PROPERTIES LANGUAGE HIP) + +# target_link_libraries(${TEST_EXE} PRIVATE multi) +target_link_libraries(hipfft.cpp.x PRIVATE hip::hipfft Boost::unit_test_framework multi) + +add_test(NAME hipfft.cpp.x COMMAND $) + diff --git a/include/boost/multi/adaptors/hipfft/test/hipfft.cpp b/include/boost/multi/adaptors/hipfft/test/hipfft.cpp new file mode 100644 index 000000000..b76cb6a1f --- /dev/null +++ b/include/boost/multi/adaptors/hipfft/test/hipfft.cpp @@ -0,0 +1,7 @@ +namespace boost::multi::hipfft{} + +// namespace boost::multi{ +// namespace cufft = hipfft; +// } + +#include "../../cufft/test/cufft.cpp" diff --git a/include/multi/adaptors/hipthrust/test/CMakeLists.txt b/include/boost/multi/adaptors/hipthrust/test/CMakeLists.txt similarity index 73% rename from include/multi/adaptors/hipthrust/test/CMakeLists.txt rename to include/boost/multi/adaptors/hipthrust/test/CMakeLists.txt index ec741be2a..4a705e067 100644 --- a/include/multi/adaptors/hipthrust/test/CMakeLists.txt +++ b/include/boost/multi/adaptors/hipthrust/test/CMakeLists.txt @@ -1,7 +1,6 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- cmake_minimum_required(VERSION 3.24) -project(boost-multi-adaptor-thrust-hip LANGUAGES CXX HIP) +project(boost-multi-adaptor-thrust-hip LANGUAGES CXX HIP) find_package(Boost REQUIRED COMPONENTS timer unit_test_framework) find_package(rocthrust REQUIRED) @@ -33,17 +32,24 @@ include(CTest) add_executable(vector.hip.amd.x vector.hip) set_source_files_properties(vector.hip PROPERTIES LANGUAGE HIP) target_link_libraries(vector.hip.amd.x PRIVATE Boost::unit_test_framework roc::rocthrust) -add_test(NAME vector.hip.amd.x COMMAND ./vector.hip.amd.x) +target_compile_definitions(vector.hip.amd.x PRIVATE BOOST_TEST_DYN_LINK=1) + +add_test(NAME vector.hip.amd.x COMMAND $) add_executable(array.hip.amd.x array.hip) set_source_files_properties(array.hip PROPERTIES LANGUAGE HIP) target_link_libraries(array.hip.amd.x PRIVATE Boost::unit_test_framework Boost::timer multi roc::rocthrust) -add_test(NAME array.hip.amd.x COMMAND ./array.hip.amd.x) +target_compile_definitions(array.hip.amd.x PRIVATE BOOST_TEST_DYN_LINK=1) + +add_test(NAME array.hip.amd.x COMMAND $) add_executable(speed.hip.amd.x speed.hip) -set_source_files_properties(array.hip PROPERTIES LANGUAGE HIP) +set_source_files_properties(speed.hip PROPERTIES LANGUAGE HIP) target_link_libraries(speed.hip.amd.x PRIVATE Boost::unit_test_framework Boost::timer multi roc::rocthrust) -add_test(NAME speed.hip.amd.x COMMAND ./speed.hip.amd.x) +target_compile_definitions(speed.hip.amd.x PRIVATE BOOST_TEST_DYN_LINK=1) +target_compile_definitions(speed.hip.amd.x PRIVATE BOOST_TEST_MODULE="C++ Unit Tests for Multi CUDA thrust universal copy and assignment") + +add_test(NAME speed.hip.amd.x COMMAND $) # foreach(TEST_FILE ${TEST_SRCS}) # # set(TEST_EXE "${TEST_FILE}") @@ -54,12 +60,6 @@ add_test(NAME speed.hip.amd.x COMMAND ./speed.hip.amd.x) # # target_include_directories(${TEST_EXE} PRIVATE ../../../../../include) -# target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") -# target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) -# target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) -# target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) -# target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) - # # if(ENABLE_CUDA) # # add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) # # endif() diff --git a/include/multi/adaptors/hipthrust/test/array.hip b/include/boost/multi/adaptors/hipthrust/test/array.hip similarity index 98% rename from include/multi/adaptors/hipthrust/test/array.hip rename to include/boost/multi/adaptors/hipthrust/test/array.hip index f852d8524..121a19f87 100644 --- a/include/multi/adaptors/hipthrust/test/array.hip +++ b/include/boost/multi/adaptors/hipthrust/test/array.hip @@ -1,14 +1,15 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021-2023 Alfredo A. Correa +// Copyright 2021-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #define BOOST_TEST_MODULE "C++ Unit Tests for Multi hip thrust" -#include +// #include #include #include -#include -// #include -// #include +#include +// #include +// #include #include #include @@ -17,8 +18,8 @@ #include -#include -#include +#include +#include #include diff --git a/include/boost/multi/adaptors/hipthrust/test/speed.hip b/include/boost/multi/adaptors/hipthrust/test/speed.hip new file mode 100644 index 000000000..94bbe8734 --- /dev/null +++ b/include/boost/multi/adaptors/hipthrust/test/speed.hip @@ -0,0 +1,267 @@ +// Copyright 2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// #include + +#include + +#include + +#include + +#include + +#include + +#include + +namespace multi = boost::multi; + +// template<> inline constexpr bool multi::force_element_trivial_default_construction> = true; +// template<> inline constexpr bool multi::force_element_trivial_default_construction> = true; + +// template<> inline constexpr bool multi::force_element_trivial_default_construction> = true; +// template<> inline constexpr bool multi::force_element_trivial_default_construction> = true; + +using test_types = boost::mpl::list< + double, ::thrust::complex, std::complex, + float , ::thrust::complex , std::complex, + // char, + unsigned, int +>; + +template +inline void DoNotOptimize(Tp&& value) noexcept { + (hipDeviceSynchronize()==hipSuccess)?void():exit(-1); + asm volatile("" : "+m"(value) : : "memory"); +} + +constexpr auto measure = [](auto&& F) { + auto start_time = std::chrono::high_resolution_clock::now(); + F(); + std::chrono::duration const time = std::chrono::high_resolution_clock::now() - start_time; + DoNotOptimize(F); + return time.count(); +}; + +BOOST_AUTO_TEST_CASE_TEMPLATE(allocation_speed, T, test_types) { + std::cout << typeid(T).name() << " ******************************************\n"; + + auto const n = 8000L; + + auto const size = n*n * sizeof(T) / 1e9; + + // static_assert( std::is_trivial_v || multi::force_element_trivial_default_construction ); + + auto hipmallocfree0 = measure([&]() { + char* Buffer; + if(auto r = hipMalloc((void**)&Buffer , n*n * sizeof(T)); r != hipSuccess) {throw std::runtime_error{""};} + DoNotOptimize(Buffer); + if(auto r = hipFree(Buffer ); r != hipSuccess) {throw std::runtime_error{""};} + DoNotOptimize(Buffer); + }); + + auto hipmallocfree1 = measure([&]() { + char* Buffer; + if(hipMalloc((void**)&Buffer , n*n * sizeof(T)) != hipSuccess) {throw std::runtime_error{""};} + DoNotOptimize(Buffer); + if(auto r = hipFree(Buffer ); r != hipSuccess) {throw std::runtime_error{""};} + DoNotOptimize(Buffer); + }); + + auto hipmallocfree2 = measure([&]() { + char* Buffer; + if(auto r = hipMalloc((void**)&Buffer , n*n * sizeof(T)); r != hipSuccess) {throw std::runtime_error{""};} + DoNotOptimize(Buffer); + if(auto r = hipFree(Buffer ); r != hipSuccess) {throw std::runtime_error{""};} + DoNotOptimize(Buffer); + }); + + auto const hipmallocfree = hipmallocfree2; + + auto const hipallocator = measure([&]() { + thrust::hip::allocator alloc; + auto p = alloc.allocate(n*n); + DoNotOptimize(p); + alloc.deallocate(p, n*n); + DoNotOptimize(p); + }); + + BOOST_TEST( (hipallocator / hipmallocfree) > 0.10 ); + + auto const hipmultiarray = measure([&]() { + multi::array> buffer({n, n}); + DoNotOptimize(buffer); + }); + + BOOST_TEST( (hipmultiarray / hipmallocfree) > 0.10 ); + + auto const hipmultiuniversalarray = measure([&]() { + multi::array> buffer({n, n}); + DoNotOptimize(buffer); + }); + + BOOST_TEST( (hipmultiuniversalarray / hipmallocfree) > 0.80 ); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(thrust_speed, T, test_types) { + std::cout << typeid(T).name() << " ******************************************\n"; + + auto const n = 8000; + + multi::array> src({n, n}, T{12}); + multi::array> dst(extensions(src), T{33}); + + auto const size = src.num_elements() * sizeof(T) / 1e9; + + auto const hipmemcpy0 = measure([&]() { + if(hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice) != hipSuccess) throw std::runtime_error(""); + DoNotOptimize(dst); + }); + + BOOST_REQUIRE( dst == src ); + + DoNotOptimize(src); + DoNotOptimize(dst); + + auto const hipmemcpy1 = measure([&]() { + if(hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice) != hipSuccess) throw std::runtime_error(""); + DoNotOptimize(dst); + }); + + BOOST_REQUIRE( dst == src ); + + DoNotOptimize(src); + DoNotOptimize(dst); + + auto const hipmemcpy = measure([&]() { + hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice) == hipSuccess?:throw std::runtime_error(""); + DoNotOptimize(dst); + }); + + DoNotOptimize(src); + DoNotOptimize(dst); + + std::cout << "hipmemcpy rate = " << size/hipmemcpy << " GB/s\n"; + + auto const hip_copy = measure([&]() { + thrust::copy_n(src.data_elements(), src.num_elements(), dst.data_elements()); + DoNotOptimize(dst); + }); + + DoNotOptimize(src); + DoNotOptimize(dst); + + std::cout << "hip_copy rate = " << size/hip_copy << " GB/s\n"; + + BOOST_TEST( hipmemcpy > 0.8* hip_copy ); + + auto const multi_copy_assign = measure([&]() { + dst = src; + DoNotOptimize(dst); + }); + + DoNotOptimize(src); + DoNotOptimize(dst); + + std::cout << "multi_copy_assign rate = " << size/multi_copy_assign << " GB/s\n"; + + BOOST_TEST( hipmemcpy > 0.8* multi_copy_assign ); + + + auto const hip_copy_elements = measure([&]() { + thrust::copy(src.elements().begin(), src.elements().end(), dst.elements().begin()); + DoNotOptimize(dst); + }); + + DoNotOptimize(src); + DoNotOptimize(dst); + + std::cout << "hip_copy_elements rate = " << size/hip_copy_elements << " GB/s\n"; + + BOOST_TEST( hipmemcpy > 0.8* hip_copy_elements ); + + auto const multi_sub_assign = measure([&]() { + dst({2, n - 2}, {2, n - 2}) = src({2, n - 2}, {2, n - 2}); + DoNotOptimize(dst); + }); + + DoNotOptimize(src); + DoNotOptimize(dst); + + std::cout << "multi_sub_assign rate = " << size/multi_sub_assign << " GB/s\n"; + + BOOST_TEST( hipmemcpy > 0.5* multi_sub_assign ); +} + +#if 0 +BOOST_AUTO_TEST_CASE_TEMPLATE(thrust_universal_speed, T, test_types) { + std::cout << typeid(T).name() << " ******************************************\n"; + + auto const n = 8000; + + multi::array> src({n, n}); + multi::array> dst(extensions(src)); + + auto const threshold = 0.10; + + auto const size = src.num_elements() * sizeof(T) / 1e9; + + auto const dummy = std::invoke([&] { + auto start_time = std::chrono::high_resolution_clock::now(); + auto r = hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice); + assert(r == hipSuccess); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; + auto rate = size / time.count(); + std::cout << "memcpy rate = " << rate << " GB/s (warmup)\n"; + return rate; + }); + + auto const memcpy_rate = std::invoke([&] { + auto start_time = std::chrono::high_resolution_clock::now(); + auto r = hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice); + assert(r == hipSuccess); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; + auto rate = size / time.count(); + std::cout << "memcpy rate = " << rate << " GB/s (ratio = 1)\n"; + return rate; + }); + + { // cctor + auto tick = std::chrono::high_resolution_clock::now(); + + auto dst2 = src; + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + std::cout << "cctor rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + BOOST_TEST(ratio >= threshold); + } + { // assign + auto tick = std::chrono::high_resolution_clock::now(); + + dst = src; + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + std::cout << "assign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + BOOST_TEST(ratio >= threshold); + } + { // subarray assign + auto tick = std::chrono::high_resolution_clock::now(); + + dst({0, n - 2}, {0, n - 2}) = src({2, n}, {2, n}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + std::cout << "subasssign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + BOOST_TEST(ratio >= threshold); + } +} +#endif diff --git a/include/multi/adaptors/hipthrust/test/vector.hip b/include/boost/multi/adaptors/hipthrust/test/vector.hip similarity index 86% rename from include/multi/adaptors/hipthrust/test/vector.hip rename to include/boost/multi/adaptors/hipthrust/test/vector.hip index dd658eaec..3cfb3a4c0 100644 --- a/include/multi/adaptors/hipthrust/test/vector.hip +++ b/include/boost/multi/adaptors/hipthrust/test/vector.hip @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE "C++ Unit Tests for Multi HIP thrust" -#include +// #include #include #include @@ -37,7 +37,7 @@ BOOST_AUTO_TEST_CASE(vector){ D[1] = 88; thrust::device_ptr p = D.data(); // this works with rocm hip 5.6 -// thrust::pointer p = D.data(); // this works with rocm hip 5.6 +// thrust::pointer p = D.data(); // this works with rocm hip 5.6 BOOST_REQUIRE( p[0] == 99 ); diff --git a/include/boost/multi/adaptors/lapack.hpp b/include/boost/multi/adaptors/lapack.hpp new file mode 100644 index 000000000..a8c3b9e81 --- /dev/null +++ b/include/boost/multi/adaptors/lapack.hpp @@ -0,0 +1,6 @@ +// Copyright 2019-2024 Alfredo A. Correa + +#pragma once + +#include "multi/adaptors/lapack/getrf.hpp" +#include "multi/adaptors/lapack/potrf.hpp" diff --git a/include/boost/multi/adaptors/lapack/CMakeLists.txt b/include/boost/multi/adaptors/lapack/CMakeLists.txt new file mode 100644 index 000000000..d790b5956 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.18) # 3.18 for LAPACK::LAPACK + +project( + boost-multi-adaptor-lapack + VERSION 0.1 + LANGUAGES CXX +) + +add_library(multi-lapack INTERFACE) + +if(NOT DEFINED ENABLE_CIRCLE) + return() +endif() + +find_package(LAPACK REQUIRED) + +foreach(lib ${LAPACK_LIBRARIES}) + # https://cmake.org/cmake/help/latest/module/FindBLAS.html#blas-lapack-vendors + if(${lib} MATCHES "mkl") + message("Some LAPACK found matches MKL") + target_compile_definitions(multi-lapack INTERFACE _MULTI_USING_LAPACK_MKL) + # SET(CMAKE_SKIP_BUILD_RPATH FALSE) + # SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + # SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib64") + # SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + # SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib64") + endif() + if(${lib} MATCHES "hpc_sdk") + message("Some LAPACK found matches hpc_sdk (nvhpc)") # nvhpc may still use a different version of BLAS + # add_definitions(-DBLAS_DOT_RETURNS_VOID) + # target_compile_definitions(multi INTERFACE BLAS_DOT_RETURNS_VOID) + endif() + if(${lib} MATCHES "Accelerate") + message("Some LAPACK found matches Accelerate (Apple) [beware of sdot and snrm2 bugs]") + # message(WARNING "Apple Accelerate BLAS is known to have bugs in single precission function `sdot` and `smrm2`, be careful: https://stackoverflow.com/a/77017238/225186, https://fortran-lang.discourse.group/t/how-many-blas-libraries-have-this-error/4454/23, https://forums.developer.apple.com/forums/thread/717757") + # add_definitions(-DBLAS_DOT_RETURNS_VOID) + # target_compile_definitions(multi INTERFACE BLAS_DOT_RETURNS_VOID) + endif() +endforeach() + + +target_link_libraries(multi-lapack INTERFACE multi LAPACK::LAPACK) + +add_subdirectory(./test) diff --git a/include/multi/adaptors/lapack/core.hpp b/include/boost/multi/adaptors/lapack/core.hpp similarity index 59% rename from include/multi/adaptors/lapack/core.hpp rename to include/boost/multi/adaptors/lapack/core.hpp index a5adbc8d9..08642d8b4 100644 --- a/include/multi/adaptors/lapack/core.hpp +++ b/include/boost/multi/adaptors/lapack/core.hpp @@ -1,17 +1,17 @@ -#ifdef COMPILATION_INSTRUCTIONS -(echo "#include\""$0"\"" > $0x.cpp) && clang++ `#-DNDEBUG` -O3 -std=c++14 -Wall -Wextra -Wpedantic -Wfatal-errors -D_TEST_MULTI_ADAPTORS_LAPACK_CORE -DADD_ $0x.cpp -o $0x.x -lblas -llapack && time $0x.x $@ && rm -f $0x.x $0x.cpp; exit -#endif -// Alfredo A. Correa 2019 © +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_LAPACK_CORE_HPP -#define MULTI_ADAPTORS_LAPACK_CORE_HPP +#ifndef BOOST_MULTI_ADAPTORS_LAPACK_CORE_HPP +#define BOOST_MULTI_ADAPTORS_LAPACK_CORE_HPP //#include #include #include -//#include -#include +// #include +// #include +// #include #define s float #define d double @@ -31,10 +31,11 @@ #define LIWORK INTEGER liwork #define IWORK int* -#define xPOTRF(T) v LAPACK(T##potrf)(UPLO, int const& N, T*, int const& LDA, int& INFO) -#define xSYEV(T) v LAPACK(T##syev) (JOBZ, UPLO, int const& N, T*, int const& LDA, T*, T*, LWORK, int& INFO) -#define xSYEVD(T) v LAPACK(T##syevd)(JOBZ, UPLO, int const& N, T*, int const& LDA, T*, T*, LWORK, IWORK, LIWORK, int& INFO) -#define xHEEV(T) v LAPACK(T##heev) (JOBZ, UPLO, int const& N, T*, int const& LDA, T*, T*, LWORK, int& INFO) +// cppcheck-suppress [preprocessorErrorDirective] bug in cppcheck 2.11 +#define xPOTRF(T) v LAPACK(T##potrf)(UPLO, int const& N, T*, int const& LDA, int& INFO) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) +#define xSYEV(T) v LAPACK(T##syev) (JOBZ, UPLO, int const& N, T*, int const& LDA, T*, T*, LWORK, int& INFO) // NOLINT(bugprone-macro-parentheses) +#define xSYEVD(T) v LAPACK(T##syevd)(JOBZ, UPLO, int const& N, T*, int const& LDA, T*, T*, LWORK, IWORK, LIWORK, int& INFO) // NOLINT(bugprone-macro-parentheses) +#define xHEEV(T) v LAPACK(T##heev) (JOBZ, UPLO, int const& N, T*, int const& LDA, T*, T*, LWORK, int& INFO) // NOLINT(bugprone-macro-parentheses) #define subroutine void #define integer int const& @@ -45,70 +46,80 @@ // http://www.netlib.org/lapack/explore-html/dd/d9a/group__double_g_ecomputational_ga0019443faea08275ca60a734d0593e60.html #define xGETRF(T) \ -subroutine T##getrf_( \ - integer M, /*The number of rows of the matrix A. M >= 0.*/ \ - integer N, /*The number of columns of the matrix A. N >= 0.*/ \ - T* A, /*On entry, the M-by-N matrix to be factored.*/ \ +/*NOLINTBEGIN(readability-identifier-length,bugprone-macro-parentheses)*/ \ +void T##getrf_( \ + integer M, /*The number of rows of the matrix A. M >= 0.*/ \ + integer N, /*The number of columns of the matrix A. N >= 0.*/ \ + T* A, /*On entry, the M-by-N matrix to be factored.*/ \ /*On exit, the factors L and U from the factorization*/ \ - integer LDA, /*The leading dimension of the array A. LDA >= max(1,M).*/\ + integer LDA, /*The leading dimension of the array A. LDA >= max(1,M).*/\ integer_ptr IPIV, /*The pivot indices; for 1 <= i <= min(M,N), row i of the matrix was interchanged with row IPIV(i).*/\ integer_out INFO /*= 0: successful exit*/\ /*< 0: if INFO = -i, the i-th argument had an illegal value*/\ /*> 0: if INFO = i, U(i,i) is exactly zero. The factorization has been completed, but the factor U is exactly singular, and division by zero will occur if it is used to solve a system of equations.*/\ -) +) \ +/*NOLINTEND(readability-identifier-length,bugprone-macro-parentheses)*/ +// NOLINTBEGIN(bugprone-macro-parentheses) +// NOLINTBEGIN(readability-identifier-length) // http://www.netlib.org/lapack/explore-html/d8/ddc/group__real_g_ecomputational_gaa00bcf4d83a118cb6f0b6619d6ffaa24.html #define xGETRS(T) \ -subroutine T##getrs_( \ +void T##getrs_( \ character TRANS,/*Specifies the form of the system of equations: */\ /* = 'N': A * X = B (No transpose) */\ /* = 'T': A**T* X = B (Transpose) */\ /* = 'C': A**T* X = B (Conjugate transpose = Transpose) */\ - integer N, /*The order of the matrix A. N >= 0. */\ - integer NRHS, /*The number of right hand sides, i.e., the number of columns*/\ + integer N, /*The order of the matrix A. N >= 0. */\ + integer NRHS, /*The number of right hand sides, i.e., the number of columns*/\ /*of the matrix B. NRHS >= 0. */\ T const* A, /* The factors L and U from the factorization A = P*L*U */\ /*as computed by SGETRF. */\ - integer LDA, /*The leading dimension of the array A. LDA >= max(1,N). */\ + integer LDA, /*The leading dimension of the array A. LDA >= max(1,N). */\ integer_cptr IPIV, /*The pivot indices from SGETRF; for 1<=i<=N, row i of the */\ /*matrix was interchanged with row IPIV(i). */\ T* B, /*On entry, the right hand side matrix B. */\ /*On exit, the solution matrix X. */\ integer LDB, /*The leading dimension of the array B. LDB >= max(1,N). */\ - integer INFO /*= 0: successful exit */\ + integer_out INFO /*= 0: successful exit */\ /*< 0: if INFO = -i, the i-th argument had an illegal value */\ ) +// NOLINTEND(readability-identifier-length) +// NOLINTEND(bugprone-macro-parentheses) -// TODO // http://www.netlib.org/lapack/explore-html/d7/d3b/group__double_g_esolve_ga5ee879032a8365897c3ba91e3dc8d512.html - +// TODO(correaa) // http://www.netlib.org/lapack/explore-html/d7/d3b/group__double_g_esolve_ga5ee879032a8365897c3ba91e3dc8d512.html extern "C"{ -//xGETRF(s) ; xGETRF(d) ; xGETRF(c) ; xGETRF(z) ; -//xGETRS(s) ; xGETRS(d) ; xGETRS(c) ; xGETRS(z) ; +xGETRF(s) ; xGETRF(d) ; xGETRF(c) ; xGETRF(z) ; +xGETRS(s) ; xGETRS(d) ; xGETRS(c) ; xGETRS(z) ; } -namespace core{ +namespace core { // http://www.netlib.org/lapack/explore-html/da/d30/a18643_ga5b625680e6251feb29e386193914981c.html -int getrf(lapack_int m, lapack_int n, double* A, lapack_int lda, int* ipiv){ +using lapack_int = int; + +// TODO(correaa) make into a template, then remove inline +inline auto getrf(lapack_int m, lapack_int n, double* A, lapack_int lda, int* ipiv) -> int { // NOLINT(readability-identifier-length) lapack conventional name assert( m >= 0 ); assert( n >= 0 ); assert( lda >= std::max(lapack_int{1}, m) ); - int info; - dgetrf_(&m, &n, A, &lda, ipiv, &info); + int info; // NOLINT(cppcoreguidelines-init-variables) delayed initialization + dgetrf_(m, n, A, lda, ipiv, info); assert(info >= 0); return info; } -void getrs(char trans, lapack_int const n, lapack_int const nrhs, double const* A, lapack_int const lda, int const* ipiv, double* B, lapack_int const ldb){ - assert( trans == 'T' or trans == 'N' or trans == 'C' ); +// TODO(correaa) make into a template, then remove inline +inline void getrs(char trans, lapack_int const n, lapack_int const nrhs, double const* A, lapack_int const lda, int const* ipiv, double* B, lapack_int const ldb) { // NOLINT(readability-identifier-length) lapack conventional name + assert( trans == 'T' || trans == 'N' || trans == 'C' ); assert( n >= 0 ); assert( nrhs >= 0 ); assert( lda >= std::max(1, n) ); - int info; - dgetrs_(&trans, &n, &nrhs, A, &lda, ipiv, B, &ldb, &info); + int info; // NOLINT(cppcoreguidelines-init-variables) delayed initialization + dgetrs_(trans, n, nrhs, A, lda, ipiv, B, ldb, info); + switch(info){ - case -1: throw std::logic_error{"transa ≠ 'N', 'T', or 'C'"}; + case -1: throw std::logic_error{"transa != 'N', 'T', or 'C'"}; case -2: throw std::logic_error{"n < 0" }; case -3: throw std::logic_error{"nrhs < 0" }; case -4: throw std::logic_error{"n > lda" }; @@ -116,29 +127,28 @@ void getrs(char trans, lapack_int const n, lapack_int const nrhs, double const* case -6: throw std::logic_error{"n > ldb" }; case -7: throw std::logic_error{"ldb ≤ 0" }; case -8: throw std::logic_error{"error!" }; + default: assert(info == 0); } - assert(info == 0 ); - return; } -} +} // end namespace core -namespace lapack{ +namespace lapack { struct context{ - template static auto getrf(Args&&... args)->decltype(core::getrf(args...)){return core::getrf(args...);} - template static auto getrs(Args&&... args)->decltype(core::getrs(args...)){return core::getrs(args...);} + template static auto getrf(Args&&... args)->decltype(core::getrf(args...)){return core::getrf(std::forward(args)...);} + template static auto getrs(Args&&... args)->decltype(core::getrs(args...)){return core::getrs(std::forward(args)...);} }; -} +} // end namespace lapack -extern "C"{ -//xPOTRF(s) ; xPOTRF(d) ; -//xPOTRF(c) ; xPOTRF(z) ; +extern "C" { +xPOTRF(s) ; xPOTRF(d) ; +xPOTRF(c) ; xPOTRF(z) ; //xSYEV(s) ; xSYEV(d) ; //xSYEVD(s) ; xSYEVD(d) ; -// xHEEV(c) ; xHEEV(z) ; +//xHEEV(c) ; xHEEV(z) ; } #undef subroutine @@ -156,15 +166,18 @@ extern "C"{ #undef INT -#define xpotrf(T) template v potrf(char uplo, S n, T *x, S incx, int& info){LAPACK(T##potrf)(uplo, n, x, incx, info);} +#define xpotrf(T) template v potrf(char uplo, S n, T *x, S incx, int& info){LAPACK(T##potrf)(uplo, n, x, incx, info);} // NOLINT(bugprone-macro-parentheses,readability-identifier-length) -namespace core{ +namespace core { xpotrf(s) xpotrf(d) xpotrf(c) xpotrf(z) -} +} //end namespace core + +// NOLINTBEGIN(bugprone-macro-parentheses) // http://www.netlib.org/lapack/explore-html/d2/d8a/group__double_s_yeigen_ga442c43fca5493590f8f26cf42fed4044.html #define xsyev(T) template v syev(char jobz, char uplo, S n, T* a, S lda, T* w, T* work, S lwork, int& info){LAPACK(T##syev)(jobz, uplo, n, a, lda, w, work, lwork, info);} + // http://www.netlib.org/lapack/explore-html/d2/d8a/group__double_s_yeigen_ga77dfa610458b6c9bd7db52533bfd53a1.html #define xsyevd(T) template v syevd(char jobz, char uplo, S n, T* a, S lda, T* w, T* work, S lwork, int* iwork, S liwork, int& info){ \ if(n <= 1 ){assert(lwork >= 1 ); assert(liwork >=1 );} \ @@ -172,13 +185,15 @@ xpotrf(c) xpotrf(z) if(jobz == 'V' and n > 1){assert(lwork >= 1 + 6*n + 2*n*n); assert(liwork >= 3 + 5*n);} \ LAPACK(T##syevd)(jobz, uplo, n, a, lda, w, work, lwork, iwork, liwork, info); \ } + #define xheev(T) template v heev(char jobz, char uplo, S n, T* a, S lda, T* w, T* work, S lwork, int& info){LAPACK(T##heev)(jobz, uplo, n, a, lda, w, work, lwork, info);} +// NOLINTEND(bugprone-macro-parentheses) -namespace core{ -xsyev (s) xsyev (d) -xsyevd(s) xsyevd(d) - xheev(c) xheev(z) -} +// namespace core{ +// // xsyev (s) xsyev (d) +// // xsyevd(s) xsyevd(d) +// // xheev(c) xheev(z) +// } #undef s #undef d @@ -188,41 +203,4 @@ xsyevd(s) xsyevd(d) #define TRANS const char& trans -/////////////////////////////////////////////////////////////////////////////// - -#if _TEST_MULTI_ADAPTORS_LAPACK_CORE - -#include "../../array.hpp" -#include "../../utility.hpp" - -#include -#include -#include - -namespace multi = boost::multi; -using std::cout; - -int main(){ - using core::potrf; - - std::vector v = { - 2., 1., - 1., 2. - }; - cout - << v[0] <<'\t'<< v[1] <<'\n' - << v[2] <<'\t'<< v[3] <<'\n' << std::endl - ; - int info; - potrf('U', 2, v.data(), 2, info); - cout << "error " << info << std::endl; - cout - << v[0] <<'\t'<< v[1] <<'\n' - << v[2] <<'\t'<< v[3] <<'\n' - ; - cout << std::endl; -} - #endif -#endif - diff --git a/include/multi/adaptors/lapack/cuda.hpp b/include/boost/multi/adaptors/lapack/cuda.hpp similarity index 99% rename from include/multi/adaptors/lapack/cuda.hpp rename to include/boost/multi/adaptors/lapack/cuda.hpp index f478332ed..6a17a0a38 100644 --- a/include/multi/adaptors/lapack/cuda.hpp +++ b/include/boost/multi/adaptors/lapack/cuda.hpp @@ -59,7 +59,7 @@ struct error_category : std::error_category{ static error_category& instance(){static cusolver::error_category instance; return instance;} }; inline std::error_code make_error_code(cusolver::status s) noexcept{ - return std::error_code(int(s), cusolver::error_category::instance()); + return {int(s), cusolver::error_category::instance()}; } struct version_t{ diff --git a/include/boost/multi/adaptors/lapack/filling.hpp b/include/boost/multi/adaptors/lapack/filling.hpp new file mode 100644 index 000000000..70e320c71 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/filling.hpp @@ -0,0 +1,31 @@ +// Copyright 2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_LAPACK_FILLING_HPP +#define BOOST_MULTI_ADAPTORS_LAPACK_FILLING_HPP +#pragma once + +// TODO(correaa) #include "multi/blas/filling.hpp" + +namespace boost::multi::lapack { + +enum class filling : char { + lower = 'U', + upper = 'L', +}; + +inline auto flip(filling side) -> filling { + switch(side) { + case filling::lower: return filling::upper; + case filling::upper: return filling::lower; + } + __builtin_unreachable(); // LCOV_EXCL_LINE +} + +inline auto operator-(filling side) -> filling { return flip(side); } +inline auto operator+(filling side) -> filling { return side; } + +} // namespace boost::multi::lapack + +#endif diff --git a/include/boost/multi/adaptors/lapack/geqrf.hpp b/include/boost/multi/adaptors/lapack/geqrf.hpp new file mode 100644 index 000000000..6657ff940 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/geqrf.hpp @@ -0,0 +1,80 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_LAPACK_GEQRF_HPP +#define BOOST_MULTI_ADAPTORS_LAPACK_GEQRF_HPP + +#include +// #include +#include // for size + +#include // for min +#include +#include // for runtime_error +#include // for operator+, to_string, allocator +#include // for forward + +extern "C" { +using integer = int const&; + +void dgeqrf_( // NOLINT(readability-identifier-naming) externally linked + integer, // M, + integer, // N, + double*, // A double precision, dimension( lda, * ) A, + integer, // LDA, + double*, // TAU, // double precision, dimension( * ) TAU, + double*, // WORK, // double precision, dimension( * ) WORK, + integer, // LWORK, + integer // INFO +); +} + +// namespace boost{namespace multi{namespace lapack{ +namespace boost::multi::lapack { + +using blas::filling; + +template +auto geqrf(Array2D&& aa, TAU& tau, Allocator alloc) -> Array2D&& { +// assert( stride(~a) == 1); + assert( size(tau) == std::min(size(~aa), size(aa)) ); + + double dwork; // NOLINT(cppcoreguidelines-init-variables) delayed initialization + int info; // NOLINT(cppcoreguidelines-init-variables) delayed initialization + dgeqrf_( + size(~aa), size(aa), aa.base(), aa.stride(), + tau.base(), + &dwork, -1, + info // cppcheck-suppress uninitvar ; + ); + if(info != 0) { // cppcheck-suppress identicalConditionAfterEarlyExit ; TODO(correaa) investigate + throw std::runtime_error("Error in DGEQRF work estimation, info: " + std::to_string(info)); + } + + auto const lwork = static_cast(dwork); + auto* const work = alloc.allocate(lwork); + + dgeqrf_( + size(~aa), size(aa), aa.base(), aa.stride(), + tau.base(), + work, lwork, + info + ); + alloc.deallocate(work, lwork); + + if(info != 0) { // cppcheck-suppress identicalConditionAfterEarlyExit ; TODO(correaa) investigate + throw std::runtime_error("Error in DGESVD computation, info: " + std::to_string(info)); + } + + return std::forward(aa); +} + +template> +auto geqrf(Array2D&& aa, TAU&& tau) -> Array2D&& { + return geqrf(std::forward(aa), std::forward(tau), Allocator{}); +} + +} // end namespace boost::multi::lapack + +#endif diff --git a/include/boost/multi/adaptors/lapack/gesvd.hpp b/include/boost/multi/adaptors/lapack/gesvd.hpp new file mode 100644 index 000000000..98cb61d28 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/gesvd.hpp @@ -0,0 +1,97 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_LAPACK_GESVD_HPP +#define BOOST_MULTI_ADAPTORS_LAPACK_GESVD_HPP + +#include +#include +#include +#include +#include + +#define MLP_INT int32_t // int64_t // #define INT int64_t + +#include + +extern "C" { +void dgesvd_(char const& jobu, char const& jobvt, int const& mm, int const& nn, double* aa, int const& lda, double* ss, double* uu, int const& ldu, double* vt, int const& ldvt, double* work, int const& lwork, int& info); // NOLINT_INT(readability-identifier-naming) +} + +namespace boost::multi::lapack { + +template +void gesvd(AArray2D&& AA, UArray2D& UU, SArray1D& ss, VTArray2D& VV, Alloc alloc) { + assert(AA.size() == UU.size()); + assert((~AA).size() == VV.size()); + assert(ss.size() == std::min(UU.size(), VV.size())); + + assert((~AA).stride() == 1); + assert(ss.stride() == 1); + assert((~UU).stride() == 1); + assert((~VV).stride() == 1); + + MLP_INT info; // NOLINT(cppcoreguidelines-init-variables) init by function + double dwork; // NOLINT(cppcoreguidelines-init-variables) init by function + + dgesvd_( + 'A' /*all left*/, 'A' /*all right*/, + static_cast(VV.size()), static_cast(UU.size()), + AA.base(), static_cast(AA.stride()), + ss.base(), + VV.base(), static_cast(VV.stride()), + UU.base(), static_cast(UU.stride()), + &dwork, -1, info + ); + if(info != 0) { + throw std::runtime_error("Error in DGESVD work estimation, info: " + std::to_string(info)); + } + + auto const lwork = static_cast(dwork); + auto* const work = alloc.allocate(lwork); + + dgesvd_( + 'A' /*all left*/, 'A' /*all right*/, + static_cast(VV.size()), static_cast(UU.size()), + AA.base(), static_cast(AA.stride()), + ss.base(), + VV.base(), static_cast(VV.stride()), + UU.base(), static_cast(UU.stride()), + work, lwork, info + ); + alloc.deallocate(work, lwork); + + if(info != 0) { + throw std::runtime_error("Error in DGESVD computation, info: " + std::to_string(info)); + } + + (void)std::forward(AA); +} + +template< + template class AllocT = std::allocator, + class AArray2D, class UArray2D, class SArray1D, class VTArray2D, + class Alloc = AllocT::element_type>> +void gesvd(AArray2D&& AA, UArray2D&& UU, SArray1D&& ss, VTArray2D&& VV) { + return gesvd(std::forward(AA), std::forward(UU), std::forward(ss), std::forward(VV), Alloc{}); +} + +template +auto gesvd(Array2D const& AA) { + auto AA_copy = AA; + auto ret = std::make_tuple( + ::boost::multi::array({AA.size(), AA.size()}), // UU // Right singular vectors + ::boost::multi::array(std::min(AA.size(), (~AA).size())), // ss // Singular values + ::boost::multi::array({(~AA).size(), (~AA).size()}) // VV // Left singular vectors + ); + gesvd(AA_copy, std::get<0>(ret), std::get<1>(ret), std::get<2>(ret)); +#if defined(_MULTI_USING_LAPACK_MKL) + std::get<2>(ret) = +~std::get<2>(ret); // MKL returns the transpose of the right singular vectors +#endif + return ret; +} + +} // end namespace boost::multi::lapack + +#endif // BOOST_MULTI_ADAPTORS_LAPACK_GESVD_HPP diff --git a/include/boost/multi/adaptors/lapack/getrf.hpp b/include/boost/multi/adaptors/lapack/getrf.hpp new file mode 100644 index 000000000..fdea4206f --- /dev/null +++ b/include/boost/multi/adaptors/lapack/getrf.hpp @@ -0,0 +1,65 @@ +// Copyright 2020-2025 Alfredo A. Correa + +#ifndef BOOST_MULTI_ADAPTORS_LAPACK_GETRF_HPP +#define BOOST_MULTI_ADAPTORS_LAPACK_GETRF_HPP + +#include +#include + +#include + +#include + +namespace boost::multi::lapack { + +using index = int; + +using blas::filling; + +template +auto getrf(Context&& ctxt, A&& arr, IPIV&& ipiv){ + assert( ipiv.size() == std::min(size(arr), size(~arr)) ); + assert( stride(arr) == 1 ); +// assert( stride(ipiv) == 1 ); + multi::index const i = std::forward(ctxt).getrf(size(~arr), size(arr), arr.base(), stride(~arr), std::forward(ipiv).data() ); + // if(i == 0) { return arr(); } + // else { return arr({0, i - 1}, {0, i - 1}); } + if(i == 0) { return std::forward(arr)(); } + return std::forward(arr)({0, i - 1}, {0, i - 1}); +} + +template +void getrs(Context&& ctxt, LU const& lu, IPIV const& ipiv, B&& barr){ + assert( size(lu) == size(~lu) ); + assert( stride(lu) == 1 ); + assert( size(ipiv) >= size(lu) ); +// assert( stride(ipiv) == 1 ); + assert( stride(barr) == 1 ); + std::forward(ctxt).getrs('N', size(lu), size(~barr), lu.base(), stride(~lu), ipiv.data(), std::forward(barr).base(), stride(~barr)); +} + +template +void getrs_one(Context&& ctxt, LU const& lu, IPIV const& ipiv, V&& barr){ + assert( size(lu) == size(~lu) ); + assert( stride(lu) == 1 ); +// assert( stride(ipiv) == 1 ); + assert( stride(barr) == 1 ); + std::forward(ctxt).getrs('N', size(lu), 1, lu.base(), stride(~lu), ipiv.data(), std::forward(barr).base(), size(lu)); +} + + +template +auto getrf(A&& arr, IPIV&& ipiv){return getrf(::lapack::context{}, std::forward(arr), std::forward(ipiv));} + +template +void getrs(LU const& lu, IPIV const& ipiv, B&& barr){return getrs(::lapack::context{}, lu, ipiv, std::forward(barr));} + +template +void getrs_one(LU const& lu, IPIV const& ipiv, B&& barr){return getrs_one(::lapack::context{}, lu, ipiv, std::forward(barr));} + + +} // namespace boost::multi::lapack + +#endif + + diff --git a/include/boost/multi/adaptors/lapack/potrf.hpp b/include/boost/multi/adaptors/lapack/potrf.hpp new file mode 100644 index 000000000..afa96b8eb --- /dev/null +++ b/include/boost/multi/adaptors/lapack/potrf.hpp @@ -0,0 +1,100 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_LAPACK_POTRF_HPP +#define BOOST_MULTI_ADAPTORS_LAPACK_POTRF_HPP +#pragma once + +#include +#include + +#include "../lapack/core.hpp" +#include "../lapack/filling.hpp" + +#include "../blas/numeric.hpp" + +#include + +namespace boost::multi::lapack { + +// using blas::filling; + +using ::core::potrf; + +template +BOOST_MULTI_NODISCARD("result has information of order of minor through .size() member") +auto potrf(filling uplo, Iterator first, Iterator last) +->decltype(potrf(static_cast(uplo), typename std::iterator_traits::difference_type{}, first.base(), stride(first), std::declval()), Iterator{}) +{ + assert( stride(first) == stride(last) ); + assert( first->stride() == 1 ); +// auto lda = stride(first); + + int info; // NOLINT(cppcoreguidelines-init-variables) + potrf(static_cast(uplo), std::distance(first, last), first.base(), stride(first), info); + + assert( info >= 0 ); + // if(info > 0) {std::cerr << "warning minor of order " << info << " is not possitive\n";} + return info==0?last:first + info - 1; +} + +template +BOOST_MULTI_NODISCARD("result has information of order of minor through .size() member") +auto potrf(filling uplo, A2D&& A) // NOLINT(readability-identifier-length) conventional lapack name +->decltype(potrf(uplo, begin(A), end(A)), A({0, 1})) +{ + using lapack::flip; + + if(stride(A) == 1) { + auto last = potrf(flip(uplo), A.rotated().begin(), A.rotated().end()); + using std::distance; + return A({0, distance(A.rotated().begin(), last)}, {0, distance(A.rotated().begin(), last)}); + } + + auto last = potrf(uplo, begin(A), end(A)); + + using std::distance; + return std::forward(A)({0, distance(begin(A), last)}); // , {0, distance(begin(A), last-1)}); +} + +template +struct hermitic_t : private A { + using underlying_type = A; + + auto underlying() const & -> underlying_type const& {return *this;} + auto underlying() & -> underlying_type & {return *this;} + auto underlying() && -> underlying_type && {return std::move(*this);} + + private: + lapack::filling side_; + + public: + auto side() const {return side_;} + + hermitic_t(A const& a, lapack::filling side) : A{a}, side_{side} {} // NOLINT(readability-identifier-length) conventional lapack name + using A::size; +}; + +template auto hermitic(lapack::filling side, A&& a) // NOLINT(readability-identifier-length) conventional lapack name +-> hermitic_t()())>> { + return {std::forward(a)(), side}; +} + +template +BOOST_MULTI_NODISCARD("result is returned because third argument is const") +auto potrf(HA&& ha) -> decltype(auto) { + return hermitic(ha.side, potrf(ha.side, std::forward(ha).underlying())); // static_cast(ha))); +} + +// orthonormalize rows +template auto onrm(A&& a, filling uplo /*= filling::upper*/) // NOLINT(readability-identifier-length) conventional lapack name +->decltype(trsm(flip(uplo), hermitized(potrf(uplo, herk(uplo, a))), std::forward(a))) { assert(size(a) <= size(rotated(a))); + return trsm(flip(uplo), hermitized(potrf(uplo, herk(uplo, a))), std::forward(a)); } + +template auto onrm(A&& a, B&& buffer, filling uplo /* = filling::upper*/) // NOLINT(readability-identifier-length) conventional lapack name +->decltype(trsm(flip(uplo), hermitized(potrf(uplo, herk(uplo, a, std::forward(buffer)))), std::forward(a))) { assert(size(a) <= size(rotated(a))); + return trsm(flip(uplo), hermitized(potrf(uplo, herk(uplo, a, std::forward(buffer)))), std::forward(a)); } + +} // end namespace boost::multi::lapack +#endif diff --git a/include/boost/multi/adaptors/lapack/syev.hpp b/include/boost/multi/adaptors/lapack/syev.hpp new file mode 100644 index 000000000..d38e9bb34 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/syev.hpp @@ -0,0 +1,90 @@ +// Copyright 2020-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_LAPACK_SYEV_HPP +#define BOOST_MULTI_ADAPTORS_LAPACK_SYEV_HPP + +#include + +namespace boost { +namespace multi { +namespace lapack { + +using blas::filling; + +using ::core::syev; + +template +auto syev(blas::filling uplo, Array2D&& a, Array1D&& w, Array1DW&& work) + -> decltype(syev('V', uplo == blas::filling::upper ? 'L' : 'U', size(a), base(a), stride(a), base(w), base(work), size(work), std::declval()), a({0L, 1L}, {0L, 1L})) { + assert(size(work) >= std::max(1L, 3 * size(a) - 1L)); + assert(size(a) == size(w)); + assert(stride(w) == 1); + assert(stride(work) == 1); + + if(size(a) == 0) + return std::forward(a)(); + + int info = -1; + + if(stride(rotated(a)) == 1) { + syev('V', uplo == blas::filling::upper ? 'L' : 'U', size(a), base(a), stride(a), base(w), base(work), size(work), info); + } else if(stride(a) == 1) { + syev('V', uplo == blas::filling::upper ? 'U' : 'L', size(a), base(a), stride(rotated(a)), base(w), base(work), size(work), info); + } else { + assert(0); + } // case not contemplated by lapack + + if(info < 0) { + assert(0); + } // bad argument + + return std::forward(a)({0, size(a) - info}, {0, size(a) - info}); +} + +template::decay_type> +auto syev(blas::filling uplo, Array2D&& a, Array1D&& w) + -> decltype(syev(uplo, std::forward(a), std::forward(w), Array1DW(std::max(1L, 3 * size(a) - 1L), get_allocator(w)))) { + return syev(uplo, std::forward(a), std::forward(w), Array1DW(std::max(1L, 3 * size(a) - 1L), get_allocator(w))); +} // TODO(correaa) obtain automatic size from lapack info routine + +template +NODISCARD("because input array is const, output gives eigenvectors") +typename Array2D::decay_type syev(blas::filling uplo, Array2D const& a, Array1D&& w) { + auto ret = a.decay(); + auto l = syev(uplo, ret, std::forward(w)); + if(size(l) != size(a)) + assert(0); // failed + return ret; +} + +template +NODISCARD("because input array is const, output gives eigenvalues") +auto syev(blas::filling uplo, Array2D&& a) { + multi::array::element_type, 1, decltype(get_allocator(a))> eigenvalues(size(a), get_allocator(a)); + syev(uplo, std::forward(a), eigenvalues); + return eigenvalues; +} + +template +NODISCARD("because input array is const, output gives a structured binding of eigenvectors and eigenvactor") +auto syev(blas::filling uplo, Array2D const& a) { + struct { + typename Array2D::decay_type eigenvectors; + typename Array2D::value_type eigenvalues; + } ret{a, typename Array2D::value_type(size(a), get_allocator(a))}; + auto&& l = syev(uplo, ret.eigenvectors, ret.eigenvalues); + assert(size(l) == size(a)); + return ret; +} + +} // namespace lapack +} // namespace multi +} // namespace boost +#endif // BOOST_MULTI_ADAPTORS_LAPACK_SYEV_HPP diff --git a/include/boost/multi/adaptors/lapack/test/CMakeLists.txt b/include/boost/multi/adaptors/lapack/test/CMakeLists.txt new file mode 100644 index 000000000..4dbf7fcc1 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/test/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 3.10) + +if(ENABLE_CUDA) + enable_language(CUDA) +endif() + +# project( +# boost-multi-adaptors-lapack-test +# VERSION 0.1 +# LANGUAGES CXX +# ) + +# set(CMAKE_CXX_STANDARD_REQUIRED ON) +# set(CMAKE_CXX_EXTENSIONS OFF) + +find_package(Boost REQUIRED NO_MODULE) + +#find_package(LAPACK REQUIRED) + +# find_package(BLAS REQUIRED) + +# set(BLA_VENDOR OpenBLAS) +# set(BLA_VENDOR Intel10_64lp) find_package(BLAS) if(BLAS_FOUND) # in some systems with MKL, regular BLAS headers need to be found for it to work message("Multi/BLAS: MKL environment detected") add_definitions(-DRETURN_BY_STACK) else() +# message("Multi/BLAS: MKL environment not detected, looking for other BLAS") unset(BLA_VENDOR) +# find_package(BLAS REQUIRED) +# endif() + +# find_path( +# BLAS_INCLUDE_DIRS +# cblas.h +# /usr/include +# /usr/local/include +# $ENV{BLAS_HOME}/include +# ) + +# include_directories(../../../../../include) + +# link_libraries(${BLAS_LIBRARIES}) +#link_libraries(-llapacke) + +# include_directories(${TEST_EXE} PRIVATE ${BLAS_INCLUDE_DIRS}) + +enable_testing() +include(CTest) + +add_executable(getrf.cpp.x getrf.cpp) +add_executable(geqrf.cpp.x geqrf.cpp) +add_executable(potrf.cpp.x potrf.cpp) +add_executable(svd.cpp.x svd.cpp) + +if(ENABLE_CUDA) + set_source_files_properties(potrf.cpp PROPERTIES LANGUAGE CUDA) +endif() + +target_link_libraries(getrf.cpp.x PUBLIC multi-lapack Boost::boost) +target_link_libraries(geqrf.cpp.x PUBLIC multi-lapack Boost::boost) +target_link_libraries(potrf.cpp.x PRIVATE multi-lapack Boost::boost) +target_link_libraries(svd.cpp.x PUBLIC multi-lapack Boost::boost) + +add_test(NAME getrf.cpp.x COMMAND $) +add_test(NAME geqrf.cpp.x COMMAND $) +add_test(NAME potrf.cpp.x COMMAND $) +add_test(NAME svd.cpp.x COMMAND $ ) diff --git a/include/boost/multi/adaptors/lapack/test/geqrf.cpp b/include/boost/multi/adaptors/lapack/test/geqrf.cpp new file mode 100644 index 000000000..f071ca91a --- /dev/null +++ b/include/boost/multi/adaptors/lapack/test/geqrf.cpp @@ -0,0 +1,344 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +// #include +#include + +#include + +#include +// #include // for std::abs +// IWYU pragma: no_include // for std::allocator + +namespace multi = boost::multi; + +// template auto print(Array const& arr) -> decltype(auto) { +// using std::cout; +// using multi::size; +// for(int i = 0; i != arr.size(); ++i) { +// for(int j = 0; j != arr[i].size(); ++j) { cout << arr[i][j] << ' '; } // NOLINT(altera-unroll-loops) +// cout << '\n'; +// } +// return cout << '\n'; +// } + +// template auto print_1d(Array1D const& arr) -> decltype(auto) { +// using std::cout; +// using multi::size; +// for(int i = 0; i != size(arr); ++i) { cout<< arr[i] <<' '; } // NOLINT(altera-unroll-loops) +// return cout << '\n'; +// } + +auto main() -> int { // NOLINT(bugprone-exception-escape) + multi::array AA = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + {7.0, 8.0, 9.0} + }; + // lapack::context ctxt; + + multi::array TAU(std::min(AA.size(), (~AA).size())); + // multi::array WORK(std::max(1l, 3*size(A)-1)); + + multi::lapack::geqrf(AA, TAU); + + // print(A); + // print(TAU); + + // { + // multi::array const AA = { + // {0.5, 1.0}, + // {2.0, 2.5}, + // }; + + // auto const [UU, ss, VV] = multi::lapack::gesvd(AA); // AA == UU.Diag(ss).(VV^T) + + // multi::array SS({ss.size(), ss.size()}, 0.0); + // SS.diagonal() = ss; + + // auto const AA_test = +multi::blas::gemm(1.0, UU, +multi::blas::gemm(1.0, SS, ~VV)); // A_test <- UU * SS * VV^T + + // BOOST_TEST( std::abs(AA_test[0][0] - AA[0][0]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[0][1] - AA[0][1]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[1][0] - AA[1][0]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[1][1] - AA[1][1]) < 1.0e-4 ); + // } + + // { + // multi::array AA = { + // { 2.27, 0.94, 1.07, 0.63, -2.35, 0.62}, + // {-1.54, -0.78, 1.22, 2.93, 2.30, -7.39}, + // { 1.15, -0.48, 0.79, -1.45, 1.03, 1.03}, + // {-1.94, -3.09, 0.63, 2.30, -2.57, -2.57}, + // }; + + // auto const AA_copy = AA; + + // // Output arrays + // multi::array ss(std::min(AA.size(), (~AA).size())); // Singular values + + // multi::array UU({(~AA).size(), (~AA).size()}); // Left singular vectors + // multi::array VT({AA.size(), AA.size()}); // Right singular vectors + + // boost::multi::lapack::gesvd(AA, UU, ss, VT); + + // std::cout << "Original array:\n"; + // { + // auto [is, js] = AA.extensions(); + // for(auto i : is) { + // for(auto j : js) { // NOLINT(altera-unroll-loops) + // std::cout << AA_copy[i][j] << ' '; + // } + // std::cout << '\n'; + // } + // } + + // multi::array SS({ss.extension(), ss.extension()}, 0.0); + // std::copy(ss.begin(), ss.end(), SS.diagonal().begin()); + + // // Print singular values + // std::cout << "Singular values:\n"; + // for(auto i : ss.extension()) { // NOLINT(altera-unroll-loops) + // std::cout << ss[i] << ' '; + // } + // std::cout << '\n'; + + // std::cout << "Singular vectors as array:\n"; + // for(auto const& row : SS) { + // for(auto const& elem : row) { // NOLINT(altera-unroll-loops) + // std::cout << elem << ' '; + // } + // std::cout << '\n'; + // } + + // // Print left singular vectors + // std::cout << "Left singular vectors:\n"; + // for(auto const& row : UU) { + // for(auto const& elem : row) { // NOLINT(altera-unroll-loops) + // std::cout << elem << ' '; + // } + // std::cout << '\n'; + // } + + // // Print right singular vectors + // std::cout << "Right singular vectors:\n"; + // { + // auto [is, js] = VT.extensions(); + // for(auto i : is) { + // for(auto j : js) { // NOLINT(altera-unroll-loops) + // std::cout << VT[i][j] << ' '; + // } + // std::cout << '\n'; + // } + // } + // // Singular values: s + // // 9.43397 4.71924 3.19716 1.88613 + // // Left singular vectors: UU + // // -0.282618 -0.218724 0.114543 0.392407 0.114648 -0.831889 + // // 0.0753653 0.370403 -0.0655296 -0.282965 0.866854 -0.146024 + // // 0.697198 0.484235 0.360692 0.216255 -0.210349 -0.241494 + // // 0.338129 -0.66227 0.546151 -0.338876 0.184255 -2.94997e-06 + // // 0.558963 -0.354894 -0.725448 0.116975 0.0640779 -0.132465 + // // 0.039885 -0.126202 0.167128 0.768545 0.391302 0.459099 + // // Right singular vectors: VT + // // -0.133831 -0.393446 0.908492 0.0439558 + // // 0.880508 0.372702 0.28873 0.0493377 + // // -0.152352 0.213988 0.0235597 0.964595 + // // 0.428467 -0.812713 -0.301202 0.255325 + + // // BOOST_TEST( std::abs(ss[0] - 9.43397 ) < 1e-4 ); + // // BOOST_TEST( std::abs(ss[3] - 1.88613 ) < 1e-4 ); + + // // BOOST_TEST( std::abs(UU[0][0] - -0.282618) < 1e-4 ); + // // BOOST_TEST( std::abs(UU[0][5] - -0.831889) < 1e-4 ); + // // BOOST_TEST( std::abs(UU[5][0] - 0.039885) < 1e-4 ); + // // BOOST_TEST( std::abs(UU[5][5] - 0.459099) < 1e-4 ); + + // // BOOST_TEST( std::abs(VT[0][0] - -0.133831) < 1e-4 ); + // // BOOST_TEST( std::abs(VT[0][3] - 0.0439558) < 1e-4 ); + // // BOOST_TEST( std::abs(VT[3][0] - 0.42846 ) < 1e-4 ); + // // BOOST_TEST( std::abs(VT[3][3] - 0.255325) < 1e-4 ); + // } + + // { + // multi::array AA = { + // {0.5, 1.0}, + // {2.0, 2.5}, + // }; + + // auto const AA_gold = AA; + + // // Output arrays + // multi::array ss(std::min(AA.size(), (~AA).size())); // Singular values + + // multi::array UU({(~AA).size(), (~AA).size()}); // Left singular vectors + // multi::array VT({AA.size(), AA.size()}); // Right singular vectors + + // multi::lapack::gesvd(AA, UU, ss, VT); // AA == VT.SS.(UU^T) + + // multi::array SS({ss.extension(), ss.extension()}, 0.0); + // std::copy(ss.begin(), ss.end(), SS.diagonal().begin()); + + // // auto const SSUUT = +multi::blas::gemm(1.0, SS, ~UU); + // auto const AA_test = +multi::blas::gemm(1.0, VT, +multi::blas::gemm(1.0, SS, ~UU)); + + // BOOST_TEST( std::abs(AA_test[0][0] - AA_gold[0][0]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[0][1] - AA_gold[0][1]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[1][0] - AA_gold[1][0]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[1][1] - AA_gold[1][1]) < 1.0e-4 ); + // } + + return boost::report_errors(); +} + +// BOOST_AUTO_TEST_CASE(lapack_geqrf){ + +// multi::array A = +// { +// {1.0, 2.0, 3.0}, +// {4.0, 5.0, 6.0}, +// {7.0, 8.0, 9.0} +// } +// ; +// // multi::lapack::context ctxt; + +// multi::array TAU(std::min(size(A), size(~A))); +// multi::array WORK(std::max(1l, 3*size(A)-1)); + +// multi::lapack::geqrf(ctxt, A, TAU, WORK); + +// print(A); +// print(TAU); + +// } + +// #if 0 +// BOOST_AUTO_TEST_CASE(lapack_syev, *boost::unit_test::tolerance(0.00001) ){ +// { +// multi::array A = { +// {167.413, 126.804, 125.114}, +// {NAN , 167.381, 126.746}, +// {NAN , NAN , 167.231} +// }; +// multi::array W(size(A)); +// multi::array WORK(std::max(1l, 3*size(A)-1)); +// multi::lapack::syev(multi::blas::filling::upper, A, W, WORK); +// BOOST_TEST( A[2][1] == -0.579092 ); +// BOOST_TEST( W[1] == 42.2081 ); +// } +// { +// multi::array A = { +// {167.413, 126.804, 125.114}, +// {NAN , 167.381, 126.746}, +// {NAN , NAN , 167.231} +// }; +// multi::array W(size(A)); +// multi::lapack::syev(multi::blas::filling::upper, A, W); +// BOOST_TEST( A[2][1] == -0.579092 ); +// BOOST_TEST( W[1] == 42.2081 ); +// } +// { +// multi::array A = { +// {167.413, 126.804, 125.114}, +// {NAN , 167.381, 126.746}, +// {NAN , NAN , 167.231} +// }; +// multi::array W(size(A)); +// multi::lapack::syev(multi::blas::filling::lower, rotated(A), W); +// BOOST_TEST( A[2][1] == -0.579092 ); +// BOOST_TEST( W[1] == 42.2081 ); +// } +// { +// namespace lapack = multi::lapack; +// multi::array A = { +// {167.413, 126.804, 125.114}, +// {NAN , 167.381, 126.746}, +// {NAN , NAN , 167.231} +// }; +// auto W = lapack::syev(multi::blas::filling::upper, A); +// BOOST_TEST( A[2][1] == -0.579092 ); +// BOOST_TEST( W[1] == 42.2081 ); +// } +// { +// multi::array const A = { +// {167.413, 126.804, 125.114}, +// {NAN , 167.381, 126.746}, +// {NAN , NAN , 167.231} +// }; +// multi::array W(size(A)); +// namespace lapack = multi::lapack; +// auto A_copy = lapack::syev(lapack::filling::upper, A, W); +// BOOST_TEST( A[1][2] == 126.746 ); +// BOOST_TEST( A_copy[2][1] == -0.579092 ); +// BOOST_TEST( W[1] == 42.2081 ); +// } +// { +// multi::array A = { +// {167.413, 126.804, 0.}, +// {NAN , 167.381, 0.}, +// {NAN , NAN , 0.} +// }; +// multi::array W(size(A)); +// namespace lapack = multi::lapack; +// auto&& A_ref = lapack::syev(lapack::filling::upper, A, W); +// BOOST_TEST( size(A_ref)==3 ); +// BOOST_TEST( W[0]==0. ); +// } +// { +// multi::array A = { +// {1. , 1., 1.}, +// {NAN, 2 , 1.}, +// {NAN, NAN, 1.} +// }; +// multi::array W(size(A)); +// namespace lapack = multi::lapack; +// auto&& A_ref = lapack::syev(lapack::filling::upper, A, W); +// print(A_ref); +// BOOST_TEST( size(A_ref)==3 ); +// BOOST_TEST( W[0]==0. ); +// } +// { +// multi::array A = {{5.}}; +// multi::array W(size(A)); +// namespace lapack = multi::lapack; +// lapack::syev(lapack::filling::upper, A, W); +// BOOST_TEST( A[0][0] == 1. ); +// BOOST_TEST( W[0]==5. ); +// } +// { +// namespace lapack = multi::lapack; +// multi::array A; +// multi::array W(size(A)); +// lapack::syev(lapack::filling::upper, A, W); +// } +// { +// multi::array const A = { +// {167.413, 126.804, 125.114}, +// {NAN , 167.381, 126.746}, +// {NAN , NAN , 167.231} +// }; +// multi::array W(size(A)); +// namespace lapack = multi::lapack; +// auto sys = lapack::syev(lapack::filling::upper, A); +// BOOST_TEST( A[1][2] == 126.746 ); +// BOOST_TEST( sys.eigenvectors[2][1] == -0.579092 ); +// BOOST_TEST( sys.eigenvalues[1] == 42.2081 ); +// } +// { +// multi::array const A = { +// {167.413, 126.804, 125.114}, +// {NAN , 167.381, 126.746}, +// {NAN , NAN , 167.231} +// }; +// multi::array W(size(A)); +// namespace lapack = multi::lapack; +// auto [eigenvecs, eigenvals] = lapack::syev(lapack::filling::upper, A); +// BOOST_TEST( A[1][2] == 126.746 ); +// BOOST_TEST( eigenvecs[2][1] == -0.579092 ); +// BOOST_TEST( eigenvals[1] == 42.2081 ); +// } + +// #endif diff --git a/include/boost/multi/adaptors/lapack/test/getrf.cpp b/include/boost/multi/adaptors/lapack/test/getrf.cpp new file mode 100644 index 000000000..8f74b8679 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/test/getrf.cpp @@ -0,0 +1,295 @@ +// Copyright 2019-2025 Alfredo A. Correa + +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +namespace multi = boost::multi; + +// BOOST_AUTO_TEST_CASE(lapack_getrf){ + +//// https://www.ibm.com/support/knowledgecenter/SSFHY8_6.2/reference/am5gr_hsgetrf.html +// multi::array A = { +// { 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6 }, +// { 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4 }, +// { 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2 }, +// { 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0 }, +// { 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8 }, +// { 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6 }, +// { 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4 }, +// { 2.4, 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2 }, +// { 2.6, 2.4, 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0 } +// }; + +// multi::array P({9}, 0.); +// lapack::context ctxt; +// auto const& LU = multi::lapack::getrf(ctxt, A, P); + +// BOOST_REQUIRE( LU.size() == A.size() ); + +// BOOST_REQUIRE_CLOSE( LU[0][0] , 2.6 , 1e-5 ); +// BOOST_REQUIRE_CLOSE( LU[0][8] , 1. , 1e-5 ); +// BOOST_REQUIRE_CLOSE( LU[8][0] , 0.923077 , 1e-5 ); +// BOOST_REQUIRE_CLOSE( LU[8][8] , 0.4 , 1e-5 ); + +//} + +// BOOST_AUTO_TEST_CASE(lapack_getrf2){ + +//// https://www.ibm.com/support/knowledgecenter/SSFHY8_6.2/reference/am5gr_hsgetrf.html +// multi::array A = { +// { 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, +// { 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0 }, +// { 4.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0 }, +// { 0.0, 5.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0 }, +// { 0.0, 0.0, 6.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0 }, +// { 0.0, 0.0, 0.0, 7.0, 1.0, 1.0, 1.0, 1.0, 1.0 }, +// { 0.0, 0.0, 0.0, 0.0, 8.0, 1.0, 1.0, 1.0, 1.0 }, +// { 0.0, 0.0, 0.0, 0.0, 0.0, 9.0, 1.0, 1.0, 1.0 }, +// { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 11.0, 12.0 } +// }; + +// multi::array P({9}, 0.); +// lapack::context ctxt; +// auto const& LU = multi::lapack::getrf(ctxt, A, P); + +// BOOST_REQUIRE( LU.size() == A.size() ); + +// for(int i = 0; i != 9; ++i){ +// for(int j = 0; j != 9; ++j){ +// std::cout<<'\t'<< LU[i][j] <<','; +// } +// std::cout< int { // NOLINT(bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(lapack_getrf) + { + multi::array const Aconst = { + { 6.80, -6.05, -0.45, 8.32, -9.67}, + {-2.11, -3.30, 2.58, 2.71, -5.14}, + { 5.66, 5.36, -2.70, 4.35, -7.26}, + { 5.97, -4.44, 0.27, -7.17, 6.08}, + { 8.23, 1.08, 9.04, 2.14, -6.87} + }; + + multi::array const Bconst = { + { 4.02, -1.56, 9.81}, + { 6.19, 4.00, -4.09}, + {-8.22, -8.67, -4.57}, + {-7.57, 1.75, -8.61}, + {-3.03, 2.86, 8.99} + }; + + multi::array PP({5}, 0.0); + + auto AA = Aconst; + auto BB = Bconst; + + auto AT = +~AA; + auto BT = +~BB; + + auto lu_solve = [](auto&& Aio, auto&& Po, auto&& Bio) { // solve A.X = B; put result in B + auto AT = +~Aio; + auto BT = +~Bio; + + auto const& LU = multi::lapack::getrf(~AT, Po); + assert(LU.size() == Po.size()); + multi::lapack::getrs(LU, std::as_const(Po), ~BT); + + Bio = ~BT; + Aio = ~AT; + }; + lu_solve(~AT, PP, ~BT); + + // using multi::blas::operators::operator*; + BOOST_TEST( std::abs((+multi::blas::gemm(1.0, Aconst, (~BT)))[1][2] - Bconst[1][2]) < 1e-10); + } + + // BOOST_AUTO_TEST_CASE(lapack_getrf_two_column) + { + multi::array const Aconst = { + { 6.80, -6.05, -0.45, 8.32, -9.67}, + {-2.11, -3.30, 2.58, 2.71, -5.14}, + { 5.66, 5.36, -2.70, 4.35, -7.26}, + { 5.97, -4.44, 0.27, -7.17, 6.08}, + { 8.23, 1.08, 9.04, 2.14, -6.87} + }; + + multi::array const Bconst = { + { 4.02, -1.56}, + { 6.19, 4.00}, + {-8.22, -8.67}, + {-7.57, 1.75}, + {-3.03, 2.86} + }; + + multi::array PP({5}, 0.0); + + auto AA = Aconst; + auto BB = Bconst; + + auto AT = +~AA; + auto BT = +~BB; + + auto lu_solve = [](auto&& Aio, auto&& Po, auto&& Bio) { // solve A.X = B; put result in B + auto AT = +~Aio; + auto BT = +~Bio; + + auto const& LU = multi::lapack::getrf(~AT, Po); + assert(LU.size() == Po.size()); + multi::lapack::getrs(LU, std::as_const(Po), ~BT); + + Bio = ~BT; + Aio = ~AT; + }; + lu_solve(~AT, PP, ~BT); + + // using multi::blas::operators::operator*; + // BOOST_REQUIRE_CLOSE( (Aconst*(~BT))[2][1] , Bconst[2][1] , 1e-10); + // BOOST_REQUIRE_CLOSE( (Aconst*(~BT))[2][0] , Bconst[2][0] , 1e-10); + } + + // BOOST_AUTO_TEST_CASE(lapack_getrf_one_column) + { + multi::array const Aconst = { + { 6.80, -6.05, -0.45, 8.32, -9.67}, + {-2.11, -3.30, 2.58, 2.71, -5.14}, + { 5.66, 5.36, -2.70, 4.35, -7.26}, + { 5.97, -4.44, 0.27, -7.17, 6.08}, + { 8.23, 1.08, 9.04, 2.14, -6.87} + }; + + multi::array const Bconst = { + {4.02}, + {6.19}, + {-8.22}, + {-7.57}, + {-3.03} + }; + + multi::array PP({5}, 0.0); + + auto AA = Aconst; + auto BB = Bconst; + + auto lu_solve = [](auto&& Aio, auto&& Po, auto&& Bio) { // solve A.X = B; put result in B + auto AT = +~Aio; + auto BT = +~Bio; + + auto const& LU = multi::lapack::getrf(~AT, Po); + assert(LU.size() == Po.size()); + multi::lapack::getrs(LU, std::as_const(Po), ~BT); + + Bio = ~BT; + Aio = ~AT; + }; + lu_solve(AA, PP, BB); + + // using multi::blas::operators::operator*; + // BOOST_REQUIRE_CLOSE( (Aconst*B)[1][0] , Bconst[1][0] , 1e-10); + // BOOST_REQUIRE_CLOSE( (Aconst*B)[1][0] , Bconst[1][0] , 1e-10); + } + + // BOOST_AUTO_TEST_CASE(lapack_getrf_one_vector) + { + multi::array const Aconst = { + { 6.80, -6.05, -0.45, 8.32, -9.67}, + {-2.11, -3.30, 2.58, 2.71, -5.14}, + { 5.66, 5.36, -2.70, 4.35, -7.26}, + { 5.97, -4.44, 0.27, -7.17, 6.08}, + { 8.23, 1.08, 9.04, 2.14, -6.87} + }; + + multi::array Vconst = {4.02, 6.19, -8.22, -7.57, -3.03}; + + multi::array PP({5}, 0.0); + + auto AA = Aconst; + auto VV = Vconst; + + auto lu_solve_one = [](auto&& Aio, auto&& Po, auto&& VV) { // solve A.X = B; put result in B + auto AT = +~Aio; + // auto BT = +~Bio; + + auto const& LU = multi::lapack::getrf(~AT, Po); + assert(LU.size() == Po.size()); + multi::lapack::getrs_one(LU, std::as_const(Po), VV); + + // Bio = ~BT; + Aio = ~AT; + }; + lu_solve_one(AA, PP, VV); + + using multi::blas::operators::operator%; + BOOST_TEST( std::abs((Aconst % VV)[2] - Vconst[2]) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(lapack_getrs) + { + // https://www.ibm.com/support/knowledgecenter/SSFHY8_6.2/reference/am5gr_hsgetrf.html + multi::array AA = { + {1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6}, + {1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4}, + {1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2}, + {1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0}, + {1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8}, + {2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6}, + {2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4}, + {2.4, 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2}, + {2.6, 2.4, 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0} + }; + + multi::array PP({9}, 0.0); + + auto BB = +~multi::array{ + {93.0, 186.0}, // 279.0, 372.0, 465.0 }, + {84.4, 168.8}, // 253.2, 337.6, 422.0 }, + {76.6, 153.2}, // 229.8, 306.4, 383.0 }, + {70.0, 140.0}, // 210.0, 280.0, 350.0 }, + {65.0, 130.0}, // 195.0, 260.0, 325.0 }, + {62.0, 124.0}, //, 186.0, 248.0, 310.0 }, + {61.4, 122.8}, // 184.2, 245.6, 307.0 }, + {63.6, 127.2}, // 190.8, 254.4, 318.0 }, + {69.0, 138.0} //, 207.0, 276.0, 345.0 } + }; + + lapack::context const ctxt; + multi::lapack::getrf(ctxt, ~AA, PP); + + multi::array dee({9}, 0.0); + for(int i = 0; i != 9; ++i) { // NOLINT(altera-unroll-loops) + dee[PP[i]] = i; + } + + for(int i = 0; i != size(AA); ++i) { + for(int j = 0; j != size(~AA); ++j) { // NOLINT(altera-unroll-loops) + std::cout << '\t' << AA[i][j] << ','; + } + std::cout << '\n'; + } + + for(int i = 0; i != size(BB); ++i) { + for(int j = 0; j != size(~BB); ++j) { // NOLINT(altera-unroll-loops) + std::cout << '\t' << BB[i][j] << ','; + } + std::cout << '\n'; + } + } +} diff --git a/include/boost/multi/adaptors/lapack/test/potrf.cpp b/include/boost/multi/adaptors/lapack/test/potrf.cpp new file mode 100644 index 000000000..2dc042695 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/test/potrf.cpp @@ -0,0 +1,362 @@ +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include // for filling, filling... +#include // for potrf + +// IWYU pragma: no_include "boost/multi/adaptors/blas/complex_traits.hpp" // for blas +#include // for gemm +#include // for herk +// IWYU pragma: no_include "boost/multi/adaptors/blas/numeric.hpp" // for underlying +#include // for H, (anonymous) + +#include // for array, subarray + +#include // for for_each, generate // IWYU pragma: keep +#include // for abs // IWYU pragma: keep +#include // for operator*, complex +// IWYU pragma: no_include // for abs +#include // for operator<<, ostream +// IWYU pragma: no_include // for size, distance // NOLINT(misc-include-cleaner) +#include // for numeric_limits +#include // for uniform_real_dis... +#include // for allocator, opera... +// IWYU pragma: no_include +// IWYU pragma: no_include // for add_const<>::type +#include // for forward + + + +namespace multi = boost::multi; +// namespace lapack = multi::lapack; +namespace blas = multi::blas; + +using complex = std::complex; + +namespace { +auto operator<<(std::ostream& os, std::complex const& cx) -> std::ostream& { + return os << real(cx) << " + I*" << imag(cx); +} + +template auto print(M const& arr) -> decltype(auto) { return print(arr, ""); } +template auto print(M const& arr, std::string const& msg) -> decltype(auto) { + using multi::size; + using std::cout; + cout << msg << "\n" + << '{'; + for(int i = 0; i != size(arr); ++i) { + cout << '{'; + for(auto j : arr[i].extension()) { // NOLINT(altera-unroll-loops) + cout << arr[i][j]; + if(j + 1 != size(arr[i])) { + cout << ", "; + } + } + cout << '}' << '\n'; + if(i + 1 != size(arr)) { + cout << ", "; + } + } + return cout << '}' << '\n'; +} + +template +auto print(M const& arr, char const* msg) -> decltype(auto) { + return print(arr, std::string{msg}); // NOLINT(fuchsia-default-arguments-calls) +} + +template +auto randomize(M&& arr) -> M&& { + std::random_device dev; + std::mt19937 eng{dev()}; + + auto gen = [&]() { + auto unif = std::uniform_real_distribution<>{-1.0, 1.0}; + return std::complex(unif(eng), unif(eng)); + }; + + std::for_each(begin(arr), end(arr), [&](auto&& row) { std::generate(begin(row), end(row), gen); }); + return std::forward(arr); +} +} // end unnamed namespace + +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ +#define BOOST_TEST_CLOSE(X, Y, ToL) BOOST_TEST(std::abs((X) - (Y)) < (ToL)) + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + /* + BOOST_AUTO_TEST_CASE(orthogonalization_over_rows, *boost::unit_test::tolerance(0.00001)){ + auto A = randomize(multi::array({3, 10})); + lapack::onrm(A); + + using blas::herk; + using blas::hermitized; + using blas::filling; + auto id = herk(filling::upper, A); + BOOST_TEST( real(id[1][1]) == 1.0 ); BOOST_TEST( imag(id[1][1]) == 0.0 ); + BOOST_TEST( real(id[1][2]) == 0.0 ); BOOST_TEST( imag(id[1][2]) == 0.0 ); + } + */ + + // BOOST_AUTO_TEST_CASE(orthogonalization_over_rows_cuda, *boost::unit_test::tolerance(0.00001)) { + // auto Acpu = randomize(multi::array({3, 10})); + + // multi::cuda::array A = Acpu; + + // using namespace blas; + // using namespace lapack; + + // trsm(filling::lower, hermitized(potrf(filling::upper, herk(filling::upper, A))), A); + + // Acpu = A; + // auto id = herk(filling::upper, Acpu); + // BOOST_TEST( real(id[1][1]) == 1.0 ); + // BOOST_TEST( imag(id[1][1]) == 0.0 ); + // BOOST_TEST( real(id[1][2]) == 0.0 ); + // BOOST_TEST( imag(id[1][2]) == 0.0 ); + // } + + /* + BOOST_AUTO_TEST_CASE(orthogonalization_over_columns, *boost::unit_test::tolerance(0.00001)){ + + auto A = randomize( multi::array({10, 3}) ); + using blas::hermitized; + lapack::onrm(hermitized(A)); + + using blas::filling; + auto id = herk(filling::upper, hermitized(A)); + BOOST_TEST( real(id[1][1]) == 1. ); BOOST_TEST( imag(id[1][1]) == 0. ); + BOOST_TEST( real(id[1][2]) == 0. ); BOOST_TEST( imag(id[1][2]) == 0. ); + }*/ + + BOOST_AUTO_TEST_CASE(numericalalgorithmsgroup_define_both_sides) { // }, *boost::unit_test::tolerance(0.0000001)) { + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) + + multi::array const A_gold = { + {3.23 + 0.00 * I, 1.51 - 1.92 * I, 1.90 + 0.84 * I, 0.42 + 2.50 * I}, + {1.51 + 1.92 * I, 3.58 + 0.00 * I, -0.23 + 1.11 * I, -1.18 + 1.37 * I}, + {1.90 - 0.84 * I, -0.23 - 1.11 * I, 4.09 + 0.00 * I, 2.33 - 0.14 * I}, + {0.42 - 2.50 * I, -1.18 - 1.37 * I, 2.33 + 0.14 * I, 4.29 + 0.00 * I}, + }; + + auto A = A_gold; // NOLINT(readability-identifier-length) lapack conventional name + + auto const As = multi::lapack::potrf(multi::lapack::filling::upper, A).size(); + BOOST_TEST( As == A.size() ); + + auto AA = A; + + for(auto i = 0; i != 4; ++i) { + for(auto j = 0; j != i; ++j) { // NOLINT(altera-unroll-loops) + AA[i][j] = 0.0; + } + } + + auto const C = +blas::herk(1.0, blas::H(AA)); // +blas::gemm(1.0, blas::H(AA), AA); // NOLINT(readability-identifier-length) conventional lapack name + + for(auto i = 0; i != 4; ++i) { + for(auto j = 0; j != 4; ++j) { // NOLINT(altera-unroll-loops) + BOOST_TEST_CLOSE(real(A_gold[i][j]), real(C[i][j]), 0.0000001); + BOOST_TEST_CLOSE(imag(A_gold[i][j]), imag(C[i][j]), 0.0000001); + } + } + } + + BOOST_AUTO_TEST_CASE(numericalalgorithmsgroup_define_upper) { + double const nan = std::numeric_limits::quiet_NaN(); + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) + + multi::array const A_gold = { + {3.23 + 0.00 * I, 1.51 - 1.92 * I, 1.90 + 0.84 * I, 0.42 + 2.50 * I}, + { nan + nan * I, 3.58 + 0.00 * I, -0.23 + 1.11 * I, -1.18 + 1.37 * I}, + { nan - nan * I, nan - nan * I, 4.09 + 0.00 * I, 2.33 - 0.14 * I}, + { nan - nan * I, nan - nan * I, nan + nan * I, 4.29 + 0.00 * I}, + }; + + auto A = A_gold; // NOLINT(readability-identifier-length) lapack conventional name + + auto const As = multi::lapack::potrf(multi::lapack::filling::upper, A).size(); + + BOOST_TEST( As == A.size() ); + + auto AA = A; + + for(auto i = 0; i != 4; ++i) { + for(auto j = 0; j != i; ++j) { // NOLINT(altera-unroll-loops) + AA[i][j] = 0.0; + } + } + + auto const C = +blas::herk(1.0, blas::H(AA)); // +blas::gemm(1.0, blas::H(AA), AA); // NOLINT(readability-identifier-length) conventional lapack name + + print(A_gold, "A gold"); // NOLINT(fuchsia-default-arguments-calls) + print(C, "recover"); // NOLINT(fuchsia-default-arguments-calls) + + for(auto i = 0; i != 4; ++i) { + // only compare upper part of the reference array (the other half is garbage) + for(auto j = i; j != 4; ++j) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + BOOST_TEST_CLOSE(real(A_gold[i][j]), real(C[i][j]), 0.0000001); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,readability-simplify-boolean-expr) bug in clang-tidy 14 + BOOST_TEST_CLOSE(imag(A_gold[i][j]), imag(C[i][j]), 0.0000001); + } + } + } + + BOOST_AUTO_TEST_CASE(numericalalgorithmsgroup_trivial_imperfect) { + double const nan = std::numeric_limits::quiet_NaN(); + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) + + multi::array const A_gold = { + {3.23 + 0.00 * I, 1.51 - 1.92 * I, 1.90 + 0.84 * I, 0.42 + 2.50 * I}, + { nan + nan * I, 3.58 + 0.00 * I, -0.23 + 1.11 * I, -1.18 + 1.37 * I}, + { nan - nan * I, nan - nan * I, -10000.00 + 0.00 * I, 0.00 - 0.00 * I}, + { nan - nan * I, nan - nan * I, nan + nan * I, -1000.00 + 0.00 * I}, + }; + + auto A = A_gold; // NOLINT(readability-identifier-length) lapack conventional name + + auto const& Adec = multi::lapack::potrf(multi::lapack::filling::upper, A); + + print(A, "A"); + print(Adec, "A dec"); + + auto AA = +Adec; + + // NOLINTNEXTLINE(altera-id-dependent-backward-branch) + for(auto i = 0; i != AA.size(); ++i) { + for(auto j = 0; j != i; ++j) { // NOLINT(altera-unroll-loops) + AA[i][j] = 0.0; + } + } + + auto const C = +blas::herk(1.0, blas::H(AA)); // +blas::gemm(1.0, blas::H(AA), AA); // NOLINT(readability-identifier-length) conventional lapack name + + print(A_gold, "A gold"); // NOLINT(fuchsia-default-arguments-calls) + print(C, "recover"); // NOLINT(fuchsia-default-arguments-calls) + + using std::get; + + for(auto i = 0; i != AA.size(); ++i) { // NOLINT(altera-id-dependent-backward-branch) + for(auto j = i; j != get<1>(C.sizes()); ++j) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + BOOST_TEST_CLOSE(real(A_gold[i][j]), real(C[i][j]), 0.0000001); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,readability-simplify-boolean-exp) bug in clang-tidy 14 + BOOST_TEST_CLOSE(imag(A_gold[i][j]), imag(C[i][j]), 0.0000001); + } + } + } + + BOOST_AUTO_TEST_CASE(numericalalgorithmsgroup_nontrivial_imperfect) { + double const nan = std::numeric_limits::quiet_NaN(); + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) + + multi::array const A_gold = { + {1.00 + 0.00 * I, 0.00 - 0.00 * I, 0.00 + 0.00 * I, 0.00 + 0.00 * I}, + { nan + nan * I, 1.00 + 0.00 * I, 0.00 + 0.00 * I, 0.00 + 0.00 * I}, + { nan - nan * I, nan - nan * I, -1.00 + 0.00 * I, 0.00 - 0.00 * I}, + { nan - nan * I, nan - nan * I, nan + nan * I, -1.00 + 0.00 * I}, + }; + + auto A = A_gold; // NOLINT(readability-identifier-length) lapack conventional name + + auto const& Adec = multi::lapack::potrf(multi::lapack::filling::upper, A); + + print(A, "A"); + print(Adec, "A dec"); + + auto AA = +Adec; // NOLINT(altera-id-dependent-backward-branch) bug in clang-tidy 14 + + // NOLINTNEXTLINE(altera-id-dependent-backward-branch) + for(auto i = 0; i != AA.size(); ++i) { + for(auto j = 0; j != i; ++j) { // NOLINT(altera-unroll-loops) + AA[i][j] = 0.0; + } + } + + auto const C = +blas::herk(1.0, blas::H(AA)); // +blas::gemm(1.0, blas::H(AA), AA); // NOLINT(readability-identifier-length) conventional lapack name + + print(A_gold, "A gold"); // NOLINT(fuchsia-default-arguments-calls) + print(C, "recover"); // NOLINT(fuchsia-default-arguments-calls) + + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + // NOLINTNEXTLINE(altera-id-dependent-backward-branch) + for(auto i = 0; i != AA.size(); ++i) { + // NOLINTNEXTLINE(altera-unroll-loops,altera-id-dependent-backward-branch) + for(auto j = i; j != get<1>(C.sizes()); ++j) { // only compare upper part of the reference array (the other half is garbage) + BOOST_TEST_CLOSE(real(A_gold[i][j]), real(C[i][j]), 0.0000001); + BOOST_TEST_CLOSE(imag(A_gold[i][j]), imag(C[i][j]), 0.0000001); + } + } + } + + BOOST_AUTO_TEST_CASE(lapack_potrf) { // , *boost::unit_test::tolerance(0.00001)) { + double const nan = std::numeric_limits::quiet_NaN(); + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) + + { + // NOLINTNEXTLINE(readability-identifier-length) + multi::array A = { + {167.413 + 0.0 * I, 126.804 - 0.00143505 * I, 125.114 - 0.1485590 * I}, + { nan + nan * I, 167.381 + 0.0 * I, 126.746 + 0.0327519 * I}, + { nan + nan * I, nan + nan * I, 167.231 + 0.0 * I}, + }; + + print(A, "original A"); + using boost::multi::lapack::filling; + using boost::multi::lapack::potrf; + + auto const As = potrf(filling::upper, A).size(); // A is hermitic in upper triangular (implicit below) + BOOST_TEST( As == A.size() ); + + BOOST_TEST_CLOSE(real(A[1][2]), 3.78646, 0.00001); + BOOST_TEST_CLOSE(imag(A[1][2]), 0.0170734, 0.00001); + // BOOST_TEST( A[2][1] != A[2][1] ); + print(A, "decomposition"); + + multi::array C(A.extensions(), complex{0.0, 0.0}); // NOLINT(readability-identifier-length) conventional lapack name + + multi::array AA = A; + + auto const [is, js] = AA.extensions(); + for(auto i : is) { + for(auto j = 0; j != i; ++j) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + AA[i][j] = std::conj(A[j][i]); + } + } + + blas::gemm(complex{1.0, 0.0}, blas::H(AA), AA, complex{0.0, 0.0}, C); + + print(C, "recovery"); + } + // { + // multi::cuda::managed::array A = { + // {167.413, 126.804 - 0.00143505 * I, 125.114 - 0.1485590 * I}, + // { NAN, 167.381, 126.746 + 0.0327519 * I}, + // { NAN, NAN, 167.231}, + // }; + // using lapack::filling; + // using lapack::potrf; + // potrf(filling::upper, A); // A is hermitic in upper triangular (implicit below) + // BOOST_TEST( real(A[1][2]) == 3.78646 ); + // BOOST_TEST( imag(A[1][2]) == 0.0170734 ); + // // BOOST_TEST( A[2][1] != A[2][1] ); + // } + // { + // multi::cuda::array A = { + // {167.413, 126.804 - 0.00143505 * I, 125.114 - 0.1485590 * I}, + // { NAN, 167.381, 126.746 + 0.0327519 * I}, + // { NAN, NAN, 167.231}, + // }; + // using lapack::filling; + // using lapack::potrf; + // potrf(filling::upper, A); // A is hermitic in upper triangular (implicit below) + // multi::array A_copy = A; + // print(A_copy); + // } + } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/lapack/test/svd.cpp b/include/boost/multi/adaptors/lapack/test/svd.cpp new file mode 100644 index 000000000..5c769fbb9 --- /dev/null +++ b/include/boost/multi/adaptors/lapack/test/svd.cpp @@ -0,0 +1,163 @@ +// Copyright 2025 Alfredo A. Correa + +#include +#include // IWYU pragma: keep +#include + +#include + +#include + +#include // IWYU pragma: keep // for std::abs +#include // for abs +#include // for tuple_element<>::type +#include // IWYU pragma: keep // for add_const<>::type +// IWYU pragma: no_include // for std::allocator + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(bugprone-exception-escape) + { + multi::array const AA = { + {0.5, 1.0}, + {2.0, 2.5}, + }; + + auto const [UU, ss, VV] = multi::lapack::gesvd(AA); // AA == UU.Diag(ss).(VV^T) + + multi::array SS({ss.size(), ss.size()}, 0.0); + SS.diagonal() = ss; + + auto const AA_test = +multi::blas::gemm(1.0, UU, +multi::blas::gemm(1.0, SS, ~VV)); // A_test <- UU * SS * VV^T + + BOOST_TEST( std::abs(AA_test[0][0] - AA[0][0]) < 1.0e-4 ); + BOOST_TEST( std::abs(AA_test[0][1] - AA[0][1]) < 1.0e-4 ); + BOOST_TEST( std::abs(AA_test[1][0] - AA[1][0]) < 1.0e-4 ); + BOOST_TEST( std::abs(AA_test[1][1] - AA[1][1]) < 1.0e-4 ); + } + // { + // multi::array AA = { + // { 2.27, 0.94, 1.07, 0.63, -2.35, 0.62}, + // {-1.54, -0.78, 1.22, 2.93, 2.30, -7.39}, + // { 1.15, -0.48, 0.79, -1.45, 1.03, 1.03}, + // {-1.94, -3.09, 0.63, 2.30, -2.57, -2.57}, + // }; + + // auto const AA_copy = AA; + + // // Output arrays + // multi::array ss(std::min(AA.size(), (~AA).size())); // Singular values + + // multi::array UU({(~AA).size(), (~AA).size()}); // Left singular vectors + // multi::array VT({AA.size(), AA.size()}); // Right singular vectors + + // boost::multi::lapack::gesvd(AA, UU, ss, VT); + + // std::cout << "Original array:\n"; + // { + // auto [is, js] = AA.extensions(); + // for(auto i : is) { + // for(auto j : js) { // NOLINT(altera-unroll-loops) + // std::cout << AA_copy[i][j] << ' '; + // } + // std::cout << '\n'; + // } + // } + + // multi::array SS({ss.extension(), ss.extension()}, 0.0); + // std::copy(ss.begin(), ss.end(), SS.diagonal().begin()); + + // // Print singular values + // std::cout << "Singular values:\n"; + // for(auto i : ss.extension()) { // NOLINT(altera-unroll-loops) + // std::cout << ss[i] << ' '; + // } + // std::cout << '\n'; + + // std::cout << "Singular vectors as array:\n"; + // for(auto const& row : SS) { + // for(auto const& elem : row) { // NOLINT(altera-unroll-loops) + // std::cout << elem << ' '; + // } + // std::cout << '\n'; + // } + + // // Print left singular vectors + // std::cout << "Left singular vectors:\n"; + // for(auto const& row : UU) { + // for(auto const& elem : row) { // NOLINT(altera-unroll-loops) + // std::cout << elem << ' '; + // } + // std::cout << '\n'; + // } + + // // Print right singular vectors + // std::cout << "Right singular vectors:\n"; + // { + // auto [is, js] = VT.extensions(); + // for(auto i : is) { + // for(auto j : js) { // NOLINT(altera-unroll-loops) + // std::cout << VT[i][j] << ' '; + // } + // std::cout << '\n'; + // } + // } + // // Singular values: s + // // 9.43397 4.71924 3.19716 1.88613 + // // Left singular vectors: UU + // // -0.282618 -0.218724 0.114543 0.392407 0.114648 -0.831889 + // // 0.0753653 0.370403 -0.0655296 -0.282965 0.866854 -0.146024 + // // 0.697198 0.484235 0.360692 0.216255 -0.210349 -0.241494 + // // 0.338129 -0.66227 0.546151 -0.338876 0.184255 -2.94997e-06 + // // 0.558963 -0.354894 -0.725448 0.116975 0.0640779 -0.132465 + // // 0.039885 -0.126202 0.167128 0.768545 0.391302 0.459099 + // // Right singular vectors: VT + // // -0.133831 -0.393446 0.908492 0.0439558 + // // 0.880508 0.372702 0.28873 0.0493377 + // // -0.152352 0.213988 0.0235597 0.964595 + // // 0.428467 -0.812713 -0.301202 0.255325 + + // // BOOST_TEST( std::abs(ss[0] - 9.43397 ) < 1e-4 ); + // // BOOST_TEST( std::abs(ss[3] - 1.88613 ) < 1e-4 ); + + // // BOOST_TEST( std::abs(UU[0][0] - -0.282618) < 1e-4 ); + // // BOOST_TEST( std::abs(UU[0][5] - -0.831889) < 1e-4 ); + // // BOOST_TEST( std::abs(UU[5][0] - 0.039885) < 1e-4 ); + // // BOOST_TEST( std::abs(UU[5][5] - 0.459099) < 1e-4 ); + + // // BOOST_TEST( std::abs(VT[0][0] - -0.133831) < 1e-4 ); + // // BOOST_TEST( std::abs(VT[0][3] - 0.0439558) < 1e-4 ); + // // BOOST_TEST( std::abs(VT[3][0] - 0.42846 ) < 1e-4 ); + // // BOOST_TEST( std::abs(VT[3][3] - 0.255325) < 1e-4 ); + // } + + // { + // multi::array AA = { + // {0.5, 1.0}, + // {2.0, 2.5}, + // }; + + // auto const AA_gold = AA; + + // // Output arrays + // multi::array ss(std::min(AA.size(), (~AA).size())); // Singular values + + // multi::array UU({(~AA).size(), (~AA).size()}); // Left singular vectors + // multi::array VT({AA.size(), AA.size()}); // Right singular vectors + + // multi::lapack::gesvd(AA, UU, ss, VT); // AA == VT.SS.(UU^T) + + // multi::array SS({ss.extension(), ss.extension()}, 0.0); + // std::copy(ss.begin(), ss.end(), SS.diagonal().begin()); + + // // auto const SSUUT = +multi::blas::gemm(1.0, SS, ~UU); + // auto const AA_test = +multi::blas::gemm(1.0, VT, +multi::blas::gemm(1.0, SS, ~UU)); + + // BOOST_TEST( std::abs(AA_test[0][0] - AA_gold[0][0]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[0][1] - AA_gold[0][1]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[1][0] - AA_gold[1][0]) < 1.0e-4 ); + // BOOST_TEST( std::abs(AA_test[1][1] - AA_gold[1][1]) < 1.0e-4 ); + // } + + return boost::report_errors(); +} diff --git a/include/multi/adaptors/lapack/test/syev.cpp b/include/boost/multi/adaptors/lapack/test/syev.cpp similarity index 94% rename from include/multi/adaptors/lapack/test/syev.cpp rename to include/boost/multi/adaptors/lapack/test/syev.cpp index c862f2a31..4f2719592 100644 --- a/include/multi/adaptors/lapack/test/syev.cpp +++ b/include/boost/multi/adaptors/lapack/test/syev.cpp @@ -1,17 +1,16 @@ -#ifdef COMPILATION_INSTRUCTIONS -`#nvcc -x cu --expt-relaxed-constexpr`$CXX -D_TEST_MULTI_ADAPTORS_LAPACK_SYEV $0 -o $0x `pkg-config --libs blas lapack` -lboost_unit_test_framework -lcudart -lcusolver&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020 +// © Alfredo A. Correa 2020-2024 #define BOOST_TEST_MODULE "C++ Unit Tests for Multi lapack adaptor syev" #define BOOST_TEST_DYN_LINK -#include +// #include #include "../../lapack/syev.hpp" #include "../../../array.hpp" -#include "../../../adaptors/cuda.hpp" -#include "../../lapack/cuda.hpp" +// #include "multi/adaptors/thrust.hpp" +// #include "../../lapack/cuda.hpp" + +namespace multi = boost::multi; BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ { @@ -26,6 +25,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( A[2][1] == -0.579092 ); BOOST_TEST( W[1] == 42.2081 ); } +#if 0 { multi::cuda::managed::array A = { {167.413, 126.804, 125.114}, @@ -50,6 +50,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( double(A[2][1]) == -0.579092 ); BOOST_TEST( double(W[1]) == 42.2081 ); } +#endif { multi::array A = { {167.413, 126.804, 125.114}, @@ -61,6 +62,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( A[2][1] == -0.579092 ); BOOST_TEST( W[1] == 42.2081 ); } +#if 0 { multi::cuda::array A = { {167.413, 126.804, 125.114}, @@ -83,6 +85,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( double(A[2][1]) == -0.579092 ); BOOST_TEST( double(W[1]) == 42.2081 ); } +#endif { multi::array A = { {167.413, 126.804, 125.114}, @@ -94,6 +97,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( A[2][1] == -0.579092 ); BOOST_TEST( W[1] == 42.2081 ); } +#if 0 { multi::cuda::array A = { {167.413, 126.804, 125.114}, @@ -116,6 +120,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( double(A[2][1]) == -0.579092 ); BOOST_TEST( double(W[1]) == 42.2081 ); } +#endif { namespace lapack = multi::lapack; multi::array A = { @@ -127,6 +132,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( A[2][1] == -0.579092 ); BOOST_TEST( W[1] == 42.2081 ); } +#if 0 { namespace lapack = multi::lapack; multi::cuda::array A = { @@ -149,6 +155,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( double(A[2][1]) == -0.579092 ); BOOST_TEST( double(W[1]) == 42.2081 ); } +#endif { namespace lapack = multi::lapack; multi::array A = { @@ -160,6 +167,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( A[2][1] == -0.579092 ); BOOST_TEST( W[1] == 42.2081 ); } +#if 0 { namespace lapack = multi::lapack; multi::cuda::array A = { @@ -182,6 +190,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( double(A[2][1]) == -0.579092 ); BOOST_TEST( double(W[1]) == 42.2081 ); } +#endif { multi::array const A = { {167.413, 126.804, 125.114}, @@ -195,6 +204,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( A_copy[2][1] == -0.579092 ); BOOST_TEST( W[1] == 42.2081 ); } +#if 0 { multi::cuda::array const A = { {167.413, 126.804, 125.114}, @@ -221,6 +231,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( double(A_copy[2][1]) == -0.579092 ); BOOST_TEST( double(W[1]) == 42.2081 ); } +#endif { multi::array A = { {167.413, 126.804, 0.}, @@ -245,6 +256,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( size(A_ref)==3 ); BOOST_TEST( W[0]==0. ); } +#if 0 { multi::cuda::array A = { {1. , 1., 1.}, @@ -259,9 +271,9 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ } { multi::cuda::managed::array A = { - {1. , 1., 1.}, - {NAN, 2 , 1.}, - {NAN, NAN, 1.} + {1.0, 1.0, 1.0}, + {NAN, 2.0, 1.0}, + {NAN, NAN, 1.0} }; multi::cuda::managed::array W(size(A)); namespace lapack = multi::lapack; @@ -269,36 +281,40 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( size(A_ref)==3 ); BOOST_TEST( double(W[0])==0. ); } +#endif { - multi::array A = {{5.}}; + multi::array A = {{5.0}}; multi::array W(size(A)); namespace lapack = multi::lapack; lapack::syev(lapack::filling::upper, A, W); BOOST_TEST( A[0][0] == 1. ); BOOST_TEST( W[0]==5. ); } +#if 0 { - multi::cuda::array A = {{5.}}; + multi::cuda::array A = {{5.0}}; multi::cuda::array W(size(A)); namespace lapack = multi::lapack; lapack::syev(lapack::filling::upper, A, W); - BOOST_TEST( A[0][0] == 1. ); - BOOST_TEST( W[0]==5. ); + BOOST_TEST( A[0][0] == 1.0 ); + BOOST_TEST( W[0]==5.0 ); } { - multi::cuda::managed::array A = {{5.}}; + multi::cuda::managed::array A = {{5.0}}; multi::cuda::managed::array W(size(A)); namespace lapack = multi::lapack; lapack::syev(lapack::filling::upper, A, W); - BOOST_TEST( A[0][0] == 1. ); - BOOST_TEST( W[0]==5. ); + BOOST_TEST( A[0][0] == 1.0 ); + BOOST_TEST( W[0]==5.0 ); } +#endif { multi::array A; multi::array W(size(A)); namespace lapack = multi::lapack; lapack::syev(lapack::filling::upper, A, W); } +#if 0 { multi::cuda::array A; multi::cuda::array W(size(A)); @@ -311,6 +327,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ namespace lapack = multi::lapack; lapack::syev(lapack::filling::upper, A, W); } +#endif { multi::array const A = { {167.413, 126.804, 125.114}, @@ -324,6 +341,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( sys.eigenvectors[2][1] == -0.579092 ); BOOST_TEST( sys.eigenvalues[1] == 42.2081 ); } +#if 0 { multi::cuda::array const A = { {167.413, 126.804, 125.114}, @@ -350,7 +368,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( double(sys.eigenvectors[2][1]) == -0.579092 ); BOOST_TEST( double(sys.eigenvalues[1]) == 42.2081 ); } -#if __cpp_structured_bindings +#endif { multi::array const A = { {167.413, 126.804, 125.114}, @@ -364,6 +382,7 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ BOOST_TEST( eigenvecs[2][1] == -0.579092 ); BOOST_TEST( eigenvals[1] == 42.2081 ); } +#if 0 { multi::cuda::array const A = { {167.413, 126.804, 125.114}, @@ -392,5 +411,3 @@ BOOST_AUTO_TEST_CASE(multi_lapack_syev, *boost::unit_test::tolerance(0.00001) ){ } #endif } - - diff --git a/include/multi/adaptors/lapack/triangular.hpp b/include/boost/multi/adaptors/lapack/triangular.hpp similarity index 78% rename from include/multi/adaptors/lapack/triangular.hpp rename to include/boost/multi/adaptors/lapack/triangular.hpp index b248f117e..050d432d6 100644 --- a/include/multi/adaptors/lapack/triangular.hpp +++ b/include/boost/multi/adaptors/lapack/triangular.hpp @@ -1,22 +1,28 @@ -#ifdef COMPILATION_INSTRUCTIONS -(echo "#include\""$0"\"" > $0x.cpp) && clang++ `#-DNDEBUG` -O3 -std=c++14 -Wall -Wextra -Wpedantic -D_TEST_MULTI_ADAPTORS_LAPACK_TRIANGULAR -DADD_ $0x.cpp -o $0x.x -lblas && time $0x.x $@ && rm -f $0x.x $0x.cpp; exit -#endif +// Copyright 2019-2024 Alfredo A. Correa + #ifndef MULTI_ADAPTORS_LAPACK_TRIANGULAR_HPP #define MULTI_ADAPTORS_LAPACK_TRIANGULAR_HPP -// Alfredo A. Correa 2019 © #include "../../../multi/array.hpp" +namespace boost::multi::lapack { + enum class filling : char { + lower = 'U', + upper = 'L', + } +} + namespace boost{ namespace multi{ namespace lapack{ template struct uhermitian : public multi::array{ -// using multi::array::array; +// using multi::array::array; template< class MultiArray, - typename = decltype(multi::array{std::forward(std::declval())}) + typename = decltype(multi::array{std::forward(std::declval())}), + std::enable_if_t, int> =0 > explicit uhermitian(MultiArray&& ma) : multi::array{std::forward(ma)}{} template decltype(auto) operator[](Index i) const{ diff --git a/include/boost/multi/adaptors/mpi.hpp b/include/boost/multi/adaptors/mpi.hpp new file mode 100644 index 000000000..8bff3d5c6 --- /dev/null +++ b/include/boost/multi/adaptors/mpi.hpp @@ -0,0 +1,369 @@ +// Copyright 2024-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 10. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_MPI_HPP_ +#define BOOST_MULTI_ADAPTORS_MPI_HPP_ +#include + +#include + +#define OMPI_SKIP_MPICXX 1 +#include + +#include // for assert +#include +#include // for numeric_limits NOLINT(misc-include-cleaner) +#include // for exchange, move + +namespace boost::multi::mpi { + +using const_MPI_Datatype = MPI_Datatype const; + +template static inline const_MPI_Datatype const datatype = std::conditional_t(sizeof(T*)), void, int>{}; + +// template<> MPI_Datatype const datatype = MPI_CHAR; +// template<> MPI_Datatype const datatype = MPI_UNSIGNED_CHAR; + +// #if(__cplusplus >= 201703L) +// MPI3_DECLARE_DATATYPE(byte , MPI_BYTE); +// #endif +// MPI3_DECLARE_DATATYPE(wchar , MPI_WCHAR); + +// MPI3_DECLARE_DATATYPE(short , MPI_SHORT); +// MPI3_DECLARE_DATATYPE(unsigned short , MPI_UNSIGNED_SHORT); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-variable-declarations" +#endif + +template<> inline const_MPI_Datatype const datatype = MPI_INT; // NOLINT(misc-misplaced-const) + +// MPI3_DECLARE_DATATYPE(unsigned int , MPI_UNSIGNED); +// MPI3_DECLARE_DATATYPE(long , MPI_LONG); +// MPI3_DECLARE_DATATYPE(unsigned long , MPI_UNSIGNED_LONG); +// MPI3_DECLARE_DATATYPE(float , MPI_FLOAT); + +template<> inline const_MPI_Datatype const datatype = MPI_FLOAT; + +template<> inline const_MPI_Datatype const datatype = MPI_DOUBLE; + +template<> inline const_MPI_Datatype const datatype> = MPI_DOUBLE_COMPLEX; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// MPI3_DECLARE_DATATYPE(long double , MPI_LONG_DOUBLE); +// MPI3_DECLARE_DATATYPE(long long int , MPI_LONG_LONG_INT); + +// MPI3_DECLARE_DATATYPE(bool , MPI_C_BOOL); // C++ binding not used MPI_CXX_BOOL); + +template +struct datatype_t { + static const_MPI_Datatype const value; // = datatype; + auto operator()() const -> decltype(datatype) { return datatype; } + operator const_MPI_Datatype() const { return datatype; } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions,readability-const-return-type) +}; + +template +const_MPI_Datatype const datatype_t::value = datatype; + +// class data { +// void* buf_; +// MPI_Datatype datatype_; + +// public: +// data(void* buf, MPI_Datatype datatype) : buf_{buf}, datatype_{datatype} {} +// template +// explicit data(It first) // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init) +// : buf_{const_cast(static_cast(first.base()))} // NOLINT(cppcoreguidelines-pro-type-const-cast) +// { +// assert( first.stride() <= std::numeric_limits::max() ); +// MPI_Type_vector( +// 1, 1, +// static_cast(first.stride()), +// mpi::datatype, +// &datatype_ +// ); + +// MPI_Type_commit(&datatype_); // type cannot be used until committed, in communication operations at least +// } + +// data(data const&) = delete; +// data(data&&) = delete; + +// auto operator=(data const&) = delete; +// auto operator=(data&&) = delete; + +// ~data() { MPI_Type_free(&datatype_); } + +// auto buffer() const { return buf_; } +// auto datatype() const { return datatype_; } +// }; + +template +auto create_subarray_aux( + Layout lyt, + int subcount, + MPI_Datatype old_datatype, + MPI_Datatype* new_datatype +) -> int { + MPI_Datatype sub_type; // NOLINT(cppcoreguidelines-init-variables) + + if constexpr(Layout::dimensionality == 1) { + MPI_Type_dup(old_datatype, &sub_type); + } else { + create_subarray_aux(lyt.sub(), lyt.sub().size(), old_datatype, &sub_type); + } + + int dt_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Type_size(old_datatype, &dt_size); + + assert(lyt.stride() * dt_size <= std::numeric_limits::max()); + { + MPI_Datatype vector_datatype; // NOLINT(cppcoreguidelines-init-variables) + MPI_Type_create_hvector( + subcount, 1, + lyt.stride() * dt_size, + sub_type, &vector_datatype + ); + + MPI_Type_create_resized(vector_datatype, 0, lyt.stride() * dt_size, new_datatype); + MPI_Type_free(&vector_datatype); + } + MPI_Type_free(&sub_type); + return MPI_SUCCESS; +} + +template class DatatypeT = mpi::datatype_t, class Size = int> +class skeleton { + MPI_Datatype datatype_ = MPI_DATATYPE_NULL; + Size count_ = 0; + + skeleton() = default; + + auto operator=(skeleton&& other) & noexcept -> skeleton& { + count_ = other.count_; + datatype_ = std::exchange(other.datatype_, MPI_DATATYPE_NULL); + return *this; + } + + public: + template + skeleton(Stride stride, SubLayout const& sublyt, MPI_Datatype dt, Size subcount) : count_{1} { + MPI_Datatype sub_type; // NOLINT(cppcoreguidelines-init-variables) + [[maybe_unused]] skeleton sk; // NOLINT(misc-const-correctness) + if constexpr(SubLayout::dimensionality == 0) { + sub_type = dt; + } else { + assert( sublyt.size() <= std::numeric_limits::max() ); + sk = skeleton(sublyt, dt, static_cast(sublyt.size())); + sub_type = sk.datatype(); + } + + int dt_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Type_size(dt, &dt_size); + + { + MPI_Datatype vector_datatype; // NOLINT(cppcoreguidelines-init-variables) + MPI_Type_create_hvector( + subcount, 1, + stride * dt_size, + sub_type, &vector_datatype + ); + + MPI_Type_create_resized(vector_datatype, 0, stride * dt_size, &datatype_); + MPI_Type_free(&vector_datatype); + } + } + + template + skeleton(Layout const& lyt, MPI_Datatype dt, Size subcount) : count_{static_cast(lyt.size())} { + assert(lyt.size() <= std::numeric_limits::max()); + + MPI_Datatype sub_type; // NOLINT(cppcoreguidelines-init-variables) + [[maybe_unused]] skeleton sk; // NOLINT(misc-const-correctness) + if constexpr(Layout::dimensionality == 1) { + sub_type = dt; + } else { + assert( lyt.sub().size() <= std::numeric_limits::max() ); + sk = skeleton(lyt.sub(), dt, static_cast(lyt.sub().size())); + sub_type = sk.datatype(); + } + + int dt_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Type_size(dt, &dt_size); + + { + MPI_Datatype vector_datatype; // NOLINT(cppcoreguidelines-init-variables) + MPI_Type_create_hvector( + subcount, 1, + lyt.stride() * dt_size, + sub_type, &vector_datatype + ); + + MPI_Type_create_resized(vector_datatype, 0, lyt.stride() * dt_size, &datatype_); + MPI_Type_free(&vector_datatype); + } + } + + skeleton(skeleton&& other) noexcept + : count_{other.count_}, datatype_{std::exchange(other.datatype_, MPI_DATATYPE_NULL)} {} + + template + skeleton(Stride stride, SubLayout const& sublyt, MPI_Datatype dt) + : skeleton{stride, sublyt, dt, 1} { + MPI_Type_commit(&datatype_); + } + + template + skeleton(Layout const& lyt, MPI_Datatype dt) + : skeleton{lyt, dt, 1} { + MPI_Type_commit(&datatype_); + } + + template + explicit skeleton(Layout const& lyt) : skeleton{lyt, DatatypeT{}()} {} + + skeleton(skeleton const&) = delete; + + auto operator=(skeleton const&) = delete; + + ~skeleton() { + if(datatype_ != MPI_DATATYPE_NULL) { + MPI_Type_free(&datatype_); + } + } + + auto count() const { return count_; } + auto datatype() const& { return datatype_; } + auto datatype() && { return std::exchange(datatype_, MPI_DATATYPE_NULL); } +}; + +#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides>=201703L) +template skeleton(Layout, decltype(MPI_INT)) -> skeleton; +#endif + +template +auto create_subarray(Layout const& lyt, MPI_Datatype old_datatype, MPI_Datatype* new_datatype) -> int { + int old_datatype_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Type_size(old_datatype, &old_datatype_size); + + // return create_subarray_aux(lyt, 1, old_datatype, new_datatype); + skeleton const sk(lyt, old_datatype); + // new_datatype = std::move(sk).type(); + { + MPI_Datatype vector_datatype; // NOLINT(cppcoreguidelines-init-variables) + assert( lyt.size() <= std::numeric_limits::max() ); + + MPI_Type_create_hvector( + static_cast(lyt.size()), 1, + lyt.stride() * old_datatype_size, + sk.datatype(), &vector_datatype + ); + + MPI_Type_create_resized(vector_datatype, 0, lyt.stride() * old_datatype_size, new_datatype); + MPI_Type_free(&vector_datatype); + } + return MPI_SUCCESS; +} + +template class DatatypeT = mpi::datatype_t, typename Size = int> +class message : skeleton { + void* buf_; + + using skeleton_type = skeleton; + + public: + message(void* buf, skeleton_type&& sk) : skeleton_type{std::move(sk)}, buf_{buf} {} + + template + message(void* buf, Layout const& lyt, MPI_Datatype dt) : skeleton_type(lyt, dt), buf_{buf} {} + + template + explicit message(ArrayElements const& arrelems) + : message{ + const_cast(static_cast(arrelems.base())), // NOLINT(cppcoreguidelines-pro-type-const-cast) + arrelems.layout(), + DatatypeT{} // value_type> + } {} + + message(message const& other) = delete; + message(message&&) = delete; + + auto operator=(message const&) = delete; + auto operator=(message&&) = delete; + + ~message() = default; + + auto buffer() const { return buf_; } + using skeleton_type::count; + // auto count() const { return this->count_; } + using skeleton_type::datatype; + // auto datatype() const { return this->datatype_; } + + // template + // std::tuple_element_t> const& get() const& { + // if constexpr(Index == 0) + // return buf_; + // if constexpr(Index == 1) + // return this->count_; + // if constexpr(Index == 2) + // return this->datatype_; + // } +}; + +template class DatatypeT = mpi::datatype_t, typename Size = int> +class iterator : skeleton { + void* buf_; + using skeleton_type = skeleton; + + public: + iterator(void* buf, skeleton_type&& sk) : skeleton_type{std::move(sk)}, buf_{buf} {} + + template + iterator(void* buf, Stride stride, SubLayout const& sublyt, MPI_Datatype dt) : skeleton_type{stride, sublyt, dt}, buf_{buf} {} + + template =0> // NOLINT(modernize-use-constraints) for C++20 + explicit iterator(ArrayIterator const& it) + : iterator{ + const_cast(static_cast(it.base())), // NOLINT(cppcoreguidelines-pro-type-const-cast) + it.stride(), + it->layout(), + DatatypeT{} + } {} + + template =0> // NOLINT(modernize-use-constraints) for C++20 + explicit iterator(ArrayIterator const& it) + : iterator{ + const_cast(static_cast(it.base())), // NOLINT(cppcoreguidelines-pro-type-const-cast) + it.stride(), + multi::layout_t<0>{}, + DatatypeT{} + } {} + + iterator(iterator const& other) = delete; + iterator(iterator&&) = delete; + + auto operator=(iterator const&) = delete; + auto operator=(iterator&&) = delete; + + ~iterator() = default; + + auto buffer() const { return buf_; } + // auto count() const { return 1; } // an iterator doesn't have a count in most contexts, you have to figure out the count yourselve, typically size() + using skeleton_type::count; + using skeleton_type::datatype; +}; + +template class DatatypeT = mpi::datatype_t, class Array> +auto begin(Array&& arr) {return iterator{std::forward(arr).begin()}; } + +#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides>=201703L) +template message(ArrayElements const&) -> message<>; +#endif + +} // namespace boost::multi::mpi + +#endif diff --git a/include/boost/multi/adaptors/mpi/CMakeLists.txt b/include/boost/multi/adaptors/mpi/CMakeLists.txt new file mode 100644 index 000000000..03935ca65 --- /dev/null +++ b/include/boost/multi/adaptors/mpi/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.10) +# project(boost-multi-mpi VERSION 0.1) + +find_package(MPI REQUIRED) + +add_subdirectory(../mpi/test) diff --git a/include/boost/multi/adaptors/mpi/README.md b/include/boost/multi/adaptors/mpi/README.md new file mode 100644 index 000000000..cb5a6dd74 --- /dev/null +++ b/include/boost/multi/adaptors/mpi/README.md @@ -0,0 +1,253 @@ + +# Multi MPI Adaptor + +_© Alfredo A. Correa, 2025_ + +(documentation in progress) + +MPI libraries are a standard for parallel computing in C and C++. +The Multi MPI Adaptor provides ways to interface arrays with MPI library implementations. +This feature helps use arrays in MPI-based programs by streamlining the communication of array contents between different MPI processes. + +The functions in the adaptor do not access the array data, which ensures that it is compatible with GPU-aware implementations of MPI. + +## Contents +[[_TOC_]] + +## Interfaces + +The message-passing interface of MPI generally works with messages to communicate data. +An MPI message is described in 3 parts: a buffer, a count, and a datatype. + +For example, the function to send data from one process to another (destination) via a communicator is: + +```cpp +int MPI_Send(const void* buffer, int count, MPI_Datatype datatype, int destination, int tag, MPI_Comm communicator); +``` + +MPI library implementations provide dozens of functions like this to send, receive, and process data between MPI processes. +This adaptor doesn't try to replace these functions; +instead, it works by providing a function that generates messages, explicitly calculating the datatypes to describe the arrays. + +The MPI user-defined datatypes are generated from the Multi arrays, including their type information and layout information (strides). + +The basic usage consists of creating a `multi::mpi::message` object from the array elements. +A message is created by passing a reference to the elements of an array, `multi::mpi::message(my_array.elements())`. +(Elements are not copied in the process.) + +The message can then be later decomposed into a buffer, a count, and a datatype for use in the MPI functions. + +In this example, which runs in 2 processes, creates an array that is communicated from process 0 to process 1: + +```cpp +// compile with `mpic++ -std=c++17 example.cpp -o example.x` +// run with `mpirun -n 2 example.x` +#include + +int main() { + int rank; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + auto const A = multi::array({ + {1, 2, 3}, + {4, 5, 6} + }); + + if(world_rank == 0) { + auto const& A_msg = multi::mpi::message(A.elements()); + MPI_Send(A_msg.buffer(), A_msg.count(), A_msg.datatype(), 1, 0, MPI_COMM_WORLD); + } else if(world_rank == 1) { + multi::array B({2, 3}); + + auto&& B_msg = multi::mpi::message(B.elements()); + MPI_Recv(B_msg.buffer(), B_msg.count(), B_msg.datatype(), 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + assert(B == A); + } +} +``` + +The examples in this documentation are two-dimensional for simplicity and illustration purposes. +The usage is general to an arbitrary number of dimensions. + +## Advanced usage + +### Subarrays + +A subarray can also be communicated, for example, a small 2x2 block of the original array. +Replacing `message(AA.elements())` with `message(A({0, 2}, {0, 2}).elements())` and `message(B.elements())` with `message(B({0, 2}, {0, 2}).elements())` will result in a communication of a subset of elements. + +```cpp + ... + if(world_rank == 0) { + auto const& msg = multi::mpi::message(A({0, 2}, {0, 2}).elements()); + MPI_Send(msg.buffer(), msg.count(), msg.datatype(), 1, 0, MPI_COMM_WORLD); + } else if(world_rank == 1) { + multi::array B({2, 3}); + + auto&& msg = multi::mpi::message(B({0, 2}, {0, 2}).elements()); + MPI_Recv(msg.buffer(), msg.count(), msg.datatype(), 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + assert(B({0, 2}, {0, 2}) == A({0, 2}, {0, 2})); // only the 2x2 block is communicated + } +``` + +### Rearrangement of elements + +It is essential to understand that, due to the way MPI works, the array's value is not what is communicated but only its fundamental elements (in a canonical order). +We emphasize this detail by passing the `.elements()` range for message construction, not the array per se. + +A consequence of this is that the user has to ensure consistency in the shape of the receiving end, as in the previous example. +Communicating a 2x3 array and receiving a 2x2 array will be an error because they have different numbers of elements. + +Similarly, a 2x3 array can be communicated into a 3x2 array, although the elements will be rearranged, which is typically not desired. + +Still, a reasonable use of rearrangement of elements could involve transposition of the array during communication. +The key to rearranging the elements is that the layouts can be different in different processes. + +```cpp +int main() { + int rank; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + auto const A = multi::array({ + {1, 2, 3}, + {4, 5, 6} + }); + + if(world_rank == 0) { + auto const& A_msg = multi::mpi::message(A.elements()); + MPI_Send(A_msg.buffer(), A_msg.count(), A_msg.datatype(), 1, 0, MPI_COMM_WORLD); + } else if(world_rank == 1) { + multi::array B({3, 2}); + + auto&& BT_msg = multi::mpi::message(B.tranposed().elements()); + MPI_Recv(BT_msg.buffer(), BT_msg.count(), BT_msg.datatype(), 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + assert(B == multi::array{ + {1, 4}, + {2, 5}, + {3, 6} + }); + } +} +``` + +In this example, the layouts of `A` and the transpose of `B` are different since the elements are arranged differently in memory. +However, they will be equal because the logical element arrangement is the same. +The result is that `B` is the transposed version of `A`. + +### Reduction and other operations + +Since the message represents a set of elements, reductions and other MPI computations can be used directly element-wise. + +```cpp +int main() { + int rank; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + auto const A = multi::array({ + {1, 2, 3}, + {4, 5, 6} + }); + + auto const& A_msg = multi::mpi::message(A.elements()); + MPI_Reduce(MPI_SUM, A_msg.buffer(), A_msg.count(), A_msg.datatype(), MPI_COMM_WORLD); +} +``` + +### Iteration and skeletons + +If subelements of an array need to be communicated repeatedly, it is wasteful to produce a new message each time. +The key is that all subarrays of a larger array have the same layout. + +Suppose we want to communicate the rows of an array in random order. + +```cpp +int main() { + int rank; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + multi::array A({ + {1, 2, 3, 4}, + {5, 6, 7, 8}, + {9, 10, 11, 12} + }); + + if(rank == 0) { + std::vector perm(A.size()); std::iota(perm.begin(), perm.end(), 0); + for(auto&& row_index: perm) { + auto const& msg = multi::mpi::message(A[row_index].elements()); + MPI_Send(msg.buffer(), msg.count(), msg.datatype(), 1, 0, MPI_COMM_WORLD); + } + } else if(rank == 1) { + multi::array B({3, 4}); + for(auto&& row: B) { + auto&& msg = multi::mpi::message(row.elements()); + MPI_Recv(msg.buffer(), msg.count(), msg.datatype(), 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } +} +``` + +The MPI datatype computation is unfortunately repeated for each message inside both loops. +The alternative is to use MPI skeletons, which only contain datatype information. +The loops can be replaced with this code: + +```cpp + ... + auto sk = multi::mpi::skeleton(A.front().layout()); + for(auto&& row_index: perm) { + MPI_Send(A[row_index].base(), sk.count(), sk.datatype(), 1, 0, MPI_COMM_WORLD); + } + ... +``` + +## GPU-aware MPI + +Messages and skeletons can be generated for arrays on the GPU. +If the MPI implementation is GPU-aware (e.g., Spectrum MPI), it can communicate the GPU array elements with the same interface. + +In the examples above, the arrays can be replaced with `multi::array >`, and the communication will automatically use GPU hardware. + +## Serialization + +The advantage of using datatypes and messages is that data doesn't need to be copied explicitly into a local buffer. +However, in certain cases it is possible that communication using datatypes is slower than compacting the array data into a contiguous buffer. + +A completely different alternative to the use of message and datatypes is to use serialization. + +```cpp +#include + +int main() { + auto const A = multi::array({ + {1, 2, 3}, + {4, 5, 6} + }); + + int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if(rank == 0) { + std::ostringstream oss; + boost::archive:::binary_oarchive oa(oss); + oa << A(); + + MPI_Send(A_msg.str().data(), A_msg.str().data(), MPI_CHAR, 1, 0, MPI_COMM_WORLD); + } else if(rank == 1) { + auto B = multi::array({2, 3}); + + MPI_Status status; MPI_Probe(0, 0, MPI_COMM_WORLD, &status); + int count; MPI_Get_count(&status, MPI_CHAR, &count); + + std::string buffer(count); + MPI_Recv(buffer.data(), bufer.size(), MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + std::istringstream iss(std::move(buffer)); + boost::archive::binary_iarchive ia(iss); + ia >> B(); + + assert(B == A); + } +} +``` diff --git a/include/boost/multi/adaptors/mpi/test/CMakeLists.txt b/include/boost/multi/adaptors/mpi/test/CMakeLists.txt new file mode 100644 index 000000000..3e6a027b5 --- /dev/null +++ b/include/boost/multi/adaptors/mpi/test/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.15) + +if(ENABLE_CUDA) + enable_language(CUDA) +endif() + +enable_testing() +include(CTest) + +find_package(Boost REQUIRED COMPONENTS NO_MODULE) + +add_executable(mpi.cpp.x mpi.cpp) + +if(ENABLE_CUDA) + set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) +endif() + +if(APPLE) + # https://apple.stackexchange.com/a/121010 + # add_custom_command (TARGET mpi.cpp.x POST_BUILD COMMAND codesign --force --deep -s ACTMPI ${CMAKE_CURRENT_BINARY_DIR}/mpi.cpp.x) +endif() + +target_link_libraries(mpi.cpp.x PRIVATE multi) +target_link_libraries(mpi.cpp.x PRIVATE MPI::MPI_CXX Boost::boost) + +if(APPLE) + add_test(NAME mpi.cpp.x COMMAND ${MPIEXEC_EXECUTABLE} --mca btl ^tcp ${MPIEXEC_NUMPROC_FLAG} 4 ${MPIEXEC_PREFLAGS} $ ${MPIEXEC_POSTFLAGS}) +else() + add_test(NAME mpi.cpp.x COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 4 ${MPIEXEC_PREFLAGS} $ ${MPIEXEC_POSTFLAGS}) +endif() \ No newline at end of file diff --git a/include/boost/multi/adaptors/mpi/test/mpi.cpp b/include/boost/multi/adaptors/mpi/test/mpi.cpp new file mode 100644 index 000000000..2cfe006b8 --- /dev/null +++ b/include/boost/multi/adaptors/mpi/test/mpi.cpp @@ -0,0 +1,744 @@ +// Copyright 2024-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 10. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +// #define OMPI_SKIP_MPICXX 1 +#include + +#include + +#include // for std::cout +#include +#include + +namespace multi = boost::multi; + +namespace { +void test_single_number(MPI_Comm comm) { + int world_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(comm, &world_rank); + int world_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(comm, &world_size); + + BOOST_TEST(world_size > 1); + + int number = 0; + if(world_rank == 0) { + number = -1; + MPI_Send(&number, 1, MPI_INT, 1, 0, comm); + } else if(world_rank == 1) { + MPI_Recv(&number, 1, MPI_INT, 0, 0, comm, MPI_STATUS_IGNORE); + BOOST_TEST(number == -1); + } + { + std::vector vv(3, 99); // NOLINT(fuchsia-default-arguments-calls) + if(world_rank == 0) { + vv = {1, 2, 3}; + MPI_Send(vv.data(), static_cast(vv.size()), MPI_INT, 1, 0, comm); + } else if(world_rank == 1) { + MPI_Recv(vv.data(), static_cast(vv.size()), MPI_INT, 0, 0, comm, MPI_STATUS_IGNORE); + BOOST_TEST( vv == std::vector({1, 2, 3}) ); // NOLINT(fuchsia-default-arguments-calls) + } + } +} + +void test_1d(MPI_Comm comm) { // NOLINT(readability-function-cognitive-complexity) + int world_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(comm, &world_rank); + int world_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(comm, &world_size); + { + if(world_rank == 0) { + multi::array const AA = multi::array({1, 2, 3, 4, 5, 6}); + auto const&& BB = AA.strided(2); + BOOST_TEST(( BB == multi::array({1, 3, 5}) )); + + static_assert(decltype(BB.begin())::rank_v == 1); + + auto const B_it = multi::mpi::begin(BB); + + MPI_Send(B_it.buffer(), static_cast(BB.size()), B_it.datatype(), 1, 0, comm); + } else if(world_rank == 1) { + multi::array CC(3, 99); // NOLINT(misc-const-correctness) + + auto const& C_msg = multi::mpi::message(CC.elements()); + + MPI_Recv(C_msg.buffer(), C_msg.count(), C_msg.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + + std::cout << "ddddd " << CC[0] << ' ' << CC[1] << ' ' << CC[2] << '\n'; + BOOST_TEST(( CC == multi::array({1, 3, 5}) )); + } + } + std::cout << world_rank << '\n'; + { + if(world_rank == 0) { + auto const AA = multi::array({1, 2, 3, 4, 5, 6}); + auto const& BB = AA.strided(2); + BOOST_TEST(( BB == multi::array({1, 3, 5}) )); + + auto const B_data = multi::mpi::begin(BB); + + MPI_Send(B_data.buffer(), static_cast(BB.size()), B_data.datatype(), 1, 0, comm); + } else if(world_rank == 1) { + multi::array CC(3, 99); // NOLINT(misc-const-correctness) + + auto const C_msg = multi::mpi::message(CC); + + MPI_Recv(C_msg.buffer(), C_msg.count(), C_msg.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + BOOST_TEST(( CC == multi::array({1, 3, 5}) )); + } + } + { + if(world_rank == 0) { + auto const AA = multi::array( + { + {1, 88}, + {2, 88}, + {3, 88}, + {4, 88}, + {5, 88}, + {6, 88} + } + ); + auto const& BB = AA.strided(2); + BOOST_TEST(( BB == multi::array({ + {1, 88}, + {3, 88}, + {5, 88} + }) )); + + // auto const& B_msg = multi::mpi::message(BB); + // MPI_Send(B_msg.buffer(), B_msg.count(), B_msg.datatype(), 1, 0, comm); + + auto const& B_msg = multi::mpi::message(BB); + // MPI_Send(B_msg.buffer(), 3, B_msg.datatype(), 1, 0, comm); + + // auto const& B_it = multi::mpi::iterator(BB.begin()); + auto const B_it = multi::mpi::begin(BB); + // BOOST_TEST( B_it.buffer() == B_msg.buffer()); + // BOOST_TEST( B_it.datatype() == B_msg.datatype() ); + MPI_Send(B_it.buffer(), static_cast(BB.size()), B_it.datatype(), 1, 0, comm); + } else if(world_rank == 1) { + multi::array CC({3, 2}, 99); // NOLINT(misc-const-correctness) + auto const& C_msg = multi::mpi::message(CC); + + MPI_Recv(C_msg.buffer(), C_msg.count(), C_msg.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + BOOST_TEST(( CC == multi::array({ + {1, 88}, + {3, 88}, + {5, 88} + }) )); + } + } + + std::cout << world_rank << '\n'; + { + if(world_rank == 0) { + auto const AA = multi::array({1, 2, 3, 4, 5, 6}); + auto const& BB = AA.strided(2); + BOOST_TEST(( BB == multi::array({1, 3, 5}) )); + + MPI_Datatype B_type; // NOLINT(cppcoreguidelines-init-variables) + multi::mpi::create_subarray(BB.layout(), MPI_INT, &B_type); + MPI_Type_commit(&B_type); + MPI_Send(BB.base(), 1, B_type, 1, 0, comm); + MPI_Type_free(&B_type); + + } else if(world_rank == 1) { + multi::array CC(3, 99); // NOLINT(misc-const-correctness) + + auto const C_msg = multi::mpi::message{CC.base(), CC.layout(), MPI_INT}; + + MPI_Recv(C_msg.buffer(), C_msg.count(), C_msg.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + BOOST_TEST(( CC == multi::array({1, 3, 5}) )); + } + } + { + if(world_rank == 0) { + auto const AA = multi::array({1, 2, 3, 4, 5, 6}); + auto const& BB = AA.strided(2); + BOOST_TEST(( BB == multi::array({1, 3, 5}) )); + + MPI_Datatype B_type; // NOLINT(cppcoreguidelines-init-variables) + multi::mpi::create_subarray(BB.layout(), MPI_INT, &B_type); + MPI_Type_commit(&B_type); + MPI_Send(BB.base(), 1, B_type, 1, 0, comm); + MPI_Type_free(&B_type); + + } else if(world_rank == 1) { + multi::array CC(3, 99); // NOLINT(misc-const-correctness) + + auto const C_msg = multi::mpi::message{CC.base(), CC.layout(), MPI_INT}; + + MPI_Recv(C_msg.buffer(), C_msg.count(), C_msg.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + BOOST_TEST(( CC == multi::array({1, 3, 5}) )); + } + } +} + +void test_2d(MPI_Comm comm) { + int world_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(comm, &world_rank); + int world_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(comm, &world_size); + + { + if(world_rank == 0) { + auto const AA = multi::array({ + {1, 2, 3}, + {4, 5, 6} + }); + + auto const& BB = AA({0, 2}, {1, 3}); + BOOST_TEST(( BB == multi::array({{2, 3}, {5, 6}}) )); + + auto const B_msg = multi::mpi::message(BB.elements()); + + MPI_Send(B_msg.buffer(), B_msg.count(), B_msg.datatype(), 1, 0, comm); + } else if(world_rank == 1) { + multi::array CC({2, 2}, 99); + + auto const C_sk = multi::mpi::skeleton(CC.elements().layout(), MPI_INT); + + MPI_Recv(CC.base(), C_sk.count(), C_sk.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + std::cout << CC[0][0] << ' ' << CC[0][1] << '\n' + << CC[1][0] << ' ' << CC[1][1] << '\n'; + + BOOST_TEST(( CC == multi::array({{2, 3}, {5, 6}}) )); + } + } + { + if(world_rank == 0) { + auto const AA = multi::array({ + {1, 2, 3}, + {4, 5, 6} + }); + + auto const& BB = AA({0, 2}, {1, 3}); + BOOST_TEST(( BB == multi::array({{2, 3}, {5, 6}}) )); + + auto const B_msg = multi::mpi::message(BB.elements()); + + MPI_Send(B_msg.buffer(), B_msg.count(), B_msg.datatype(), 1, 0, comm); + } else if(world_rank == 1) { + multi::array CC({2, 2}, 99); + + auto const C_sk = multi::mpi::skeleton(CC.elements().layout(), MPI_INT); + + MPI_Recv(CC.base(), C_sk.count(), C_sk.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + std::cout << CC[0][0] << ' ' << CC[0][1] << '\n' + << CC[1][0] << ' ' << CC[1][1] << '\n'; + + BOOST_TEST(( CC == multi::array({{2, 3}, {5, 6}}) )); + } + } + { + if(world_rank == 0) { + auto const AA = multi::array( + { + {1, 2, 3}, + {4, 5, 6} + } + ); + auto const& BB = AA({0, 2}, {1, 3}); + BOOST_TEST(( BB[1][1] == multi::array({{2, 3}, {5, 6}})[1][1] )); + + MPI_Datatype B_type; // NOLINT(cppcoreguidelines-init-variables) + multi::mpi::create_subarray(BB.layout(), MPI_INT, &B_type); + MPI_Type_commit(&B_type); + MPI_Send(BB.base(), 1, B_type, 1, 0, comm); + MPI_Type_free(&B_type); + } else if(world_rank == 1) { + multi::array CC({2, 2}, 99); + + auto const C_sk = multi::mpi::skeleton(CC.layout(), MPI_INT); + + MPI_Recv(CC.base(), C_sk.count(), C_sk.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + std::cout << CC[0][0] << ' ' << CC[0][1] << '\n' + << CC[1][0] << ' ' << CC[1][1] << '\n'; + + BOOST_TEST(( CC == multi::array({{2, 3}, {5, 6}}) )); + } + } +} + +void test_2d_int(MPI_Comm comm) { + int world_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(comm, &world_rank); + int world_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(comm, &world_size); + + { + if(world_rank == 0) { + auto const AA = multi::array({ + {1, 2, 3}, + {4, 5, 6} + }); + + auto const& BB = AA({0, 2}, {1, 3}); + BOOST_TEST(( BB == multi::array({{2, 3}, {5, 6}}) )); + + auto const B_msg = multi::mpi::message(BB.elements()); + + MPI_Send(B_msg.buffer(), B_msg.count(), B_msg.datatype(), 1, 0, comm); + } else if(world_rank == 1) { + multi::array CC({2, 2}, 99); + + auto const C_sk = multi::mpi::skeleton(CC.layout()); + + MPI_Recv(CC.base(), C_sk.count(), C_sk.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + std::cout << CC[0][0] << ' ' << CC[0][1] << '\n' + << CC[1][0] << ' ' << CC[1][1] << '\n'; + + BOOST_TEST(( CC == multi::array({{2, 3}, {5, 6}}) )); + } + } +} + +void test_2d_double(MPI_Comm comm) { + int world_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(comm, &world_rank); + int world_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(comm, &world_size); + + { + if(world_rank == 0) { + auto const AA = multi::array({ + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0} + }); + + auto const& BB = AA({0, 2}, {1, 3}); + BOOST_TEST(( BB == multi::array({{2.0, 3.0}, {5.0, 6.0}}) )); + + auto const& B_msg = multi::mpi::message(BB.elements()); + + MPI_Send(B_msg.buffer(), B_msg.count(), B_msg.datatype(), 1, 0, comm); + } else if(world_rank == 1) { + multi::array CC({2, 2}, 99.0); + + auto const C_sk = multi::mpi::skeleton(CC.layout()); + + MPI_Recv(CC.base(), C_sk.count(), C_sk.datatype(), 0, 0, comm, MPI_STATUS_IGNORE); + std::cout << CC[0][0] << ' ' << CC[0][1] << '\n' + << CC[1][0] << ' ' << CC[1][1] << '\n'; + + BOOST_TEST(( CC == multi::array({{2.0, 3.0}, {5.0, 6.0}}) )); + } + } +} + +} // namespace + +auto main() -> int { // NOLINT(bugprone-exception-escape,readability-function-cognitive-complexity) + MPI_Init(nullptr, nullptr); + + int world_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + int world_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + + std::cout << "size " << world_size << '\n'; + // int world_rank; MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); // NOLINT(cppcoreguidelines-init-variables) + // int world_size; MPI_Comm_size(MPI_COMM_WORLD, &world_size); // NOLINT(cppcoreguidelines-init-variables) + + test_single_number(MPI_COMM_WORLD); + test_1d(MPI_COMM_WORLD); + + { + multi::array AA({3}, 99); + if(world_rank == 0) { + AA = multi::array({1, 2, 3}); + MPI_Send(AA.base(), static_cast(AA.size()), MPI_INT, 1, 0, MPI_COMM_WORLD); + } else if(world_rank == 1) { + MPI_Recv(AA.base(), static_cast(AA.size()), MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + BOOST_TEST(( AA == boost::multi::array({1, 2, 3}) )); + } + } + { + if(world_rank == 0) { + auto const AA = multi::array({ + {1, 2, 3}, + {4, 5, 6} + }); + + auto const& BB = AA({0, 2}, {1, 3}); + BOOST_TEST(( BB == multi::array({{2, 3}, {5, 6}}) )); + + auto const B_sk = multi::mpi::skeleton(BB.layout(), MPI_INT); + + MPI_Send(BB.base(), B_sk.count(), B_sk.datatype(), 1, 0, MPI_COMM_WORLD); + } else if(world_rank == 1) { + multi::array CC({2, 2}, 99); + + auto const C_sk = multi::mpi::skeleton(CC.elements().layout(), MPI_INT); + + MPI_Recv(CC.base(), C_sk.count(), C_sk.datatype(), 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + BOOST_TEST(( CC == multi::array({{2, 3}, {5, 6}}) )); + } + } + MPI_Barrier(MPI_COMM_WORLD); + + test_2d(MPI_COMM_WORLD); + test_2d_int(MPI_COMM_WORLD); + test_2d_double(MPI_COMM_WORLD); + + if(world_size == 4) { + multi::array local_arr(world_size); + std::iota(local_arr.begin(), local_arr.end(), world_rank * 10); + + BOOST_TEST( local_arr[1] == (world_rank*10) + 1 ); + + multi::array local_arr2(world_size, 99); + + auto local_arr_it = multi::mpi::begin(local_arr); + auto local_arr2_it = multi::mpi::begin(local_arr2); + + MPI_Alltoall( + local_arr_it.buffer(), 1, local_arr_it.datatype(), + local_arr2_it.buffer(), 1, local_arr2_it.datatype(), + MPI_COMM_WORLD + ); + + if(world_rank == 0) { + BOOST_TEST(( local_arr2 == multi::array{00, 10, 20, 30} )); + } + if(world_rank == 1) { + BOOST_TEST(( local_arr2 == multi::array{01, 11, 21, 31} )); + } + + multi::array in_place_arr = local_arr; + + auto in_place_arr_it = multi::mpi::begin(in_place_arr); + + MPI_Alltoall( + MPI_IN_PLACE, 1, in_place_arr_it.datatype(), + in_place_arr_it.buffer(), 1, in_place_arr_it.datatype(), + MPI_COMM_WORLD + ); + + BOOST_TEST( in_place_arr == local_arr2 ); + } + + { + { + multi::array arr = { + { + {1, 2}, + {3, 4}, + } + }; + + multi::array brr(arr.extensions(), 666); + auto arr_begin = multi::mpi::begin(arr); + auto brrt = brr.rotated().transposed().unrotated(); + + auto brrt_begin = multi::mpi::begin(brrt); + + MPI_Alltoall( + arr_begin.buffer(), 1, arr_begin.datatype(), + brrt_begin.buffer(), 1, brrt_begin.datatype(), + MPI_COMM_SELF + ); + + BOOST_TEST(( + brr == multi::array{{ + {1, 3}, + {2, 4}, + }} + )); + } + { + multi::array arr = { + { + {1, 2}, + {3, 4}, + } + }; + + auto&& trr = arr.rotated().transposed().unrotated(); + + auto arr_begin = multi::mpi::begin(arr); + auto trr_begin = multi::mpi::begin(trr); + + BOOST_TEST( arr_begin.buffer() == trr_begin.buffer() ); + + MPI_Alltoall( + arr_begin.buffer(), 1, arr_begin.datatype(), + trr_begin.buffer(), 1, trr_begin.datatype(), + MPI_COMM_SELF + ); + + BOOST_TEST(( + arr == multi::array{{ + {1, 3}, + {2, 4}, + }} + )); + } + { + multi::array arr({1, 2, 2}, 666); + std::iota(arr.elements().begin(), arr.elements().end(), 1); + + auto Tarr = +arr.transposed(); + + auto&& trr = arr.rotated().transposed().unrotated(); + + auto arr_begin = multi::mpi::begin(arr); + auto trr_begin = multi::mpi::begin(trr); + + BOOST_TEST( arr_begin.buffer() == trr_begin.buffer() ); + + MPI_Alltoall( + arr_begin.buffer(), 1, arr_begin.datatype(), + trr_begin.buffer(), 1, trr_begin.datatype(), + MPI_COMM_SELF + ); + + BOOST_TEST(( + arr == multi::array{{ + {1, 3}, + {2, 4}, + }} + )); + } + } + + if(world_size == 4) { + multi::array local_arr = { + {(world_rank * 10) + 0, (world_rank * 10) + 0}, + {(world_rank * 10) + 1, (world_rank * 10) + 1}, + {(world_rank * 10) + 2, (world_rank * 10) + 2}, + {(world_rank * 10) + 3, (world_rank * 10) + 3} + }; + + multi::array local_arr2({4, 2}, 99); + + auto local_arr_it = multi::mpi::begin(local_arr); + auto local_arr2_it = multi::mpi::begin(local_arr2); + + MPI_Alltoall( + local_arr_it.buffer(), 1, local_arr_it.datatype(), + local_arr2_it.buffer(), 1, local_arr2_it.datatype(), + MPI_COMM_WORLD + ); + + if(world_rank == 0) { + BOOST_TEST(( local_arr2 == multi::array{{00, 00}, {10, 10}, {20, 20}, {30, 30}} )); + } + if(world_rank == 1) { + BOOST_TEST(( local_arr2 == multi::array{{01, 01}, {11, 11}, {21, 21}, {31, 31}} )); + } + } + { + MPI_Comm sub_comm; // NOLINT(cppcoreguidelines-init-variables) + + MPI_Comm_split( + MPI_COMM_WORLD, + /*color*/ world_rank < 2 ? 0 : MPI_UNDEFINED, // Assign to the same color to group them + /*key*/ world_rank < 2 ? world_rank : 0, // Use the original rank for ordering + &sub_comm + ); + + if(sub_comm != MPI_COMM_NULL) { + int sub_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(sub_comm, &sub_rank); + + int sub_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(sub_comm, &sub_size); + + multi::array A; // NOLINT(readability-identifier-length) conventional name + switch(sub_rank) { + /****/ case 0: + A = multi::array{ + { 1, 2, 3}, + { 7, 8, 9}, + {13, 14, 15}, + {19, 20, 21}, + }; + break; + case 1: + A = multi::array{ + { 4, 5, 6}, + {10, 11, 12}, + {16, 17, 18}, + {22, 23, 24}, + }; + break; + default: { + } + } + + multi::array B({6, 2}, 99); // NOLINT(readability-identifier-length) + + auto&& Ap2 = A.partitioned(2); + BOOST_TEST( Ap2.size() == 2 ); + auto&& Bp2 = B.partitioned(2).rotated().transposed().unrotated(); + BOOST_TEST( Bp2.size() == 2 ); + + auto A_it = multi::mpi::begin(Ap2); + auto B_it = multi::mpi::begin(Bp2); + + MPI_Alltoall( + A_it.buffer(), 1, A_it.datatype(), + B_it.buffer(), 1, B_it.datatype(), + sub_comm + ); + + switch(sub_rank) { + case 0: + BOOST_TEST(( + B == multi::array{ + {1, 7}, + {2, 8}, + {3, 9}, + {4, 10}, + {5, 11}, + {6, 12}, + } + )); + break; + case 1: + BOOST_TEST(( + B == multi::array{ + {13, 19}, + {14, 20}, + {15, 21}, + {16, 22}, + {17, 23}, + {18, 24}, + } + )); + break; + default: { + } + } + } + + if(sub_comm != MPI_COMM_NULL) { + int sub_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(sub_comm, &sub_rank); + int sub_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(sub_comm, &sub_size); + + BOOST_TEST( sub_size == 2 ); + + multi::array A_local; + switch(sub_rank) { + case 0: + A_local = multi::array{ + { 1, 2, 3}, + { 7, 8, 9}, + {13, 14, 15}, + {19, 20, 21}, + }; + break; + case 1: + A_local = multi::array{ + { 4, 5, 6}, + {10, 11, 12}, + {16, 17, 18}, + {22, 23, 24}, + }; + break; + default: { + } + } + + // B's memmory completelly aliases A's memory + // auto&& B_local = multi::array_ref({6, 2}, A_local.data_elements()); // NOLINT(readability-identifier-length) conventional name + multi::array B_local({6, 2}, 99); // this one would do out-of-place // NOLINT(readability-identifier-length) conventional name + + auto A_local_it = multi::mpi::begin(A_local.partitioned(2)); // magic way to compute the datatype + auto B_local_it = multi::mpi::begin(B_local.partitioned(2).rotated().transposed().unrotated()); // magic way to compute the datatype + + BOOST_TEST( A_local_it.buffer() != B_local_it.buffer() ); + + MPI_Alltoall( + A_local_it.buffer(), 1, A_local_it.datatype(), + B_local_it.buffer(), 1, B_local_it.datatype(), + sub_comm + ); + + switch(sub_rank) { + case 0: + BOOST_TEST(( + B_local == multi::array{ + {1, 7}, + {2, 8}, + {3, 9}, + {4, 10}, + {5, 11}, + {6, 12} + } + )); + break; + case 1: + BOOST_TEST(( + B_local == multi::array{ + {13, 19}, + {14, 20}, + {15, 21}, + {16, 22}, + {17, 23}, + {18, 24} + } + )); + break; + default: { + } + } + } + + if(sub_comm != MPI_COMM_NULL) { + int sub_rank; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_rank(sub_comm, &sub_rank); + int sub_size; // NOLINT(cppcoreguidelines-init-variables) + MPI_Comm_size(sub_comm, &sub_size); + + BOOST_TEST( sub_size == 2 ); + + auto block_n = 20L; + auto block_m = 30L; + + multi::array A_local({block_n * sub_size, block_m}); + std::iota(A_local.elements().begin(), A_local.elements().end(), sub_rank); + + // B's memmory completelly aliases A's memory + // auto&& B_local = multi::array_ref({block_m*sub_size, block_n}, A_local.data_elements()); + // multi::array B_local({6, 2}, 99); // this one would do out-of-place + auto G_local = multi::array({block_m * sub_size, block_n}); + + auto A_local_it = multi::mpi::begin(A_local.partitioned(sub_size)); // magic way to compute the datatype + // auto B_local_it = multi::mpi::begin(B_local.partitioned(sub_size).rotated().transposed().unrotated()); // magic way to compute the datatype + auto G_local_it = multi::mpi::begin(G_local.partitioned(sub_size).rotated().transposed().unrotated()); // magic way to compute the datatype + + BOOST_TEST( G_local_it.buffer() != A_local_it.buffer() ); + + MPI_Alltoall( + A_local_it.buffer(), 1, A_local_it.datatype(), + G_local_it.buffer(), 1, G_local_it.datatype(), + sub_comm + ); + + // warning MPI_All_to_all doesn't work in-place + // BOOST_TEST( A_local_it.buffer() == B_local_it.buffer() ); + + // MPI_Alltoall( + // A_local_it.buffer(), 1, A_local_it.datatype(), + // B_local_it.buffer(), 1, B_local_it.datatype(), + // sub_comm + // ); + + // BOOST_TEST( B_local == G_local ); + } + } + + MPI_Finalize(); + + return boost::report_errors(); +} diff --git a/include/multi/adaptors/tblis.hpp b/include/boost/multi/adaptors/tblis.hpp similarity index 81% rename from include/multi/adaptors/tblis.hpp rename to include/boost/multi/adaptors/tblis.hpp index 3ce1c7ae8..ea8ae4126 100644 --- a/include/multi/adaptors/tblis.hpp +++ b/include/boost/multi/adaptors/tblis.hpp @@ -1,7 +1,7 @@ #ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4-*- $CXX -std=c++17 -O0 -I/home/correaa/include/tblis -I/home/correaa/tblis/src/external/tci -L/home/correaa/lib -Wl,-rpath=/home/correaa/lib -ltblis $0 -o $0x&&$0x&&rm $0x;exit #endif -// © Alfredo A. Correa 2021 +// Copyright 2021-2024 Alfredo A. Correa #include "tblis/tblis.h" @@ -103,17 +103,17 @@ namespace μνσ{ -template auto init_matrix = std::enable_if_t{}; -template<> auto init_matrix = ::tblis::tblis_init_matrix_s; -template<> auto init_matrix = ::tblis::tblis_init_matrix_d; -template<> auto init_matrix> = ::tblis::tblis_init_matrix_c; -template<> auto init_matrix> = ::tblis::tblis_init_matrix_z; +template auto const init_matrix = std::enable_if_t{}; +template<> auto const init_matrix = ::tblis::tblis_init_matrix_s; +template<> auto const init_matrix = ::tblis::tblis_init_matrix_d; +template<> auto const init_matrix> = ::tblis::tblis_init_matrix_c; +template<> auto const init_matrix> = ::tblis::tblis_init_matrix_z; -template auto init_tensor = std::enable_if_t{}; -template<> auto init_tensor = ::tblis::tblis_init_tensor_s; -template<> auto init_tensor = ::tblis::tblis_init_tensor_d; -template<> auto init_tensor> = ::tblis::tblis_init_tensor_c; -template<> auto init_tensor> = ::tblis::tblis_init_tensor_z; +template auto const init_tensor = std::enable_if_t{}; +template<> auto const init_tensor = ::tblis::tblis_init_tensor_s; +template<> auto const init_tensor = ::tblis::tblis_init_tensor_d; +template<> auto const init_tensor> = ::tblis::tblis_init_tensor_c; +template<> auto const init_tensor> = ::tblis::tblis_init_tensor_z; template struct indexed_tensor; @@ -124,13 +124,14 @@ struct tensor : ::tblis::tblis_tensor{ std::array<::tblis::len_type , D> lens_; std::array<::tblis::stride_type, D> strides_; template>{}, int> =0> - explicit tensor(A&& a) : + explicit tensor(A&& a) : // NOLINT(bugprone-forwarding-reference-overload) workaround for DeepSource lens_ (std::apply([](auto... s){return std::array<::tblis::len_type , D>{s...};}, sizes (a))), - strides_(std::apply([](auto... s){return std::array<::tblis::stride_type, D>{s...};}, strides(a))){ + strides_(std::apply([](auto... s){return std::array<::tblis::stride_type, D>{s...};}, strides(a))) + { tblis::init_tensor>(this, D, lens_.data(), const_cast*>(base(a)), strides_.data()); } tensor(tensor const&) = delete; - tensor(tensor&& other) : lens_{other.lens_}, strides_{other.strides_}{ + tensor(tensor&& other) noexcept : lens_{other.lens_}, strides_{other.strides_}{ tblis::init_tensor>(this, D, lens_.data(), const_cast*>(other.data()), strides_.data()); } using dimensionality_type = multi::dimensionality_type; @@ -155,7 +156,7 @@ struct indexed_tensor{ tensor tensor_; std::string indices_; indexed_tensor(tensor&& t, std::string indices) : tensor_(std::move(t)), indices_{std::move(indices)}{} - indexed_tensor(indexed_tensor&& other) = default; + indexed_tensor(indexed_tensor&& other) noexcept = default; tensor& tensor_part()&{return tensor_;} std::string indices() const{return indices_;} }; @@ -188,16 +189,17 @@ auto mult(ITensorA&& aijk, ITensorB&& bijk, ITensorC&& cijk) template struct matrix : ::tblis::tblis_matrix{ public: - template, std::decay_t>{}, int> =0> - matrix(A&& a){ + matrix(matrix const&) = delete; + matrix(matrix&&) noexcept = default; + +// template matrix(matrix const& other) : ::tblis::tblis_matrix + template>>> > + matrix(A&& a) { // NOLINT(bugprone-forwarding-reference-overload) workaround for DeepSource init_matrix(this, std::get<0>(a.sizes()), std::get<1>(a.sizes()), const_cast(a.base()), std::get<0>(a.strides()), std::get<1>(a.strides()) ); } -// template matrix(matrix const& other) : ::tblis::tblis_matrix - matrix(matrix const&) = delete; - matrix(matrix&&) = default; }; template::element_ptr> matrix(A&&)->matrix::element_type>; diff --git a/include/multi/adaptors/tblis/test/CMakeLists.txt b/include/boost/multi/adaptors/tblis/test/CMakeLists.txt similarity index 93% rename from include/multi/adaptors/tblis/test/CMakeLists.txt rename to include/boost/multi/adaptors/tblis/test/CMakeLists.txt index ae370d26e..d2803c501 100644 --- a/include/multi/adaptors/tblis/test/CMakeLists.txt +++ b/include/boost/multi/adaptors/tblis/test/CMakeLists.txt @@ -60,10 +60,10 @@ foreach(TEST_FILE ${TEST_SRCS}) add_executable(${TEST_EXE} ${TEST_FILE}) if(ENABLE_CUDA OR DEFINED CXXCUDA) set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) - target_compile_options(${TEST_EXE} PRIVATE -std=c++17) + # target_compile_options(${TEST_EXE} PRIVATE -std=c++17) endif() # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") + # target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) @@ -106,8 +106,9 @@ foreach(TEST_FILE ${TEST_SRCS}) -diag-error:3846 > $<$: - /W4> + #/W4 + > ) endif() - add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) + add_test(NAME ${TEST_EXE} COMMAND $) endforeach() diff --git a/include/multi/adaptors/tblis/test/tensor.cpp b/include/boost/multi/adaptors/tblis/test/tensor.cpp similarity index 92% rename from include/multi/adaptors/tblis/test/tensor.cpp rename to include/boost/multi/adaptors/tblis/test/tensor.cpp index 4bba92932..099ce0347 100644 --- a/include/multi/adaptors/tblis/test/tensor.cpp +++ b/include/boost/multi/adaptors/tblis/test/tensor.cpp @@ -1,11 +1,8 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -// © Alfredo A. Correa 2019-2021 +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS dot" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../tblis.hpp" +sass namespace multi = boost::multi; @@ -69,7 +66,7 @@ BOOST_AUTO_TEST_CASE(blis_matrix){ multi::array C({5, 7}, 0.); using namespace tblis::indices; tblis::mult(A(a, c), B(c, b), C(a, b)); - // BOOST_REQUIRE( C_gold == C ); + // BOOST_REQUIRE( C_gold == C ); } } diff --git a/include/multi/adaptors/thrust.hpp b/include/boost/multi/adaptors/thrust.hpp similarity index 59% rename from include/multi/adaptors/thrust.hpp rename to include/boost/multi/adaptors/thrust.hpp index 9d76baef2..a05257dfe 100644 --- a/include/multi/adaptors/thrust.hpp +++ b/include/boost/multi/adaptors/thrust.hpp @@ -1,28 +1,18 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021-2022 Alfredo A. Correa +// Copyright 2021-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_THRUST_HPP_ -#define MULTI_ADAPTORS_THRUST_HPP_ +#ifndef BOOST_MULTI_ADAPTORS_THRUST_HPP_ +#define BOOST_MULTI_ADAPTORS_THRUST_HPP_ #pragma once -#ifndef hicup -#define hicup cuda -#endif - -#include "../array.hpp" - -// #if hicup == cuda -// #include "./thrust/cuda/managed.hpp" -// #endif - -#include "./thrust/reference.hpp" +#include #include #include -#include -#if defined(__NVCC__) +#if !defined(MULTI_USE_HIP) #include // for ::thrust::cuda::allocator #else #include // for ::thrust::hip::allocator @@ -31,10 +21,37 @@ // #include -#include // std::copy +// #include // std::copy -#include -#include +#include // for pointer + +#include // for allocator (ptr only), stateless_resource_allocator +#include // for memory_resource + +#if !defined(MULTI_USE_HIP) +#include // for tag +#include // for universal_memory_resource +#include // for universal_pointer + +#include // for cudaGetDevice, cudaMemPrefetchAsync, cudaPointerGetAttributes +#include // for cudaErrorInvalidValue, cudaPointerAttributes, cudaSuccess, cudaErrorInvalidDevice, cudaMemoryTypeManaged +#else +// #include // for tag +// #include // for universal_memory_resource +// #include // for universal_pointer + +// #include // for cudaGetDevice, cudaMemPrefetchAsync, cudaPointerGetAttributes +#endif + +#include + +#include +#include // for iterator_traits +#include // for allocator_traits, allocator, pointer_traits +// #include // for iterator_system +#include // for decay_t + +// #include // // begin of nvcc trhust 11.5 workaround : https://github.com/NVIDIA/thrust/issues/1629 // namespace thrust { @@ -55,7 +72,7 @@ // } // end namespace std // // end of nvcc thrust 11.5 workaround -#if defined(__NVCC__) +#if !defined(MULTI_USE_HIP) #define HICUP cuda #define HICUP_(NAME) cuda ## NAME #else @@ -63,6 +80,8 @@ #define HICUP_(NAME) hip ## NAME #endif +namespace boost::multi { template struct allocator_traits; } + namespace boost::multi { template @@ -87,67 +106,75 @@ struct allocator_traits<::thrust::mr::stateless_resource_allocator pointer { - auto ret = allocator_traits::allocate(a, n); - if(not hint) { - prefetch_to_device(ret, n*sizeof(TT), get_current_device()); + [[nodiscard]] static constexpr auto allocate(Alloc& alloc, size_type n, const_void_pointer hint) -> pointer { + auto ret = allocator_traits::allocate(alloc, n); + if(!hint) { + prefetch_to_device_(ret, n*sizeof(TT), get_current_device_()); return ret; } - prefetch_to_device(ret, n*sizeof(TT), get_device(hint)); + prefetch_to_device_(ret, n*sizeof(TT), get_device_(hint)); return ret; } private: using device_index = int; - static auto get_current_device() -> device_index { - int device; + static auto get_current_device_() -> device_index { + int device; // NOLINT(cppcoreguidelines-init-variables) delayed init switch(HICUP_(GetDevice)(&device)) { case HICUP_(Success) : break; - case HICUP_(ErrorInvalidValue): assert(0); + case HICUP_(ErrorInvalidValue): assert(0); // NOLINT(bugprone-branch-clone) default: assert(0); } return device; } - static void prefetch_to_device(const_void_pointer p, size_type byte_count, device_index d) { - switch(HICUP_(MemPrefetchAsync)(raw_pointer_cast(p), byte_count, d)) { + static void prefetch_to_device_(const_void_pointer ptr, size_type byte_count, device_index dev) { +#if(CUDART_VERSION < 13000) // CudaMemPrefetchAsync changes its interface on version 13 TODO(correaa) update API call + switch(HICUP_(MemPrefetchAsync)(raw_pointer_cast(ptr), byte_count, dev)) { case HICUP_(Success) : break; - case HICUP_(ErrorInvalidValue) : assert(0); break; - case HICUP_(ErrorInvalidDevice): assert(0); break; + case HICUP_(ErrorInvalidValue) : assert(0); break; // NOLINT(bugprone-branch-clone) + case HICUP_(ErrorInvalidDevice): assert(0); break; // NOLINT(bugprone-branch-clone) default: assert(0); } +#endif } - static auto get_device(const_void_pointer p) -> device_index { - #if defined(__NVCC__) - cudaPointerAttributes attr{}; - #else + static auto get_device_(const_void_pointer ptr) -> device_index { + #if defined(__HIPCC__) hipPointerAttribute_t attr{}; + #else // #if defined(__NVCC__) + cudaPointerAttributes attr{}; #endif - switch(HICUP_(PointerGetAttributes)(&attr, raw_pointer_cast(p))) { + switch(HICUP_(PointerGetAttributes)(&attr, raw_pointer_cast(ptr))) { case HICUP_(Success): break; - case HICUP_(ErrorInvalidDevice): assert(0); break; - case HICUP_(ErrorInvalidValue): assert(0); break; + case HICUP_(ErrorInvalidDevice): assert(0); break; // NOLINT(bugprone-branch-clone) + case HICUP_(ErrorInvalidValue): assert(0); break; // NOLINT(bugprone-branch-clone) + default: assert(0); // 71 enumeration values not handled in switch: 'hipErrorOutOfMemory', 'hipErrorNotInitialized', 'hipErrorDeinitialized'... } assert(attr.type == HICUP_(MemoryTypeManaged)); return attr.device; } }; -} // end namespace ::boost::multi +} // end namespace boost::multi // this is important for algorithms to dispatch to the right thrust executor namespace thrust { -template struct iterator_system; +// template struct iterator_system; // not needed in cuda 12.0, doesn't work on cuda 12.5 -template -struct iterator_system>{ - using type = typename ::thrust::iterator_system::element_ptr>::type; +template +struct iterator_system<::boost::multi::array_iterator >{ + using type = typename ::thrust::iterator_system::element_ptr>::type; }; template -struct iterator_system> { - using type = typename ::thrust::iterator_system::pointer>::type; +struct iterator_system<::boost::multi::elements_iterator_t > { // TODO(correaa) might need changes for IsConst templating + using type = typename ::thrust::iterator_system::pointer>::type; +}; + +template +struct iterator_system<::boost::multi::transform_ptr > { // TODO(correaa) might need changes for IsConst templating + using type = typename ::thrust::iterator_system::type; }; // namespace detail { @@ -198,10 +225,9 @@ struct iterator_system> { // }; // } -} // end namespace ::thrust +} // end namespace thrust -namespace boost::multi { -namespace thrust { +namespace boost::multi::thrust { // defines multi::thrust::device_array // defines multi::thrust::host_array @@ -222,7 +248,7 @@ namespace cuda { template using array = multi::array>; // namespace managed { - // template using array = multi::array>; + // template using array = multi::array>; // } // end namespace managed } // end namespace cuda @@ -238,16 +264,14 @@ template using universal_array = multi::a namespace universal { template using array = multi::thrust::cuda::universal_array; -} +} // end namespace universal namespace pmr { template using universal_array = ::boost::multi::thrust::pmr::array>; } // end namespace pmr } // end namespace cuda - -} // end namespace thrust -} // end namespace boost::multi +} // end namespace boost::multi::thrust namespace boost::multi { @@ -256,6 +280,9 @@ constexpr auto default_allocator_of(::thrust::pointer>::value_type>{}; } -} +} // end namespace boost::multi + +#undef HICUP +#undef HICUP_ #endif diff --git a/include/boost/multi/adaptors/thrust/CMakeLists.txt b/include/boost/multi/adaptors/thrust/CMakeLists.txt new file mode 100644 index 000000000..42ca175b0 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.11) +project( + boost-multi-adaptor-cuda + VERSION 0.1 + LANGUAGES CXX +) + +find_package(Boost CONFIG REQUIRED COMPONENTS headers) # REQUIRED COMPONENTS unit_test_framework) + +if(ENABLE_CUDA OR DEFINED CXXCUDA) + enable_language(CUDA) + + enable_testing() + + add_subdirectory(test) + add_subdirectory(cuda/test) +endif() + +add_subdirectory(omp/test) diff --git a/include/multi/adaptors/thrust/algorithms.hpp b/include/boost/multi/adaptors/thrust/algorithms.hpp similarity index 100% rename from include/multi/adaptors/thrust/algorithms.hpp rename to include/boost/multi/adaptors/thrust/algorithms.hpp diff --git a/include/multi/adaptors/thrust/allocator_traits.hpp b/include/boost/multi/adaptors/thrust/allocator_traits.hpp similarity index 99% rename from include/multi/adaptors/thrust/allocator_traits.hpp rename to include/boost/multi/adaptors/thrust/allocator_traits.hpp index 9e01fa62e..86c97a669 100644 --- a/include/multi/adaptors/thrust/allocator_traits.hpp +++ b/include/boost/multi/adaptors/thrust/allocator_traits.hpp @@ -17,7 +17,7 @@ namespace thrust { namespace boost { namespace multi { -namespace memor y{ +namespace memory { template //template<> diff --git a/include/multi/adaptors/thrust/complex.hpp b/include/boost/multi/adaptors/thrust/complex.hpp similarity index 100% rename from include/multi/adaptors/thrust/complex.hpp rename to include/boost/multi/adaptors/thrust/complex.hpp diff --git a/include/multi/adaptors/thrust/cuda/managed.hpp b/include/boost/multi/adaptors/thrust/cuda/managed.hpp similarity index 100% rename from include/multi/adaptors/thrust/cuda/managed.hpp rename to include/boost/multi/adaptors/thrust/cuda/managed.hpp diff --git a/include/boost/multi/adaptors/thrust/cuda/test/CMakeLists.txt b/include/boost/multi/adaptors/thrust/cuda/test/CMakeLists.txt new file mode 100644 index 000000000..5fa932214 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/cuda/test/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.16) + +if(ENABLE_CUDA OR DEFINED CXXCUDA) + enable_language(CUDA) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) + endif() + find_package(CUDAToolkit REQUIRED COMPONENTS Thrust) # requires cmake 3.17 +endif() + +find_package(Boost CONFIG REQUIRED) # tests require Boost.Core LightweightTest + +enable_testing() +include(CTest) + +include_directories(${CMAKE_BINARY_DIR}) + +set(TEST_SRCS) +# neighbor_list.cu) + +foreach(TEST_FILE ${TEST_SRCS}) + if(ENABLE_CUDA OR DEFINED CXXCUDA) + set(TEST_EXE "${TEST_FILE}.x") + add_executable(${TEST_EXE} ${TEST_FILE}) + + target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) + + # target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) + target_include_directories(${TEST_EXE} PRIVATE ${CUDA_INCLUDE_DIRS}) + + #target_compile_options(${TEST_EXE} PRIVATE --expt-relaxed-constexpr) + + target_link_libraries(${TEST_EXE} PRIVATE ${CUDA_LIBRARIES}) + target_link_libraries(${TEST_EXE} PRIVATE multi) + + set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) + + add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) + endif() +endforeach() + +# target_compile_options(neighbor_list.cu.x PRIVATE $<$:--extended-lambda>) diff --git a/include/boost/multi/adaptors/thrust/cuda/test/managed.cu b/include/boost/multi/adaptors/thrust/cuda/test/managed.cu new file mode 100644 index 000000000..5da45294c --- /dev/null +++ b/include/boost/multi/adaptors/thrust/cuda/test/managed.cu @@ -0,0 +1,145 @@ + +auto main() -> int { +} +// #include + +// #include "../../cuda/managed.hpp" + +// namespace multi = boost::multi; + +// void set_one(double* p){ +// *p = 1.; +// } + +// void set_two_gpu(thrust::cuda::pointer p){ +// *p = 2.; +// } + +// void set_three_ref(double& p){ +// p = 3.; +// } + +// template::value_type, class = std::enable_if_t{} and std::is_convertible>{}> > +// void some_fun(Pointer p){} + +// template::value_type, class = std::enable_if_t{} and std::is_convertible{}> > +// void some_other_fun(Pointer p){} + +// template class prio : std::conditional_t, std::false_type>{}; + +// template::value_type, std::enable_if_t{} and std::is_convertible>{}, int> =0> +// int overload_aux(Pointer p, prio<0>){return 0;} + +// template::value_type, std::enable_if_t{} and std::is_convertible{}, int> =0> +// int overload_aux(Pointer p, prio<1>){return 1;} + +// template int overload(Pointer p){return overload_aux(p, prio<1>{});} + +// BOOST_AUTO_TEST_CASE(vector){ + +// multi::thrust::cuda::managed::allocator alloc; +// multi::thrust::cuda::managed::pointer p = alloc.allocate(100); + +// p[17] = 3.; +// BOOST_TEST_REQUIRE( p[17] == 3. ); + +// set_one(p); +// BOOST_TEST_REQUIRE( p[0] == 1. ); + +// set_two_gpu(p); +// BOOST_TEST_REQUIRE( p[0] == 2. ); + +// set_three_ref( p[1] ); +// BOOST_TEST_REQUIRE( p[1] == 3. ); + +// some_fun(p); + +// BOOST_TEST_REQUIRE(overload(p) == 1); + +// alloc.deallocate(p, 100); + +// } + +// BOOST_AUTO_TEST_CASE(vector) +// { +// static_assert(std::is_same>::element_type, double>{}, "!"); +// cuda::allocator calloc; +// cuda::ptr p = calloc.allocate(100); +// cuda::ptr v = p; +// cuda::ptr vc{v}; +// v = const_pointer_cast(vc); +// assert( vc == v ); +// std::pointer_traits::rebind pc = p; // cuda::ptr pc = p; +// assert( pc == p ); +// using cuda::const_pointer_cast; +// auto end = p + 100; +// auto rbegin = std::make_reverse_iterator(end); +// auto rend = std::make_reverse_iterator(p); +// std::transform(rbegin, rend, rbegin, [](auto&& e){return std::forward(e) + 99.;}); +// assert( p[11] == 99. ); +// p[33] = 123.; +// p[99] = 321.; +// // p[33] += 1; +// add_one(p[33]); +// double p33 = p[33]; +// assert( p33 == 124. ); +// assert( p[33] == 124. ); +// assert( p[33] == p[33] ); +// swap(p[33], p[99]); +// assert( p[99] == 124. ); +// assert( p[33] == 321. ); +// std::cout << p[33] << std::endl; +// calloc.deallocate(p, 100); + +// multi::array> arr2(multi::array::extensions_type{100l}, 999.); + +// assert(size(arr2) == 100); +// } + +// #ifdef COMPILATION_INSTRUCTIONS +// nvcc -ccbin cuda-c++ -std=c++14 $0 -o $0x && $0x && rm -f $0x; exit +// #endif + +// #include "../../../../multi/array.hpp" +// #include "../../../../multi/detail/stack_allocator.hpp" +// #include "../../../../multi/detail/cuda/allocator.hpp" + +// #include + +// namespace multi = boost::multi; +// namespace cuda = multi::detail::memory::cuda; + +// using std::cout; + +// int main(){ +// { +// std::size_t stack_size = 4000; +// multi::stack_buffer> buf{stack_size}; +// for(int i = 0; i != 3; ++i){ +// cout<<"pass "<< i << std::endl; +// { +// multi::array>> A({2, 10}, &buf); +// multi::array>> B({3, 10}, &buf); +// multi::array>> C({4, 10}, &buf); +// for(int j = 0; j != 100; ++j) +// multi::array>> D({4, 10}, &buf); +// B[1][1] = 33.; +// B[2][2] = 33.; +// assert( B[1][1] == B[2][2] ); +// } +// cout +// <<" size: "<< buf.size() +// <<"\n hits: "<< buf.hits() +// <<"\n misses "<< buf.misses() +// <<"\n allocated(bytes) "<< buf.allocated_bytes() +// <<"\n deallocated(bytes) "<< buf.deallocated_bytes() +// <<"\n max_needed(bytes) "<< buf.max_needed() +// <<"\n stack recovered(bytes) " << buf.stack_recovered() +// << std::endl +// ; +// assert( buf.allocated_bytes() == buf.deallocated_bytes() ); +// if(buf.max_needed() > buf.size()) buf.reset(buf.max_needed()); +// } +// } +// assert( cuda::allocation_counter::n_allocations == 1 ); +// } diff --git a/include/multi/adaptors/thrust/fix_complex_traits_.hpp b/include/boost/multi/adaptors/thrust/fix_complex_traits_.hpp similarity index 100% rename from include/multi/adaptors/thrust/fix_complex_traits_.hpp rename to include/boost/multi/adaptors/thrust/fix_complex_traits_.hpp diff --git a/include/multi/adaptors/thrust/fix_copy.hpp b/include/boost/multi/adaptors/thrust/fix_copy.hpp similarity index 97% rename from include/multi/adaptors/thrust/fix_copy.hpp rename to include/boost/multi/adaptors/thrust/fix_copy.hpp index aaa38140d..edc4f87f3 100644 --- a/include/multi/adaptors/thrust/fix_copy.hpp +++ b/include/boost/multi/adaptors/thrust/fix_copy.hpp @@ -1,13 +1,14 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021-2022 Alfredo A. Correa +// Copyright 2021-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_ADAPTORS_THRUST_HPP_ -#define MULTI_ADAPTORS_THRUST_HPP_ +#ifndef BOOST_MULTI_ADAPTORS_THRUST_FIX_COPY_HPP_ +#define BOOST_MULTI_ADAPTORS_THRUST_FIX_COPY_HPP_ #pragma once namespace boost::multi { -#if 1 +#if 0 template auto copy_n( boost::multi::elements_iterator_t< Q1* , L1> first, Size count, diff --git a/include/boost/multi/adaptors/thrust/fix_pointer_traits.hpp b/include/boost/multi/adaptors/thrust/fix_pointer_traits.hpp new file mode 100644 index 000000000..867b10251 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/fix_pointer_traits.hpp @@ -0,0 +1,31 @@ +// Copyright 2021-2023 Alfredo A. Correa + +#ifndef BOOST_MULTI_ADAPTORS_THRUST_FIX_POINTER_TRAITS_HPP_ +#define BOOST_MULTI_ADAPTORS_THRUST_FIX_POINTER_TRAITS_HPP_ +#pragma once + +#include + +#include // for std::pointer_traits + +#include + +// #if(__CUDACC_VER_MAJOR__ * 10000 + __CUDACC_VER_MINOR__ * 100 + __CUDACC_VER_BUILD__ < 120500) +// begin of nvcc thrust 11.5 workaround : https://github.com/NVIDIA/thrust/issues/1629 +namespace thrust { + +// template class pointer; +// template struct pointer_traits; + +} // end namespace thrust + +template +struct std::pointer_traits<::thrust::pointer> // NOLINT(cert-dcl58-cpp) normal way to specialize pointer_traits +: ::thrust::detail::pointer_traits> { + template + using rebind = typename ::thrust::detail::pointer_traits<::thrust::pointer>::template rebind::other; +}; +// end of nvcc thrust 11.5 workaround +// #endif + +#endif diff --git a/include/multi/adaptors/thrust/managed_allocator.hpp b/include/boost/multi/adaptors/thrust/managed_allocator.hpp similarity index 98% rename from include/multi/adaptors/thrust/managed_allocator.hpp rename to include/boost/multi/adaptors/thrust/managed_allocator.hpp index ead023e4b..37108fde2 100644 --- a/include/multi/adaptors/thrust/managed_allocator.hpp +++ b/include/boost/multi/adaptors/thrust/managed_allocator.hpp @@ -88,7 +88,7 @@ class managed_allocator { // } return ret; } - [[deprecate]] void deallocate(pointer p, size_type) { + [[deprecated]] void deallocate(pointer p, size_type) { // MULTI_MARK_SCOPE("thrust::managed_deallocate"); // cuda::managed::free(static_cast>(p)); cudaFree(raw_pointer_cast(p)); diff --git a/include/boost/multi/adaptors/thrust/omp.hpp b/include/boost/multi/adaptors/thrust/omp.hpp new file mode 100644 index 000000000..878031237 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/omp.hpp @@ -0,0 +1,89 @@ +// Copyright 2018-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_THRUST_OMP_HPP +#define BOOST_MULTI_ADAPTORS_THRUST_OMP_HPP + +#include +#include // for ::thrust::omp::allocator +#include + +namespace boost::multi::thrust::omp { + template using array = multi::array>; +} // end namespace boost::multi::thrust::omp + +namespace thrust { + +// template struct iterator_system; // not needed in cuda 12.0, doesn't work on cuda 12.5 + +namespace detail { + +// template // ::boost::multi::dimensionality_type D, class Pointer, bool IsConst, bool IsMove, typename Stride> +// struct iterator_system_impl > { +// using type = typename ::thrust::iterator_system::type; +// }; + +} // end namespace detail + +template +struct iterator_system<::boost::multi::array_iterator > { + using type = typename ::thrust::iterator_system::element_ptr>::type; +}; + +template +struct iterator_system<::boost::multi::elements_iterator_t > { // TODO(correaa) might need changes for IsConst templating + using type = typename ::thrust::iterator_system::pointer>::type; +}; + +// namespace detail { +// template +// struct pointer_traits< +// boost::multi::basic_array_ptr< +// boost::multi::subarray, thrust::use_default>, LO>, +// LO +// > +// > +// { +// using Ptr = boost::multi::basic_array_ptr< +// boost::multi::subarray, thrust::use_default>, LO>, +// LO +// >; +// using pointer = Ptr; +// using reference = thrust::tagged_reference; +// typedef typename pointer_element::type element_type; +// typedef typename pointer_difference::type difference_type; + +// template +// struct rebind +// { +// typedef typename rebind_pointer::type other; +// }; + +// // __host__ __device__ +// // inline static pointer pointer_to(typename pointer_traits_detail::pointer_to_param::type r) +// // { +// // // XXX this is supposed to be pointer::pointer_to(&r); (i.e., call a static member function of pointer called pointer_to) +// // // assume that pointer has a constructor from raw pointer instead + +// // return pointer(&r); +// // } + +// // thrust additions follow +// //typedef typename pointer_raw_pointer::type raw_pointer; +// using raw_pointer = boost::multi::basic_array_ptr< +// boost::multi::subarray, +// LO +// >; + +// __host__ __device__ +// inline static raw_pointer get(pointer ptr) +// { +// return reinterpret_cast(ptr); // return ptr.get(); +// } +// }; +// } + +} // end namespace thrust + +#endif // BOOST_MULTI_ADAPTORS_THRUST_OMP_HPP diff --git a/include/boost/multi/adaptors/thrust/omp/test/CMakeLists.txt b/include/boost/multi/adaptors/thrust/omp/test/CMakeLists.txt new file mode 100644 index 000000000..cfa5426d5 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/omp/test/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.18) +project( + boost-multi-adaptor-thrust-omp-test + VERSION 0.1 + LANGUAGES CXX +) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +find_package(Boost CONFIG REQUIRED) # tests require Boost.Core LightweightTest + +find_package(OpenMP) + +if(${CMAKE_CXX_COMPILER_ID} MATCHES "NVHPC") + if(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS_EQUAL "22.11") + message(WARNING "NVHPC 22.11 produces an internal error when compiling OMP code") + return() + endif() +endif() + +if(DISABLE_THRUST_OMP) + message(WARNING "Cannot find Thurst in the system, fetching from CCCL") +else() + if(OpenMP_FOUND) + + find_package(Thrust CONFIG) + if(NOT Thrust_FOUND) + message(WARNING "Cannot find Thurst in the system, fetching from CCCL") + + find_package(Git QUIET) + + if(GIT_FOUND) + message(STATUS "Git command found: ${GIT_EXECUTABLE}") + else() + message(WARNING "Git command not found, won't fetch content.") + return() + endif() + + include(FetchContent) + FetchContent_Declare( + cccl-cmake + GIT_REPOSITORY https://github.com/NVIDIA/cccl.git + GIT_TAG main + GIT_SHALLOW ON + ) + FetchContent_MakeAvailable(cccl-cmake) + + endif() + + include(CTest) + enable_testing() + + thrust_create_target(ThrustOMP DEVICE OMP) + + add_executable(omp.cpp.x omp.cpp) + + target_include_directories(omp.cpp.x SYSTEM PUBLIC ${_THRUST_INCLUDE_DIR}) + target_link_libraries(omp.cpp.x PUBLIC ThrustOMP) + target_link_libraries(omp.cpp.x PUBLIC multi Boost::boost OpenMP::OpenMP_CXX) + + add_test(NAME omp.cpp.x COMMAND $) + else() + message(WARNING "Cannot find OpenMP, will not be tested") + endif() +endif() \ No newline at end of file diff --git a/include/boost/multi/adaptors/thrust/omp/test/omp.cpp b/include/boost/multi/adaptors/thrust/omp/test/omp.cpp new file mode 100644 index 000000000..d7ff6259f --- /dev/null +++ b/include/boost/multi/adaptors/thrust/omp/test/omp.cpp @@ -0,0 +1,220 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // NOLINT(misc-include-cleaner) + +#include // for omp_get_num_threads + +#include + +#ifndef __clang__ +#include +#endif + +// #include // NOLINT(clang-diagnostic-error) +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#ifndef __clang__ +#include +#include +#include +#endif + +#ifdef _MSC_VER +#pragma warning(disable : 5045) // Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified +#endif + +#ifndef __clang__ +namespace { + +template +auto serial_array_sum(Array1D const& arr) { + auto const size = arr.size(); + auto const* const aptr = raw_pointer_cast(arr.data_elements()); + typename Array1D::value_type total = 0.0; + for(typename Array1D::size_type i = 0; i != size; ++i) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + total += aptr[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + return total; +} + +template +auto parallel_array_sum(Array1D const& arr) { + auto const size = arr.size(); + auto const* const aptr = raw_pointer_cast(arr.data_elements()); + typename Array1D::value_type total = 0.0; +#pragma omp parallel for reduction(+ : total) // NOLINT(openmp-use-default-none) + for(typename Array1D::size_type i = 0; i < size; ++i) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + total += aptr[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + return total; +} + +template +auto parallel_idiom_array_sum(Array1D const& arr) { + typename Array1D::value_type total = 0.0; +#if !defined(__NVCOMPILER) && !defined(_MSC_VER) +#pragma omp parallel for reduction(+ : total) // NOLINT(openmp-use-default-none) + for(auto const i : arr.extension()) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + // cppcheck-suppress useStlAlgorithm ; // NOLINTNEXTLINE(clang-analyzer-core.NonNullParamChecker) + total += arr[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } +#elif defined(_MSC_VER) +#pragma omp parallel for reduction(+ : total) // NOLINT(openmp-use-default-none) + for(auto i = arr.extension().front(); i < arr.extension().back() + 1; ++i) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + // NOLINTNEXTLINE(clang-analyzer-core.NonNullParamChecker) + total += arr[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } +#else +#pragma omp parallel for reduction(+ : total) // NOLINT(openmp-use-default-none) + for(auto it = arr.extension().begin(); it < arr.extension().end(); ++it) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + // NOLINTNEXTLINE(clang-analyzer-core.NonNullParamChecker) + total += arr[*it]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } +#endif + return total; +} + +template +auto thrust_array_sum(Array1D const& arr) { + return thrust::reduce(arr.begin(), arr.end(), typename Array1D::value_type{}); +} + +template +auto thrust_omp_array_sum(Array1D const& arr) { + return thrust::reduce(thrust::omp::par, arr.begin(), arr.end(), typename Array1D::value_type{}); +} + +// clang-format off +template +inline +#if defined(_MSC_VER) +__forceinline +#else +__attribute__((always_inline)) +#endif +void DoNotOptimize(Tp const& value) { // NOLINT(readability-identifier-naming) +#if defined(_MSC_VER) + _ReadWriteBarrier(); (void)value; +#else + asm volatile("" : : "r,m"(value) : "memory"); // NOLINT(hicpp-no-assembler) +#endif +} + +template +inline +#if defined(_MSC_VER) +__forceinline +#else +__attribute__((always_inline)) +#endif +void DoNotOptimize(Tp& value) { // NOLINT(readability-identifier-naming) +#if defined(_MSC_VER) + _ReadWriteBarrier(); (void)value; +#else +#if defined(__clang__) + asm volatile("" : "+r,m"(value) : : "memory"); // NOLINT(hicpp-no-assembler) +#else + asm volatile("" : "+m,r"(value) : : "memory"); +#endif +#endif +} +// clang-format on + +} // end namespace +#endif + +// auto parallel_array_sum(int n, float const *a) { +// float total = 0.0; +// #pragma omp parallel for reduction(+:total) +// for (int i = 0; i < n; i++) { +// total += a[i]; +// } +// return total; +// } + +/** + * @brief Solution to the Hello world exercise in OpenMP. + **/ +auto main() -> int { +// 1) Create the OpenMP parallel region +#pragma omp parallel default(none) + { + // 1.1) Get my thread number + int const my_id = omp_get_thread_num(); + + // 1.2) Get the number of threads inside that parallel region + int const thread_number = omp_get_num_threads(); + + // 1.3) Print everything + printf("\"Hello world!\" from thread %d, we are %d threads.\n", my_id, thread_number); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + } + +#ifndef __clang__ + namespace multi = boost::multi; + + multi::thrust::omp::array arr(1U << 20U); + + { +#pragma omp parallel for default(none) shared(arr) + for(int i = 0; i < arr.size(); ++i) { // NOLINT(altera-unroll-loops) + arr[i] = static_cast(i) * static_cast(i); + } + } + + BOOST_TEST( arr[arr.size() - 1] == static_cast(arr.size() - 1)*static_cast(arr.size() - 1) ); + + auto tick = std::chrono::high_resolution_clock::now(); + + auto const serial = serial_array_sum(arr); + DoNotOptimize(serial); + std::cout << "serial " << (std::chrono::high_resolution_clock::now() - tick).count() << '\n'; + + tick = std::chrono::high_resolution_clock::now(); + + auto const parallel = parallel_array_sum(arr); + DoNotOptimize(parallel); + std::cout << "parallel " << (std::chrono::high_resolution_clock::now() - tick).count() << '\n'; + + std::cout << serial << ' ' << parallel << ' ' << serial - parallel << '\n'; + BOOST_TEST( std::abs((serial / parallel) - 1.0) < 1.0e-12 ); + + tick = std::chrono::high_resolution_clock::now(); + + auto const parallel_idiom = parallel_idiom_array_sum(arr); + DoNotOptimize(parallel_idiom); + std::cout << "parallel idiom " << (std::chrono::high_resolution_clock::now() - tick).count() << '\n'; + + BOOST_TEST( std::abs((parallel_idiom / parallel) - 1.0) < 1.0e-12 ); + + tick = std::chrono::high_resolution_clock::now(); + + auto const thrust = thrust_array_sum(arr); + DoNotOptimize(thrust); + std::cout << "thrust " << (std::chrono::high_resolution_clock::now() - tick).count() << '\n'; + + BOOST_TEST( std::abs((thrust / parallel) - 1.0) < 1.0e-12 ); + + tick = std::chrono::high_resolution_clock::now(); + + auto const thrust_omp = thrust_omp_array_sum(arr); + DoNotOptimize(thrust_omp); + std::cout << "thrust omp " << (std::chrono::high_resolution_clock::now() - tick).count() << '\n'; + + BOOST_TEST( std::abs((thrust_omp / parallel) - 1.0) < 1.0e-12 ); + + multi::array const arr_normal{arr}; + DoNotOptimize(arr_normal); + + tick = std::chrono::high_resolution_clock::now(); + + auto const thrust_omp_normal = thrust_omp_array_sum(arr_normal); + DoNotOptimize(thrust_omp_normal); + std::cout << "thrust omp normal " << (std::chrono::high_resolution_clock::now() - tick).count() << '\n'; + BOOST_TEST( std::abs((thrust_omp_normal / parallel) - 1.0) < 1.0e-12 ); +#endif + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/thrust/reduce_by_index.hpp b/include/boost/multi/adaptors/thrust/reduce_by_index.hpp new file mode 100644 index 000000000..eee2564c9 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/reduce_by_index.hpp @@ -0,0 +1,116 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_THRUST_REDUCE_BY_INDEX_HPP_ +#define BOOST_MULTI_ADAPTORS_THRUST_REDUCE_BY_INDEX_HPP_ +#include +#pragma once + +#include +#include +#include + +namespace boost::multi::thrust{ + +namespace detail { +template +struct divide_by { + SizeType divsr; + __host__ __device__ constexpr auto operator()(SizeType divdn) const -> SizeType { return divdn/divsr; } +}; +} + +template +auto reduce_by_index(ExecutionPolicy&& ep, T const& M, S&& sums) -> S&& { + assert(M.extension() == sums.extension()); + + auto const row_ids_begin = + ::thrust::make_transform_iterator( + ::thrust::make_counting_iterator(std::ptrdiff_t{0}), + detail::divide_by{M.elements().size()/M.size()} + ) + ; + auto const row_ids_end = row_ids_begin + M.elements().size(); + + // auto const row_ids_begin = + // thrust::make_transform_iterator( + // M.extensions().elements().begin(), + // [] __host__ __device__ (typename T::indexes e) -> std::ptrdiff_t { using std::get; return get<0>(e); } + // ) + // ; + // auto const row_ids_end = row_ids_begin + M.num_elements(); + + ::thrust::reduce_by_key( + std::forward(ep), + row_ids_begin, row_ids_end, + M.elements().begin(), + ::thrust::make_discard_iterator(), + sums.begin() + ); + + return std::forward(sums); +} + +template +auto reduce_by_index(ExecutionPolicy&& ep, T const& M, S&& sums, BinaryOp&& op) -> S&& { + assert(M.extension() == sums.extension()); + + auto const row_ids_begin = + ::thrust::make_transform_iterator( + ::thrust::make_counting_iterator(std::ptrdiff_t{0}), + detail::divide_by{M.num_elements()/M.size()} + ) + ; + auto const row_ids_end = row_ids_begin + M.num_elements(); + + // auto const row_ids_begin = + // thrust::make_transform_iterator( + // M.extensions().elements().begin(), + // [] __host__ __device__ (typename T::indexes e) -> std::ptrdiff_t { using std::get; return get<0>(e); } + // ) + // ; + // auto const row_ids_end = row_ids_begin + M.num_elements(); + + ::thrust::reduce_by_key( + std::forward(ep), + row_ids_begin, row_ids_end, + M.elements().begin(), + ::thrust::make_discard_iterator(), + sums.begin(), + ::thrust::equal_to{}, + std::forward(op) + ); + + return std::forward(sums); +} + +template +auto reduce_by_index(T const& M, S&& sums) -> S&& { + return reduce_by_index(::thrust::cuda::par, M, std::forward(sums)); +} + +template +auto reduce_by_index(T const& M, S&& sums, BinOp&& op) -> S&& { + return reduce_by_index(::thrust::cuda::par, M, std::forward(sums), std::forward(op)); +} + +template +auto reduce_by_index(T const& M) { + multi::array ret(M[0].extensions(), M.get_allocator()); + return reduce_by_index(M, std::move(ret)); +} + +template< + class T, class BinOp, class TE = typename T::element, + std::enable_if_t>::value> =0, + class = decltype(std::declval()(std::declval(), std::declval())) +> +auto reduce_by_index(T const& M, BinOp&& op) { + multi::array ret(M.layout().sub().extensions(), M.get_allocator()); + return reduce_by_index(M, std::move(ret), std::forward(op)); +} + +} + +#endif // BOOST_MULTI_ADAPTORS_THRUST_REDUCE_BY_INDEX_HPP_ diff --git a/include/multi/adaptors/thrust/reference.hpp b/include/boost/multi/adaptors/thrust/reference.hpp similarity index 65% rename from include/multi/adaptors/thrust/reference.hpp rename to include/boost/multi/adaptors/thrust/reference.hpp index 8ebf98916..a25c7e437 100644 --- a/include/multi/adaptors/thrust/reference.hpp +++ b/include/boost/multi/adaptors/thrust/reference.hpp @@ -1,15 +1,20 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022-2023 Alfredo A. Correa +// Copyright 2022-2024 Alfredo A. Correa #pragma once +#include // hipthrust needs this + +#if defined(__NVCC__) +#define BOOST_MULTI_HD_ __host__ __device__ +#else +#define BOOST_MULTI_HD_ +#endif + namespace thrust { + // TODO(correaa) consider restrict this for universal memory only template - // #ifndef __CUDA_ARCH__ - // [[deprecated]] - // #endif - HD auto operator*( + BOOST_MULTI_HD_ constexpr auto operator*( thrust::tagged_reference const& r1, thrust::tagged_reference const& r2 ) @@ -17,10 +22,7 @@ namespace thrust { return thrust::raw_reference_cast(r1) * thrust::raw_reference_cast(r2); } template - // #ifndef __CUDA_ARCH__ - // [[deprecated]] - // #endif - HD auto operator*( + BOOST_MULTI_HD_ constexpr auto operator*( thrust::tagged_reference const& r1, T2 const& r2 ) @@ -28,10 +30,7 @@ namespace thrust { return thrust::raw_reference_cast(r1) * r2; } template - // #ifndef __CUDA_ARCH__ - // [[deprecated]] - // #endif - HD auto operator*( + BOOST_MULTI_HD_ constexpr auto operator*( T1 const& r1, thrust::tagged_reference const& r2 ) @@ -39,3 +38,5 @@ namespace thrust { return r2 * thrust::raw_reference_cast(r1); } } + +#undef BOOST_MULTI_HD_ diff --git a/include/boost/multi/adaptors/thrust/test/CMakeLists.txt b/include/boost/multi/adaptors/thrust/test/CMakeLists.txt new file mode 100644 index 000000000..8b20e0e78 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/CMakeLists.txt @@ -0,0 +1,96 @@ +cmake_minimum_required(VERSION 3.18) +project( + boost-multi-adaptor-cuda-thrust-test + VERSION 0.1 + LANGUAGES CXX CUDA +) + +if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) +endif() + +# set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --extended-lambda --Werror=cross-execution-space-call -Xcudafe \"--display_error_number\"") + +find_package(Boost) # COMPONENTS boost unit_test_framework) # tests require Boost.Core LightweightTest + +include(CTest) +enable_testing() + +# find_program(MEMORYCHECK_COMMAND valgrind) +# set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --error-exitcode=1") + +find_package(Boost COMPONENTS timer) +if(NOT Boost_FOUND) + message(ERROR " Cannot find Boost.Timer. Try\n sudo apt install libboost-timer-dev # in Debian/Ubuntu") +endif() + +set(TEST_SRCS + array.cu + by_key.cu + diffusion3d_kernel.cu + memory_resource.cu + neighbor_list.cu + run_reduce.cu + set_identity_kernel.cu + speed.cu + speed_algo.cu + universal.cu + vector.cu +) + +foreach(TEST_FILE ${TEST_SRCS}) + set(TEST_EXE "${TEST_FILE}.x") + add_executable(${TEST_EXE} ${TEST_FILE}) + if(ENABLE_CUDA OR DEFINED CXXCUDA) + set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) + set_property(TARGET ${TEST_EXE} PROPERTY CUDA_STANDARD 17) + endif() + + target_include_directories(${TEST_EXE} PUBLIC ../../../../../include) + + target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") + target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) + target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) + target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) + target_link_libraries(${TEST_EXE} PRIVATE multi) + target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) + if(ENABLE_CUDA) + add_test(NAME ${TEST_EXE} COMMAND $) + endif() + + target_compile_options(${TEST_EXE} PRIVATE + $<$: + --diag-suppress=1394 # field of class type without a DLL interface used in a class with a DLL interface + --extended-lambda + #> + $<$: + # /GS # Enables buffer security check (can help detect stack-based buffer overflows) + $<$>: + /W4 # /volatile:iso /WX /Zc:wchar_t /Zc:forScope /Zc:inline + > + $<$: + --diag-suppress=1394 # field of class type without a DLL interface used in a class with a DLL interface + -Xcompiler=/W4 + -Xcompiler=/wd4061 # enumerator 'cudaErrorSystemNotReady' in switch of enum 'cudaError' is not explicitly handled by a case label + -Xcompiler=/wd4324 # structure was padded due to alignment specifier + -Xcompiler=/wd4464 + -Xcompiler=/wd4664 + -Xcompiler=/wd4668 + > + > + > + ) +endforeach() + +set_tests_properties(speed.cu.x PROPERTIES RUN_SERIAL TRUE) + +# target_compile_options(array.cu.x PRIVATE $<$:$<$:-Xcompiler=/wd4668 -Xcompiler=/wd4664 -Xcompiler=/wd4464 > --extended-lambda >) +# target_compile_options(by_key.cu.x PRIVATE $<$:$<$:-Xcompiler=/wd4668 -Xcompiler=/wd4664 -Xcompiler=/wd4464 > >) +# target_compile_options(run_reduce.cu.x PRIVATE $<$:--extended-lambda >) +# target_compile_options(speed.cu.x PRIVATE $<$:--extended-lambda >) + +# target_compile_features(neighbor_list.cu.x PRIVATE cuda_std_20 cxx_std_20) diff --git a/include/boost/multi/adaptors/thrust/test/array.cu b/include/boost/multi/adaptors/thrust/test/array.cu new file mode 100644 index 000000000..dc68beba4 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/array.cu @@ -0,0 +1,1073 @@ +// Copyright 2021-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// #include + +#include +#include + +namespace multi = boost::multi; + +#ifdef __NVCC__ +template<> +inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::std::complex> = true; +template<> +inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::std::complex> = true; +template<> +inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::thrust::complex> = true; +template<> +inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::thrust::complex> = true; +#else // vvv nvcc (12.1?) doesn't support this kind of customization: "error: expected initializer before ‘<’" +template +inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::std::complex> = std::is_trivially_default_constructible::value; +template +inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::thrust::complex> = std::is_trivially_default_constructible::value; +#endif + +namespace { + +template using test_allocator = + // multi::thrust::cuda::managed_allocator + thrust::cuda::allocator; +} + +class auto_timer : std::chrono::high_resolution_clock { + std::string label_; + time_point start_; + + public: + explicit auto_timer(char const* label) : label_{label} { + cudaDeviceSynchronize() == cudaSuccess ? void() : assert(0); // NOLINT(misc-include-cleaner) the header is included conditionally + start_ = now(); + } + + auto_timer(auto_timer const&) = delete; + auto_timer(auto_timer&&) = delete; + + auto operator=(auto_timer const&) -> auto_timer& = delete; + auto operator=(auto_timer&&) -> auto_timer& = delete; + + auto_timer() : auto_timer("") {} + auto elapsed() const { + cudaDeviceSynchronize() == cudaSuccess ? void() : assert(0); + struct { + long long wall; + } ret{std::chrono::duration_cast(now() - start_).count()}; + return ret; + } + // ~auto_timer() { + // cudaDeviceSynchronize() == cudaSuccess ? void() : assert(0); + // auto const count = std::chrono::duration(now() - start_).count(); + // std::cerr << label_ << ": " << count << " sec\n"; + // } +}; + +auto universal_memory_supported() -> bool { + std::cout << "testing for universal memory supported" << std::endl; + int d; + cudaGetDevice(&d); + int is_cma = 0; + cudaDeviceGetAttribute(&is_cma, cudaDevAttrConcurrentManagedAccess, d); + if(is_cma) { + std::cout << "universal memory is supported" << std::endl; + } else { + std::cout << "universal memory is NOT supported" << std::endl; + } + return (is_cma == 1) ? true : false; +} + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + + // BOOST_AUTO_TEST_CASE(cuda_allocators) + { + + multi::array> A1(200, 0.0); + + BOOST_TEST( size(A1) == 200 ); + A1[100] = 1.0; + + multi::array> const B1(200, 2.0); + BOOST_TEST( B1[10] == 2.0 ); + + A1[10] = B1[10]; + BOOST_TEST( A1[10] == 2.0 ); + } + + // BOOST_AUTO_TEST_CASE(cuda_universal_empty) + if(universal_memory_supported()) { + using complex = thrust::complex; + multi::array> A; + multi::array> B = A; + BOOST_TEST( A.is_empty() ); + BOOST_TEST( B.is_empty() ); + BOOST_TEST( A == B ); + } + + // BOOST_AUTO_TEST_CASE(cuda_1d_initlist) + { + multi::array> A1 = {1.0, 2.0, 3.0}; + BOOST_TEST( A1.size() == 3 ); + + // BOOST_TEST( size(A1) == 200 ); + // A1[100] = 1.0; + + // multi::array> const B1(200, 2.0); + // BOOST_TEST( B1[10] == 2.0 ); + + // A1[10] = B1[10]; + // BOOST_TEST( A1[10] == 2.0 ); + + { + thrust::device_vector> vA(3, 44); + thrust::device_vector> vB(3, 0); + + thrust::copy(thrust::cuda::par, vA.begin(), vA.end(), vB.begin()); + + static_assert(std::is_same_v>); + + thrust::copy(thrust::cuda::par, vA.data(), vA.data() + vA.size(), vB.begin()); + + BOOST_TEST( vB[0] == 44 ); + } + { + thrust::device_allocator devalloc; + + auto Aptr = devalloc.allocate(3); + + thrust::fill(thrust::cuda::par, Aptr, Aptr + 3, 44); + + auto Bptr = devalloc.allocate(3); + + thrust::fill(thrust::cuda::par, Bptr, Bptr + 3, 0); + + thrust::copy(thrust::cuda::par, Aptr, Aptr + 3, Bptr); + + devalloc.deallocate(Aptr, 3); + devalloc.deallocate(Bptr, 3); + } + { + thrust::device_allocator devalloc; + + auto Aptr = devalloc.allocate(3); + + thrust::uninitialized_fill(thrust::cuda::par, Aptr, Aptr + 3, 44); + + auto Bptr = devalloc.allocate(3); + + thrust::uninitialized_fill(thrust::cuda::par, Bptr, Bptr + 3, 44); + + multi::array_ref> aA(Aptr, 3); + + multi::array_ref> aB(Bptr, 3); + + thrust::copy_n(thrust::cuda::par, Aptr, aA.size(), Bptr); + + BOOST_TEST( Aptr == aA.data_elements() ); + BOOST_TEST( Bptr == aB.data_elements() ); + + thrust::copy_n(thrust::cuda::par, aA.data_elements(), aA.size(), aB.data_elements()); + + auto Ait = multi::array_iterator>(Aptr, 1); + + static_assert(std::is_same_v< + ::thrust::iterator_system::type, + ::thrust::iterator_system>::type>); + + thrust::copy_n(Ait, aA.size(), aB.data_elements()); + + thrust::copy_n(thrust::cuda::par, Ait, aA.size(), aB.data_elements()); + + thrust::copy_n(thrust::cuda::par, aA.begin(), aA.size(), aB.data_elements()); + + thrust::copy_n(thrust::cuda::par, aA.data_elements(), aA.size(), aB.begin()); + + static_assert(std::is_same_v>); + + thrust::copy(thrust::cuda::par, aA.data_elements(), aA.data_elements() + aA.size(), aB.begin()); + + thrust::copy(thrust::cuda::par, aA.begin(), aA.end(), aB.begin()); + + BOOST_TEST( aB[0] == 44 ); + } + { + multi::array> A(3, 44); + multi::array> B(3, 0); + + BOOST_TEST( A[1] == 44 ); + + thrust::transform(A.data_elements(), A.data_elements() + A.num_elements(), B.data_elements(), [] __device__(int const& elem) -> int { return elem * 2; }); + + B[0] = A[0] * 2; + + thrust::copy(A.begin(), A.end(), B.begin()); + + thrust::transform(A.begin(), A.end(), B.begin(), [] __device__(int const& elem) -> int { return elem * 2; }); + + int B1 = B[1]; + BOOST_TEST( B1 == 88 ); + + BOOST_TEST( B[1] == 88 ); + } + { + multi::array> A = {1, 2, 3}; + multi::array> B(3, 0); + + BOOST_TEST( A[1] == 2 ); + + thrust::transform(A.begin(), A.end(), B.begin(), [] __device__(int elem) { return elem * 2; }); + + int B1 = B[1]; + BOOST_TEST( B1 == 4 ); + + BOOST_TEST( B[1] == 4 ); + } + { + multi::array> A = {1.0, 2.0, 3.0}; + multi::array> B(3); + + // // for(int i = 0; i != A.size(); ++i) { B[i] = A[i]*2.0; } + // // for(auto i : A.extension()) { B[i] = A[i]*2.0; } + + thrust::transform(A.begin(), A.end(), B.begin(), [] __device__(double const& elem) { return elem * 2.0; }); + + BOOST_TEST( B[1] == 4.0 ); + } + } + + // BOOST_AUTO_TEST_CASE(test_univ_alloc) + if(universal_memory_supported()) { + multi::array> Dev({128, 128}); + *raw_pointer_cast(Dev.base()) = 99.0; + } + + // BOOST_AUTO_TEST_CASE(mtc_universal_array) + if(universal_memory_supported()) { + multi::thrust::cuda::universal_array Dev({128, 128}); + *raw_pointer_cast(Dev.base()) = 99.0; + } + + // BOOST_AUTO_TEST_CASE(mtc_universal_coloncolon_array) + if(universal_memory_supported()) { + multi::thrust::cuda::universal::array Dev({128, 128}); + *raw_pointer_cast(Dev.base()) = 99.0; + } + + // BOOST_AUTO_TEST_CASE(test_alloc) + { + multi::array> Dev({128, 128}); + // *raw_pointer_cast(Dev.base()) = 99.0; // segmentation fault (correct behavior) + } + +#ifdef NDEBUG + + auto const n = 1024; + + // BOOST_AUTO_TEST_CASE(thrust_copy_1D_issue123_double) + { // BOOST_AUTO_TEST_CASE(fdfdfdsfds) { using T = char; + using T = double; + + static_assert(std::is_trivially_default_constructible_v); + static_assert(std::is_trivially_copy_constructible_v); + static_assert(std::is_trivially_assignable_v); + + multi::array> Devc(multi::extensions_t<1>{n * n}); + multi::array> Dev2(multi::extensions_t<1>{n * n}); + multi::array Host(multi::extensions_t<1>{n * n}); + std::iota(Host.elements().begin(), Host.elements().end(), 12.0); + multi::array Hos2(multi::extensions_t<1>{n * n}); + + std::cout << "| 1D `" << typeid(T).name() << "` total data size: " << Host.num_elements() * sizeof(T) / 1073741824. << " GB | speed |\n|---|---|" << std::endl; + { + Devc = Host; + } + { + auto_timer t{""}; + Devc = Host; + cudaDeviceSynchronize(); + std::cout << "| contiguous host -> devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + Devc.sliced(0, n * n / 2) = Host.sliced(0, n * n / 2); + cudaDeviceSynchronize(); + std::cout << "| sliced host -> devc | " << Host.sliced(0, n * n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + Devc.strided(2) = Host.strided(2); + cudaDeviceSynchronize(); + std::cout << "| strided host -> devc | " << Host.strided(2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + Hos2 = Devc; + cudaDeviceSynchronize(); + std::cout << "| contiguous devc -> host | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Hos2 == Host ); + } + { + auto_timer t{""}; + Hos2.sliced(0, n * n / 2) = Devc.sliced(0, n * n / 2); + cudaDeviceSynchronize(); + std::cout << "| sliced devc -> host | " << Host.sliced(0, n * n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Hos2 == Host ); + } + { + auto_timer t{""}; + + Hos2.strided(2) = Devc.strided(2); + cudaDeviceSynchronize(); + std::cout << "| strided devc -> host | " << Host.strided(2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Hos2 == Host ); + } + { + auto_timer t{""}; + + Dev2 = Devc; + cudaDeviceSynchronize(); + std::cout << "| contiguous devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + auto Dev3 = Devc; + cudaDeviceSynchronize(); + std::cout << "| copy_ctr devc -> devc | " << Devc.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev3 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2.sliced(0, n * n / 2) = Devc.sliced(0, n * n / 2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| sliced devc -> devc | " << Dev2.sliced(0, n * n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2.strided(2) = Devc.strided(2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| strided devc -> devc | " << Dev2.strided(2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Hos2 = Host; + cudaDeviceSynchronize(); + std::cout << "| contiguous host -> host | " << Hos2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2.sliced(0, n * n / 2) = Host.sliced(0, n * n / 2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| sliced host -> host | " << Hos2.sliced(0, n * n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2.strided(2) = Host.strided(2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| strided host -> host | " << Hos2.strided(2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + } + + // BOOST_AUTO_TEST_CASE(thrust_copy_1D_issue123_complex) + { + using T = thrust::complex; + + // static_assert(multi::is_trivially_default_constructible{}); + // static_assert(std::is_trivially_copy_constructible_v); + // static_assert(std::is_trivially_assignable_v); + + multi::array> Devc(multi::extensions_t<1>{n * n}); + multi::array> Dev2(multi::extensions_t<1>{n * n}); + multi::array Host(multi::extensions_t<1>{n * n}); + std::iota(Host.elements().begin(), Host.elements().end(), 12.); + multi::array Hos2(multi::extensions_t<1>{n * n}); + + std::cout << "| 1D `" << typeid(T).name() << "` total data size: " << Host.num_elements() * sizeof(T) / 1073741824.0 << " GB | speed |\n|---|---|" << std::endl; + { + Devc = Host; + } + { + auto_timer t{""}; + Devc = Host; + cudaDeviceSynchronize(); + std::cout << "| contiguous host -> devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc.sliced(0, n * n / 2) = Host.sliced(0, n * n / 2); + cudaDeviceSynchronize(); + std::cout << "| sliced host -> devc | " << Host.sliced(0, n * n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc.strided(2) = Host.strided(2); + cudaDeviceSynchronize(); + std::cout << "| strided host -> devc | " << Host.strided(2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2 = Devc; + cudaDeviceSynchronize(); + std::cout << "| contiguous devc -> host | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Hos2 == Host ); + } + { + auto_timer t{""}; + + Hos2.sliced(0, n * n / 2) = Devc.sliced(0, n * n / 2); + cudaDeviceSynchronize(); + std::cout << "| sliced devc -> host | " << Host.sliced(0, n * n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Hos2 == Host ); + } + { + auto_timer t{""}; + + Hos2.strided(2) = Devc.strided(2); + cudaDeviceSynchronize(); + std::cout << "| strided devc -> host | " << Host.strided(2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Hos2 == Host ); + } + { + auto_timer t{""}; + + Dev2 = Devc; + cudaDeviceSynchronize(); + std::cout << "| contiguous devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + auto Dev3 = Devc; + cudaDeviceSynchronize(); + std::cout << "| copy_ctr devc -> devc | " << Devc.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev3 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2.sliced(0, n * n / 2) = Devc.sliced(0, n * n / 2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| sliced devc -> devc | " << Dev2.sliced(0, n * n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2.strided(2) = Devc.strided(2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| strided devc -> devc | " << Dev2.strided(2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Hos2 = Host; + cudaDeviceSynchronize(); + std::cout << "| contiguous host -> host | " << Hos2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2.sliced(0, n * n / 2) = Host.sliced(0, n * n / 2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| sliced host -> host | " << Hos2.sliced(0, n * n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2.strided(2) = Host.strided(2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| strided host -> host | " << Hos2.strided(2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + std::cout << " " << std::endl; + } + + // BOOST_AUTO_TEST_CASE(thrust_cpugpu_2D_issue123_double) + { + using T = double; + + auto const exts = multi::extensions_t<2>({n, n}); + + std::cout << "| 2D `" << typeid(T).name() << "` max data size " << exts.num_elements() * sizeof(T) / 1073741824.0 << " GB | speed |\n|---|---|" << std::endl; + + multi::array> Devc(exts); + multi::array> Dev2(exts); + + multi::array Host(exts); + std::iota(Host.elements().begin(), Host.elements().end(), 12.); + multi::array Hos2(exts); + + { + Devc({0, n / 2}, {0, n / 2}) = Host({0, n / 2}, {0, n / 2}); // 0.002859s + } + { + auto_timer t{""}; + + Devc = Host; + std::cout << "| contiguous host to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc.sliced(0, n / 2) = Host.sliced(0, n / 2); // 0.005292s + std::cout << "| sliced host to devc | " << Host.sliced(0, n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc({0, n / 2}, {0, n / 2}) = Host({0, n / 2}, {0, n / 2}); // 0.002859s + std::cout << "| strided host to devc | " << Host({0, n / 2}, {0, n / 2}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2 = Devc; + std::cout << "| contiguous devc to host | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Hos2.sliced(0, n / 2) = Devc.sliced(0, n / 2); // 0.005292s + std::cout << "| sliced devc to host | " << Host.sliced(0, n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Hos2({0, n / 2}, {0, n / 2}) = Devc({0, n / 2}, {0, n / 2}); // 0.002859s + std::cout << "| strided devc to host | " << Host({0, n / 2}, {0, n / 2}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Dev2 = Devc; + cudaDeviceSynchronize(); + std::cout << "| contiguous devc to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + auto Dev3 = Devc; + cudaDeviceSynchronize(); + std::cout << "| copy_ctr devc -> devc | " << Devc.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev3 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + auto Dev3 = Devc; + cudaDeviceSynchronize(); + std::cout << "| copy_ctr devc to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2.sliced(0, n / 2) = Devc.sliced(0, n / 2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| sliced devc to devc | " << Host.sliced(0, n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2({0, n / 2}, {0, n / 2}) = Devc({0, n / 2}, {0, n / 2}); // 0.002859s + cudaDeviceSynchronize(); + std::cout << "| strided devc to devc | " << Host({0, n / 2}, {0, n / 2}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Hos2 = Host; + std::cout << "| contiguous host to host | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2.sliced(0, n / 2) = Host.sliced(0, n / 2); // 0.005292s + std::cout << "| sliced host to host | " << Host.sliced(0, n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2({0, n / 2}, {0, n / 2}) = Host({0, n / 2}, {0, n / 2}); // 0.002859s + std::cout << "| strided host to host | " << Host({0, n / 2}, {0, n / 2}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + std::cout << " " << std::endl; + } + + // BOOST_AUTO_TEST_CASE(thrust_cpugpu_2D_issue123_complex) + { + using T = thrust::complex; + + auto const exts = multi::extensions_t<2>({n, n}); + + std::cout << "| 2D `" << typeid(T).name() << "` max data size " << exts.num_elements() * sizeof(T) / 1073741824.0 << " GB | speed |\n|---|---|" << std::endl; + + multi::array> Devc(exts); + multi::array> Dev2(exts); + + multi::array Host(exts); + std::iota(Host.elements().begin(), Host.elements().end(), 12.); + multi::array Hos2(exts); + + { + Devc({0, n / 2}, {0, n / 2}) = Host({0, n / 2}, {0, n / 2}); // 0.002859s + } + { + auto_timer t{""}; + Devc = Host; + std::cout << "| contiguous host to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc.sliced(0, n / 2) = Host.sliced(0, n / 2); // 0.005292s + std::cout << "| sliced host to devc | " << Host.sliced(0, n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc({0, n / 2}, {0, n / 2}) = Host({0, n / 2}, {0, n / 2}); // 0.002859s + std::cout << "| strided host to devc | " << Host({0, n / 2}, {0, n / 2}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824.0 << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2 = Devc; + std::cout << "| contiguous devc to host | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Hos2.sliced(0, n / 2) = Devc.sliced(0, n / 2); // 0.005292s + std::cout << "| sliced devc to host | " << Host.sliced(0, n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Hos2({0, n / 2}, {0, n / 2}) = Devc({0, n / 2}, {0, n / 2}); // 0.002859s + std::cout << "| strided devc to host | " << Host({0, n / 2}, {0, n / 2}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Dev2 = Devc; + cudaDeviceSynchronize(); + std::cout << "| contiguous devc to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + auto Dev3 = Devc; + cudaDeviceSynchronize(); + std::cout << "| copy_ctr devc -> devc | " << Devc.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev3 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + auto Dev3 = Devc; + cudaDeviceSynchronize(); + std::cout << "| copy_ctr devc to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2.sliced(0, n / 2) = Devc.sliced(0, n / 2); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| sliced devc to devc | " << Host.sliced(0, n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2({0, n / 2}, {0, n / 2}) = Devc({0, n / 2}, {0, n / 2}); // 0.002859s + cudaDeviceSynchronize(); + std::cout << "| strided devc to devc | " << Host({0, n / 2}, {0, n / 2}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Hos2 = Host; + std::cout << "| contiguous host to host | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + Hos2.sliced(0, n / 2) = Host.sliced(0, n / 2); // 0.005292s + std::cout << "| sliced host to host | " << Host.sliced(0, n / 2).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2({0, n / 2}, {0, n / 2}) = Host({0, n / 2}, {0, n / 2}); // 0.002859s + std::cout << "| strided host to host | " << Host({0, n / 2}, {0, n / 2}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + std::cout << " " << std::endl; + } + + // BOOST_AUTO_TEST_CASE(thrust_cpugpu_issue123_3D_double) + { + using T = double; + auto const exts = multi::extensions_t<3>({1024, 1024, 100}); + + std::cout << "| 3D `" << typeid(T).name() << "` max data size " << exts.num_elements() * sizeof(T) / 1073741824. << " GB | speed |\n|---|---|" << std::endl; + + multi::array> Devc(exts); + multi::array> Dev2(exts); + multi::array Host(exts); + std::iota(Host.elements().begin(), Host.elements().end(), 12.); + multi::array Hos2(exts); + + { + Devc({0, 512}, {0, 512}, {0, 512}) = Host({0, 512}, {0, 512}, {0, 512}); // 0.002859s + } + { + auto_timer t{""}; + + Devc = Host; + std::cout << "| contiguous host to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << " GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc.sliced(0, 512) = Host.sliced(0, 512); // 0.005292s + std::cout << "| sliced host to devc | " << Host.sliced(0, 512).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << " GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc({0, 512}, {0, 512}, {0, 512}) = Host({0, 512}, {0, 512}, {0, 512}); // 0.002859s + std::cout << "| strided host to devc | " << Host({0, 512}, {0, 512}, {0, 512}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2 = Devc; + std::cout << "| contiguous devc to host | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Hos2.sliced(0, 512) = Devc.sliced(0, 512); // 0.005292s + std::cout << "| sliced devc to host | " << Host.sliced(0, 512).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Hos2({0, 512}, {0, 512}, {0, 512}) = Devc({0, 512}, {0, 512}, {0, 512}); // 0.002859s + std::cout << "| strided devc to host | " << Host({0, 512}, {0, 512}, {0, 512}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Dev2 = Devc; + cudaDeviceSynchronize(); + std::cout << "| contiguous devc to devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Dev2 == Devc); + } + { + auto_timer t{""}; + + auto Dev3 = Devc; + cudaDeviceSynchronize(); + std::cout << "| copy_ctr devc -> devc | " << Devc.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev3 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2.sliced(0, 512) = Devc.sliced(0, 512); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| sliced devc to devc | " << Dev2.sliced(0, 512).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Dev2 == Devc); + } + { + auto_timer t{""}; + + Dev2({0, 512}, {0, 512}, {0, 512}) = Devc({0, 512}, {0, 512}, {0, 512}); // 0.002859s + cudaDeviceSynchronize(); + std::cout << "| strided devc to devc | " << Dev2({0, 512}, {0, 512}, {0, 512}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Dev2 == Devc); + } + { + auto_timer t{""}; + + Hos2 = Host; + std::cout << "| contiguous host to host | " << Hos2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2.sliced(0, 512) = Host.sliced(0, 512); // 0.005292s + std::cout << "| sliced host to host | " << Hos2.sliced(0, 512).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2({0, 512}, {0, 512}, {0, 512}) = Host({0, 512}, {0, 512}, {0, 512}); // 0.002859s + std::cout << "| strided host to host | " << Hos2({0, 512}, {0, 512}, {0, 512}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + std::cout << " " << std::endl; + } + + // BOOST_AUTO_TEST_CASE(thrust_cpugpu_issue123_3D_complex) + { + using T = thrust::complex; + auto const exts = multi::extensions_t<3>({1024, 1024, 100}); + + std::cout << "| 3D `" << typeid(T).name() << "` max data size " << exts.num_elements() * sizeof(T) / 1073741824. << " GB | speed |\n|---|---|" << std::endl; + + multi::array> Devc(exts); + multi::array> Dev2(exts); + multi::array Host(exts); + std::iota(Host.elements().begin(), Host.elements().end(), 12.); + multi::array Hos2(exts); + + { + Devc({0, 512}, {0, 512}, {0, 512}) = Host({0, 512}, {0, 512}, {0, 512}); // 0.002859s + } + { + auto_timer t{""}; + + Devc = Host; + std::cout << "| contiguous host to devc | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << " GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc.sliced(0, 512) = Host.sliced(0, 512); // 0.005292s + std::cout << "| sliced host to devc | " << Host.sliced(0, 512).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << " GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Devc({0, 512}, {0, 512}, {0, 512}) = Host({0, 512}, {0, 512}, {0, 512}); // 0.002859s + std::cout << "| strided host to devc | " << Host({0, 512}, {0, 512}, {0, 512}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2 = Devc; + std::cout << "| contiguous devc to host | " << Host.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Hos2.sliced(0, 512) = Devc.sliced(0, 512); // 0.005292s + std::cout << "| sliced devc to host | " << Host.sliced(0, 512).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Hos2({0, 512}, {0, 512}, {0, 512}) = Devc({0, 512}, {0, 512}, {0, 512}); // 0.002859s + std::cout << "| strided devc to host | " << Host({0, 512}, {0, 512}, {0, 512}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Hos2 == Host); + } + { + auto_timer t{""}; + + Dev2 = Devc; + cudaDeviceSynchronize(); + std::cout << "| contiguous devc to devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Dev2 == Devc); + } + { + auto_timer t{""}; + + auto Dev3 = Devc; + cudaDeviceSynchronize(); + std::cout << "| copy_ctr devc -> devc | " << Devc.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev3 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + cudaMemcpy(raw_pointer_cast(Dev2.data_elements()), raw_pointer_cast(Devc.data_elements()), Devc.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::cout << "| cudaMemcpy devc -> devc | " << Dev2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST( Dev2 == Devc ); + } + { + auto_timer t{""}; + + Dev2.sliced(0, 512) = Devc.sliced(0, 512); // 0.005292s + cudaDeviceSynchronize(); + std::cout << "| sliced devc to devc | " << Dev2.sliced(0, 512).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Dev2 == Devc); + } + { + auto_timer t{""}; + + Dev2({0, 512}, {0, 512}, {0, 512}) = Devc({0, 512}, {0, 512}, {0, 512}); // 0.002859s + cudaDeviceSynchronize(); + std::cout << "| strided devc to devc | " << Dev2({0, 512}, {0, 512}, {0, 512}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + // BOOST_TEST(Dev2 == Devc); + } + { + auto_timer t{""}; + + Hos2 = Host; + std::cout << "| contiguous host to host | " << Hos2.num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2.sliced(0, 512) = Host.sliced(0, 512); // 0.005292s + std::cout << "| sliced host to host | " << Hos2.sliced(0, 512).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + { + auto_timer t{""}; + + Hos2({0, 512}, {0, 512}, {0, 512}) = Host({0, 512}, {0, 512}, {0, 512}); // 0.002859s + std::cout << "| strided host to host | " << Hos2({0, 512}, {0, 512}, {0, 512}).num_elements() * sizeof(T) / (t.elapsed().wall / 1e9) / 1073741824. << "GB/sec |" << std::endl; + } + std::cout << " " << std::endl; + } +#endif + + { + multi::array> const arr = [] __host__ __device__(multi::index i, multi::index j) { return i + j; } ^ multi::extensions_t(10, 10); + BOOST_TEST( arr[3][4] == 3 + 4 ); + } + { + multi::array> const arr = [](multi::index i, multi::index j) constexpr { return i + j; } ^ multi::extensions_t(10, 10); + BOOST_TEST( arr[3][4] == 3 + 4 ); + } + { + multi::array> const arr = [](multi::index i, multi::index j) { return i + j; } ^ multi::extensions_t(10, 10); + BOOST_TEST( arr[3][4] == 3 + 4 ); + } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/thrust/test/by_key.cu b/include/boost/multi/adaptors/thrust/test/by_key.cu new file mode 100644 index 000000000..ef6833fc9 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/by_key.cu @@ -0,0 +1,85 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include +#include + +#include +#include + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + + multi::thrust::universal_array M = { + {15.0, 6.0, 18.0, 15.0}, + {29.0, 26.0, 24.0, 10.0}, + { 1.0, 4.0, 12.0, 8.0}, + { 8.0, 15.0, 1.0, 14.0}, + {26.0, 5.0, 12.0, 25.0}, + {13.0, 14.0, 23.0, 25.0}, + {20.0, 23.0, 19.0, 18.0}, + {11.0, 14.0, 3.0, 12.0} + }; + + BOOST_TEST( M.size() == 8 ); + BOOST_TEST( (~M).size() == 4 ); + + + using std::get; + std::cout + << get<0>(M.extensions()[1][1]) << ' ' + << get<1>(M.extensions()[1][1]) << '\n' + ; + + BOOST_TEST(true); + // M.extensions().elements(); + + auto row_ids_begin_ref = + thrust::make_transform_iterator( + M.extensions().elements().begin(), + [] __host__ __device__ (decltype(M)::indexes e) -> std::ptrdiff_t { using std::get; return get<0>(e); } + ) + ; + + auto row_ids_begin = + thrust::make_transform_iterator( + thrust::make_counting_iterator(std::ptrdiff_t{0}), + [] __host__ __device__ (std::ptrdiff_t e) -> std::ptrdiff_t { return e / 4; } // std::get; return get<0>(e); } + ) + ; + auto row_ids_end = row_ids_begin + M.num_elements(); + + auto row_ids_end_ref = row_ids_begin_ref + M.num_elements(); + + BOOST_TEST( thrust::equal(row_ids_begin, row_ids_end, row_ids_begin_ref) ); // , row_ids_end_ref) ); + BOOST_TEST( thrust::equal(row_ids_begin+1, row_ids_end, row_ids_begin_ref+1) ); // row_ids_end_ref) ); + + BOOST_TEST( row_ids_end - row_ids_begin == M.num_elements() ); + + multi::thrust::universal_array sums(M.size()); + + thrust::reduce_by_key(thrust::cuda::par, + row_ids_begin_ref, row_ids_end_ref, + M.elements().begin(), + thrust::make_discard_iterator(), + sums.begin() + ); + + // thrust::reduce_by_key( + // thrust::make_counting_iterator(0), + // thrust::make_counting_iterator(M.size()), + // M.data(), + // thrust::make_discard_iterator(), + // sums.data() + // ); + + BOOST_TEST( sums[0] == M[0][0] + M[0][1] + M[0][2] + M[0][3] ); + BOOST_TEST( sums[1] == M[1][0] + M[1][1] + M[1][2] + M[1][3] ); + + return boost::report_errors(); +} diff --git a/include/multi/adaptors/thrust/test/device_vector.cu b/include/boost/multi/adaptors/thrust/test/device_vector.cu similarity index 57% rename from include/multi/adaptors/thrust/test/device_vector.cu rename to include/boost/multi/adaptors/thrust/test/device_vector.cu index 6c2422245..8cf38843b 100644 --- a/include/multi/adaptors/thrust/test/device_vector.cu +++ b/include/boost/multi/adaptors/thrust/test/device_vector.cu @@ -1,10 +1,10 @@ #include int main(){ -// thrust::device_vector D(5); -// assert( D.size() == 5 ); - -// cudaDeviceSynchronize(); +// thrust::device_vector D(5); +// assert( D.size() == 5 ); +sasa; +// cudaDeviceSynchronize(); std::allocator alloc; int* p = alloc.allocate(10); p[0] = 2; diff --git a/include/boost/multi/adaptors/thrust/test/diffusion3d_kernel.cu b/include/boost/multi/adaptors/thrust/test/diffusion3d_kernel.cu new file mode 100644 index 000000000..cc86cd3f5 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/diffusion3d_kernel.cu @@ -0,0 +1,165 @@ +#define ENABLE_GPU 1 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +template +inline +#if defined(_MSC_VER) + __forceinline +#else + __attribute__((always_inline)) +#endif + void + DoNotOptimize(Tp const& value) { // NOLINT(readability-identifier-naming) +#if defined(_MSC_VER) + _ReadWriteBarrier(); + (void)value; +#else + asm volatile("" : : "r,m"(value) : "memory"); // NOLINT(hicpp-no-assembler) +#endif +} + +// A simple macro for checking CUDA API calls +#define checkCudaErrors(val) check((val), #val, __FILE__, __LINE__) +void check(cudaError_t result, char const* const func, char const* const file, int const line) { + if(result) { + fprintf(stderr, "CUDA error at %s:%d code=%d(%s) \"%s\" \n", file, line, static_cast(result), cudaGetErrorName(result), func); + exit(99); + } +} + +// The 3D diffusion kernel to be tested. +__global__ void diffusion3d_gpu(int nx, int ny, int nz, float cc, float ce, float cw, float cn, float cs, float ct, float cb, float const* f, float* fn) { + int const i = blockIdx.x * blockDim.x + threadIdx.x; + int const j = blockIdx.y * blockDim.y + threadIdx.y; + int const k = blockIdx.z * blockDim.z + threadIdx.z; + + if(i < nx && j < ny && k < nz) { + int const ix = nx * ny * k + nx * j + i; + int const ip = i == nx - 1 ? ix : ix + 1; + int const im = i == 0 ? ix : ix - 1; + int const jp = j == ny - 1 ? ix : ix + nx; + int const jm = j == 0 ? ix : ix - nx; + int const kp = k == nz - 1 ? ix : ix + nx * ny; + int const km = k == 0 ? ix : ix - nx * ny; + + fn[ix] = cc * f[ix] + ce * f[ip] + cw * f[im] + cn * f[jp] + cs * f[jm] + ct * f[kp] + cb * f[km]; + } +} + +// A CPU version of the function for verification purposes. +void diffusion3d_cpu(int nx, int ny, int nz, float cc, float ce, float cw, float cn, float cs, float ct, float cb, float const* f, float* fn_cpu) { + for(int k = 0; k < nz; k++) { + for(int j = 0; j < ny; j++) { + for(int i = 0; i < nx; i++) { + int const ix = nx * ny * k + nx * j + i; + int const ip = i == nx - 1 ? ix : ix + 1; + int const im = i == 0 ? ix : ix - 1; + int const jp = j == ny - 1 ? ix : ix + nx; + int const jm = j == 0 ? ix : ix - nx; + int const kp = k == nz - 1 ? ix : ix + nx * ny; + int const km = k == 0 ? ix : ix - nx * ny; + + fn_cpu[ix] = cc * f[ix] + ce * f[ip] + cw * f[im] + cn * f[jp] + cs * f[jm] + ct * f[kp] + cb * f[km]; + } + } + } +} + +auto main() -> int { + // Problem dimensions + const int nx = 128; + const int ny = 128; + const int nz = 128; + { + const int arraySize = nx * ny * nz; + const int memSize = arraySize * sizeof(float); + + // Host pointers + float *f_h, *fn_h, *fn_cpu; + + // Device pointers + float *f_d, *fn_d; + + // Allocate host memory + f_h = (float*)malloc(memSize); + fn_h = (float*)malloc(memSize); + fn_cpu = (float*)malloc(memSize); + + // Initialize host input data + for (int i = 0; i < arraySize; i++) { + f_h[i] = (float)i; + } + + // Allocate device memory and copy data from host to device + checkCudaErrors(cudaMalloc((void**)&f_d, memSize)); + checkCudaErrors(cudaMalloc((void**)&fn_d, memSize)); + checkCudaErrors(cudaMemcpy(f_d, f_h, memSize, cudaMemcpyHostToDevice)); + + // Diffusion coefficients + float cc = 0.5f, ce = 0.1f, cw = 0.1f, cn = 0.1f, cs = 0.1f, ct = 0.1f, cb = 0.1f; + + // Define CUDA grid and block dimensions + const int threadsPerBlock_x = 16; + const int threadsPerBlock_y = 16; + const int threadsPerBlock_z = 4; + dim3 threadsPerBlock(threadsPerBlock_x, threadsPerBlock_y, threadsPerBlock_z); + dim3 numBlocks((nx + threadsPerBlock_x - 1) / threadsPerBlock_x, + (ny + threadsPerBlock_y - 1) / threadsPerBlock_y, + (nz + threadsPerBlock_z - 1) / threadsPerBlock_z); + + // Launch the CUDA kernel + diffusion3d_gpu<<>>(nx, ny, nz, cc, ce, cw, cn, cs, ct, cb, f_d, fn_d); + checkCudaErrors(cudaGetLastError()); + + // Copy the result back from device to host + checkCudaErrors(cudaMemcpy(fn_h, fn_d, memSize, cudaMemcpyDeviceToHost)); + + // Perform CPU calculation for verification + diffusion3d_cpu(nx, ny, nz, cc, ce, cw, cn, cs, ct, cb, f_h, fn_cpu); + + // Compare results + int errors = 0; + for (int i = 0; i < arraySize; i++) { + if (abs((fn_h[i] - fn_cpu[i])/fn_cpu[i]) > 1e-6) { + if (errors < 10) { + printf("Error at index %d: GPU result %f, CPU result %f\n", i, fn_h[i], fn_cpu[i]); + } + errors++; + } + } + + if (errors == 0) { + printf("Success! GPU and CPU results match.\n"); + } else { + printf("Verification failed: Found %d errors.\n", errors); + return 1; + } + + // Free all allocated memory + free(f_h); + free(fn_h); + free(fn_cpu); + checkCudaErrors(cudaFree(f_d)); + checkCudaErrors(cudaFree(fn_d)); + } + { + namespace multi = boost::multi; + multi::thrust::device_array f_d({nx, ny, nz}); + // f_d.elements() = [] __host__ __device__ (multi::thrust::device_array::index i) { return i; } ^ multi::extensions_t<1>(f_d.num_elements()); + } + return 0; +} diff --git a/include/multi/adaptors/thrust/test/memory_resource.cu b/include/boost/multi/adaptors/thrust/test/memory_resource.cu similarity index 92% rename from include/multi/adaptors/thrust/test/memory_resource.cu rename to include/boost/multi/adaptors/thrust/test/memory_resource.cu index 816b8c20d..c996ee383 100644 --- a/include/multi/adaptors/thrust/test/memory_resource.cu +++ b/include/boost/multi/adaptors/thrust/test/memory_resource.cu @@ -1,8 +1,11 @@ +// Copyright 2022-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + #define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA thrust memory resource" -#include -#include -#include +#include +#include #include // for cuda_pointer @@ -11,7 +14,7 @@ #include // for thrust::mr::tls_disjoint_pool #include // for thrust::mr::unsynchronized_pool_resource -#include +// #include #include #include @@ -35,6 +38,12 @@ void do_stuff_with_array(typename MultiArray::allocator_type alloc) { BOOST_REQUIRE(arr1.num_elements() == 600); } +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + +#if 0 BOOST_AUTO_TEST_CASE(thrust_host_memory_resource) { thrust::mr::new_delete_resource memres; @@ -67,8 +76,6 @@ BOOST_AUTO_TEST_CASE(thrust_host_memory_resource) { } } -template void what(T&&) = delete; - BOOST_AUTO_TEST_CASE(thrust_device_memory_resource) { { // use the global device_ptr-flavored device memory resource @@ -79,7 +86,7 @@ BOOST_AUTO_TEST_CASE(thrust_device_memory_resource) { using Alloc = thrust::mr::polymorphic_allocator>; Alloc alloc(&adaptor); - do_stuff_with_array>(alloc); + do_stuff_with_array>(alloc); multi::array arr({10, 10}, &adaptor); } @@ -118,7 +125,7 @@ BOOST_AUTO_TEST_CASE(thrust_universal_memory_resource) { using Alloc = thrust::mr::polymorphic_allocator>; Alloc alloc(&adaptor); - do_stuff_with_array>(alloc); + do_stuff_with_array>(alloc); multi::array arr({10, 10}, &adaptor); } @@ -289,12 +296,12 @@ auto& tls_pool(std::pmr::memory_resource* upstream) { template< class T, class Base_ = thrust::mr::allocator>> -// = std::pmr::polymorphic_allocator +// = std::pmr::polymorphic_allocator > struct caching_allocator : Base_ { caching_allocator() : Base_{ &thrust::mr::tls_disjoint_pool(thrust::mr::get_global_resource(), thrust::mr::get_global_resource()) - // & tls_pool (std::pmr::new_delete_resource()) + // & tls_pool (std::pmr::new_delete_resource()) } {} caching_allocator(caching_allocator const&) : caching_allocator{} {} template struct rebind {using other = caching_allocator;}; @@ -309,10 +316,10 @@ BOOST_AUTO_TEST_CASE(thrust_benchmark_contd) { for(int64_t i = 0; i != count; ++i) { multi::array> arr({1000 - i%10, 1000 + i%10}); - // auto arr2 = arr; - // arr2 = arr; - // arr2 = std::move(arr); - // DoNotOptimize(arr2); + // auto arr2 = arr; + // arr2 = arr; + // arr2 = std::move(arr); + // DoNotOptimize(arr2); DoNotOptimize(arr); } @@ -321,3 +328,8 @@ BOOST_AUTO_TEST_CASE(thrust_benchmark_contd) { } } #endif + +#endif +return boost::report_errors(); + +} \ No newline at end of file diff --git a/include/boost/multi/adaptors/thrust/test/mngd_ptr.cpp b/include/boost/multi/adaptors/thrust/test/mngd_ptr.cpp new file mode 100644 index 000000000..29d42fa29 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/mngd_ptr.cpp @@ -0,0 +1,130 @@ +#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS gemm" +#define BOOST_TEST_DYN_LINK +// #include + +#include "../../../../adaptors/cuda/cublas/context.hpp" + +#include "../managed/ptr.hpp" +#include "../../../../adaptors/cuda.hpp" +#include "../../../../adaptors/blas/gemm.hpp" +#include "../../../../adaptors/blas/trsm.hpp" + +#include + +namespace multi = boost::multi; +namespace cuda = multi::memory::cuda; +namespace blas = multi::blas; + +namespace utf = boost::unit_test; + +BOOST_AUTO_TEST_CASE(multi_cuda_mngd_ptr){ + using T = double; + static_assert( sizeof(cuda::managed::ptr) == sizeof(T*) ); + static_assert( std::is_convertible, T*>{} ); + auto f = [](double* dp){return bool{dp};}; + cuda::managed::ptr p; + f(p); +} + +BOOST_AUTO_TEST_CASE(const multi_cuda_mngd_ptr_call_gemm){ + using complex = std::complex; complex const I{0.0, 1.0}; + boost::multi::cuda::managed::array m = { + { 1.0 + 2.0*I, 3.0 - 3.0*I, 1.0 - 9.0*I}, + { 9.0 + 1.0*I, 7.0 + 4.0*I, 1.0 - 8.0*I}, + }; + boost::multi::cuda::managed::array const b = { + { 11.0 + 1.0*I, 12.0 + 1.0*I, 4.0 + 1.0*I, 8.0 - 2.0*I}, + { 7.0 + 8.0*I, 19.0 - 2.0*I, 2.0 + 1.0*I, 7.0 + 1.0*I}, + { 5.0 + 1.0*I, 3.0 - 1.0*I, 3.0 + 8.0*I, 1.0 + 1.0*I}, + }; +// { +// blas::context ctxt; +// auto c =+ blas::gemm(&ctxt, 1., m, b); +// static_assert( std::is_same>{} ); +// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); +// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); +// } +// { +// multi::cuda::managed::array c({2, 4}); +// multi::cuda::cublas::context ctxt; +// blas::gemm_n(ctxt, 1., begin(m), size(m), begin(b), 0., begin(c)); +// cudaDeviceSynchronize(); +// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); +// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); +// } + { + multi::cuda::cublas::context ctxt; + auto c =+ blas::gemm(&ctxt, 1.0, m, b); + static_assert( std::is_same>{} ); + BOOST_REQUIRE( c[1][2] == complex(112, 12) ); + BOOST_REQUIRE( b[1][2] == 2.0 + 1.0*I ); + } +// { +// auto c =+ blas::gemm(1., m, b); +// static_assert( std::is_same>{} ); +// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); +// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); +// } +// { +// auto c =+ blas::gemm(1., m, b);//blas::default_context_of(m.base()), 1., m, b); +// static_assert( std::is_same>{} ); +// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); +// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); +// } +// { +// multi::cuda::managed::array c({2, 4}); +// multi::cuda::cublas::context ctxt; +// blas::gemm_n(ctxt, 1., begin(m), size(m), begin(b), 0., begin(c)); +// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); +// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); +// } +// { +// auto c =+ blas::gemm(1., m, b); +// static_assert( std::is_same>{} ); +// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); +// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); +// } +} + +//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_managed_ptr, *utf::tolerance(0.00001)){ +// using complex = std::complex; complex const I{0, 1}; +// multi::cuda::managed::array const A = { +// { 1. + 4.*I, 3., 4.- 10.*I}, +// { 0., 7.- 3.*I, 1.}, +// { 0., 0., 8.- 2.*I} +// }; +// namespace blas = multi::blas; +// { +// { +// multi::cuda::managed::array B = { +// {1. + 1.*I, 5. + 3.*I}, +// {2. + 1.*I, 9. + 3.*I}, +// {3. + 1.*I, 1. - 1.*I}, +// }; +// blas::trsm(blas::side::left, blas::filling::lower, 1., blas::H(A), B); // S = A⁻¹†.B, S† = B†.A⁻¹ +// cudaDeviceSynchronize(); +// BOOST_TEST( real(B[2][1]) == 1.71608 ); +// } +// { +// multi::cuda::managed::array B = { +// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, +// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} +// }; +// auto const S =+ blas::trsm(blas::side::left, blas::filling::upper, 1., A, blas::H(B)); // S = A⁻¹B†, S†=B.A⁻¹†, S=(B.A⁻¹)†, B <- S†, B <- B.A⁻¹† +// cudaDeviceSynchronize(); +// // BOOST_TEST( imag(S[2][1]) == +0.147059 ); +// BOOST_TEST( imag(B[1][2]) == -0.147059 ); +// } +// { +// multi::cuda::managed::array B = { +// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, +// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} +// }; +// auto const S =+ blas::trsm(blas::side::left, blas::filling::upper, 2., A, blas::H(B)); // S = A⁻¹B†, S†=B.A⁻¹†, S=(B.A⁻¹)†, B <- S†, B <- B.A⁻¹† +// cudaDeviceSynchronize(); +// // BOOST_TEST( imag(S[2][1]) == +0.147059*2. ); +// BOOST_TEST( imag(B[1][2]) == -0.147059*2. ); +// } +// } +//} + diff --git a/include/boost/multi/adaptors/thrust/test/neighbor_list.cu b/include/boost/multi/adaptors/thrust/test/neighbor_list.cu new file mode 100644 index 000000000..c7bd01f13 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/neighbor_list.cu @@ -0,0 +1,310 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +// #include +#include // Include for execution policies +#include // for thrust::transform_reduce + +#include + +// #include // for std::execution::par, doesn't work on gcc 13.3 and nvcc 12.0 +#include +#include // for std::transform_reduce + +namespace multi = boost::multi; + +struct v2d { + double x; + double y; + + friend __host__ __device__ constexpr auto x(v2d const& self) { return self.x; } + friend __host__ __device__ constexpr auto y(v2d const& self) { return self.y; } +}; + +__host__ __device__ constexpr auto v(double dist) -> double { + return dist * dist; +} + +// declare universal array type +// pros: can be used in CPU, and GPU +// cons: members cannot be used directly on array elements (ref wrapped), careless use might cause a lot of page faults (CPU<->GPU page swapping) +template +using array = multi::thrust::universal_array; + +// pros: familiarity +// cons: bug prone, lots of state and variables, for init is ugly +template +auto energy_raw_loops(Array1D const& positions, Array2D const& neighbors) { + double ret = 0; + for(multi::index i = 0; i != positions.size(); ++i) { + auto const& neighbors_i = neighbors[i]; + for(multi::index j = 0; j != neighbors_i.size(); ++j) { + if(neighbors_i[j] == -1) { + continue; + } // or add zero + auto dist = std::abs(x(positions[i]) - x(positions[neighbors_i[j]])); + ret += v(dist); + } + } + return ret; +} + +// pros: familiar, more compact +// cons: bug prone +template +auto energy_range_loops(Array1D const& positions, Array2D const& neighbors) { + double ret = 0; + for(auto const i : positions.extension()) { + auto const positions_i = positions[i]; + for(auto const& nbidx : neighbors[i]) { + if(nbidx == -1) { + continue; + } + auto dist = std::abs(x(positions_i) - x(positions[nbidx])); + ret += v(dist); + } + } + return ret; +} + +// pros: familiar, less state +// const: too nested +template +auto energy_reduce_in_loop(Array1D const& positions, Array2D const& neighbors) { + double ret = 0.0; + for(auto const i : positions.extension()) { + ret += std::transform_reduce( + neighbors[i].begin(), neighbors[i].end(), 0.0, std::plus<>{}, + [positions_i = positions[i], &positions](auto nbidx) { + return nbidx == -1 ? 0.0 : v(std::abs(x(positions_i) - x(positions[nbidx]))); + } + ); + } + return ret; +} + +// pros: single ouput, no state, ready for some parallelism, ready for CUDA thrust +// cons: unfamiliar, too many nested structures, return type buried +template +auto energy_nested_reduce(Array1D const& positions, Array2D const& neighbors) { + return std::transform_reduce( + positions.extension().begin(), positions.extension().end(), + 0.0, std::plus<>{}, + [&positions, &neighbors](auto i) { + return std::transform_reduce( + neighbors[i].begin(), neighbors[i].end(), + 0.0, std::plus<>{}, + [positions_i = positions[i], &positions](auto nbidx) { + return nbidx == -1 ? 0.0 : v(std::abs(x(positions_i) - x(positions[nbidx]))); + } + ); + } + ); +} + +// // pros: single ouput, no state, parallel +// // cons: unfamiliar, too many nested structures, parallelization is partial and nested +// auto energy_nested_par_reduce(auto const& positions, auto const& neighbors) { +// return std::transform_reduce( +// std::execution::par, +// positions.extension().begin(), positions.extension().end(), +// 0.0, std::plus<>{}, +// [&positions, &neighbors](auto i) { +// return std::transform_reduce( +// std::execution::unseq, +// neighbors[i].begin(), neighbors[i].end(), +// 0.0, std::plus<>{}, +// [positions_i = positions[i], &positions](auto nbidx) { +// return nbidx == -1 ? 0.0 : v(std::abs(x(positions_i) - x(positions[nbidx]))); +// } +// ); +// } +// ); +// } + +// pros: correct parallelization, no state +// cons: unfamiliar, needs coordinate decomposition, use special Multi features +// auto energy_flatten_par_reduce(auto const& positions, auto const& neighbors) { +// return std::transform_reduce( +// std::execution::par, +// neighbors.extensions().elements().begin(), neighbors.extensions().elements().end(), +// 0.0, +// std::plus<>{}, +// [&positions, &neighbors](multi::array::indexes c) { +// auto [i, j] = c; +// return neighbors[i][j] == -1 ? 0.0 : v(std::abs(x(positions[i]) - x(positions[neighbors[i][j]]))); +// } +// ); +// } + +#ifdef _MSC_VER +template +struct inner_coordinates { + Positions positions; + Neighbors neighbors; + + __host__ __device__ inner_coordinates(Positions positions, Neighbors neighbors) : positions{positions}, neighbors{neighbors} {} + + __host__ __device__ auto operator()(array::indexes ij) const -> double { + auto [i, j] = ij; + return neighbors[i][j] == -1 ? 0.0 : v(std::abs(x(positions[i]) - x(positions[neighbors[i][j]]))); + } +}; +#endif + +// pros: GPU optimized, runs completely on GPU +// const: verbose, enclosing function cannot deduce return, or auto parameters, lamba captures need special care, order of arguments is different from STL, may need a complete diffeent algorithm to extract different information: e.g. thrust::reduce_by_key +template +auto energy_flatten_gpu_reduce(Arr1D const& positions, Arr2D const& neighbors) -> double { + return thrust::transform_reduce( + thrust::cuda::par, + neighbors.extensions().elements().begin(), neighbors.extensions().elements().end(), +#ifdef _MSC_VER + inner_coordinates{positions.begin(), neighbors.begin()}, +#else + [positions = positions.begin(), neighbors = neighbors.begin()] __device__(array::indexes ij) -> double { + auto [i, j] = ij; + return neighbors[i][j] == -1 ? 0.0 : v(std::abs(x(positions[i]) - x(positions[neighbors[i][j]]))); + }, +#endif + 0.0, + std::plus<>{} + ); +} + +template +struct inner { + V2D positions_i; + Positions positions; + + __host__ __device__ inner(V2D positions_i, Positions positions) : positions_i{positions_i}, positions{positions} {} + + __host__ __device__ auto operator()(array::index nbidx) const { + return nbidx == -1 ? 0.0 + : v(std::abs(x(positions_i) - x(positions[nbidx]))); + } +}; + + +template +struct outer { + Positions positions; + Neighbors neighbors; + + __host__ __device__ outer(Positions positions, Neighbors neighbors) : positions{positions}, neighbors{neighbors} {} + + __host__ __device__ auto operator()(array::index i) -> double { + return thrust::transform_reduce( + thrust::device, + neighbors[i].begin(), neighbors[i].end(), + inner{positions[i], positions}, + 0.0, + std::plus<>{} + ); + } +}; + +#if !defined(__clang_major__) +// no pros: this for testing purposed only +// const: requires auxiliary class, and an extra auxiliary class (outer) on Windows +template +auto energy_gpu_nested_reduce(Arr1D const& positions, Arr2D const& neighbors) -> double { + return thrust::transform_reduce( + thrust::cuda::par, + positions.extension().begin(), positions.extension().end(), + #if !defined(_MSC_VER) + [positions = positions.begin(), neighbors = neighbors.begin()] __device__(typename Arr1D::index i) -> double { + return thrust::transform_reduce( + thrust::device, + neighbors[i].begin(), neighbors[i].end(), + inner{positions[i], positions}, + 0.0, + std::plus<>{} + ); + }, + #else + outer{positions.begin(), neighbors.begin()}, + #endif + 0.0, + std::plus<>{} + ); +} + +#endif + +auto universal_memory_supported() -> bool { + std::cout << "testing for universal memory supported" << std::endl; + int d; + cudaGetDevice(&d); + int is_cma = 0; + cudaDeviceGetAttribute(&is_cma, cudaDevAttrConcurrentManagedAccess, d); + if(is_cma) { + std::cout << "universal memory is supported" << std::endl; + } else { + std::cout << "universal memory is NOT supported" << std::endl; + } + return (is_cma == 1) ? true : false; +} + +auto main() -> int { + if(universal_memory_supported()) { + array positions = { + {1.0, 0.0}, + {2.0, 0.0}, + {3.0, 0.0}, + {4.0, 0.0}, + {5.0, 0.0} + }; + + array::index, 2> neighbors = { + {1, 2, -1, -1}, /* of at 0*/ + {0, 2, 3, -1}, /* of at 1*/ + {0, 1, 3, 4}, /* of at 2*/ + {1, 2, 4, -1}, /* of at 3*/ + {2, 3, -1, -1} /* of at 4*/ + }; + + { + auto en = energy_raw_loops(positions, neighbors); + BOOST_TEST( en == 32.0 ); + } + { + auto en = energy_range_loops(positions, neighbors); + BOOST_TEST( en == 32.0 ); + } + { + auto en = energy_reduce_in_loop(positions, neighbors); + BOOST_TEST( en == 32.0 ); + } + { + auto en = energy_nested_reduce(positions, neighbors); + BOOST_TEST( en == 32.0 ); + } +// { +// auto en = energy_nested_par_reduce(positions, neighbors); +// BOOST_TEST( en == 32.0 ); +// } +// { +// auto en = energy_flatten_par_reduce(positions, neighbors); +// BOOST_TEST( en == 32.0 ); +// } +#if !defined(__clang_major__) + { + auto en = energy_flatten_gpu_reduce(positions, neighbors); + BOOST_TEST( en == 32.0 ); + } +#endif +#if !defined(__clang_major__) + { // this is not recommended, it is for testing purposes + auto en = energy_gpu_nested_reduce(positions, neighbors); + BOOST_TEST( en == 32.0 ); + } +#endif + } + + return boost::report_errors(); +} diff --git a/include/multi/memory/adaptors/cuda_/test/overlap_kernel_2d.cu b/include/boost/multi/adaptors/thrust/test/overlap_kernel_2d.cu similarity index 100% rename from include/multi/memory/adaptors/cuda_/test/overlap_kernel_2d.cu rename to include/boost/multi/adaptors/thrust/test/overlap_kernel_2d.cu diff --git a/include/boost/multi/adaptors/thrust/test/run_reduce.cu b/include/boost/multi/adaptors/thrust/test/run_reduce.cu new file mode 100644 index 000000000..6e22e1559 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/run_reduce.cu @@ -0,0 +1,242 @@ +#define ENABLE_GPU 1 + +#include + +#include + +#include + +#include +#include + +#include +#include + +#include + +#include + +template +inline +#if defined(_MSC_VER) + __forceinline +#else + __attribute__((always_inline)) +#endif + void + DoNotOptimize(Tp const& value) { // NOLINT(readability-identifier-naming) +#if defined(_MSC_VER) + _ReadWriteBarrier(); (void)value; +#else + asm volatile("" : : "r,m"(value) : "memory"); // NOLINT(hicpp-no-assembler) +#endif +} + +namespace multi = boost::multi; + +template +__global__ void reduce_kernel_vr(multi::size_t sizex, multi::size_t sizey, kernel_type kernel, array_type odata) { + + extern __shared__ char shared_mem[]; + auto reduction_buffer = (typename array_type::element*)shared_mem; // {blockDim.x, blockDim.y} + + // each thread loads one element from global to shared mem + unsigned int ix = blockIdx.x * blockDim.x + threadIdx.x; + unsigned int tid = threadIdx.y; + unsigned int iy = blockIdx.y * blockDim.y + threadIdx.y; + + if(ix >= sizex) + return; + + if(iy < sizey) { + reduction_buffer[threadIdx.x + blockDim.x * tid] = kernel(ix, iy); + } else { + reduction_buffer[threadIdx.x + blockDim.x * tid] = (typename array_type::element)0.0; + } + + __syncthreads(); + + // do reduction in shared mem + + for(unsigned int s = blockDim.y / 2; s > 0; s >>= 1) { + if(tid < s) { + reduction_buffer[threadIdx.x + blockDim.x * tid] += reduction_buffer[threadIdx.x + blockDim.x * (tid + s)]; + } + __syncthreads(); + } + + // write result for this block to global mem + if(tid == 0) + odata[blockIdx.y][ix] = reduction_buffer[threadIdx.x]; +} + +namespace gpu { +template> +using array = boost::multi::array; + +struct reduce { + explicit reduce(multi::size_t arg_size) : size(arg_size) { + } + multi::size_t size; +}; + +template +struct array_access { + array_type array; + + __host__ __device__ auto operator()(multi::size_t ii) const { + return array[ii]; + } + + __host__ __device__ auto operator()(multi::size_t ix, multi::size_t iy) const { + return array[ix][iy]; + } +}; + +template +auto run(multi::size_t sizex, reduce const& redy, kernel_type kernel) /*-> gpu::array*/ { + + auto const sizey = redy.size; + + // using type = decltype(kernel(0, 0)); + +#ifndef ENABLE_GPU + + gpu::array accumulator(sizex, 0.0); + + for(multi::size_t iy = 0; iy < sizey; iy++) { + for(multi::size_t ix = 0; ix < sizex; ix++) { + accumulator[ix] += kernel(ix, iy); + } + } + + // thrust::transform( + // boost::multi::extension_t({0, sizey}).begin(), + // boost::multi::extension_t({0, sizey}).end(), + // accumulator.begin(), + // [](auto e) {return 0.0;} + // ); + + return accumulator; + +#else + + gpu::array result; + + auto blocksize = 256; // max_blocksize(reduce_kernel_vr); + + unsigned bsizex = 4; // this seems to be the optimal value + if(sizex <= 2) + bsizex = sizex; + unsigned bsizey = blocksize / bsizex; + + assert(bsizey > 1); + + unsigned nblockx = (sizex + bsizex - 1) / bsizex; + unsigned nblocky = (sizey + bsizey - 1) / bsizey; + + result.reextent({nblocky, sizex}); + + gpu::array result2({nblocky, sizex}); + + struct dim3 dg { + nblockx, nblocky + }; + struct dim3 db { + bsizex, bsizey + }; + + auto shared_mem_size = blocksize * sizeof(type); + + assert(shared_mem_size <= 48 * 1024); + + reduce_kernel_vr<<>>(sizex, sizey, kernel, begin(result)); + // check_error(last_error()); + + boost::multi::extensions_t<2> xs = {sizex, sizey}; + assert( xs.size() == sizex ); + + // thrust::transform_reduce( + // xs. + // ); + + if(nblocky == 1) { + cudaDeviceSynchronize(); + // gpu::sync(); + + assert(result[0].size() == sizex); + + return gpu::array(result[0]); + } else { + return run(sizex, reduce(nblocky), array_access{begin(result.transposed())}); + } + +#endif +} + +} // namespace gpu + +struct prod { + /*__host__*/ __device__ auto operator()(multi::size_t ix, multi::size_t iy) const { + return double(ix) * double(iy); + } +}; + +auto main() -> int { + #ifdef NDEBUG + multi::size_t const maxsize = 39062; + multi::size_t const nmax = 1000; + #else + multi::size_t const maxsize = 390; // 390625; + multi::size_t const nmax = 100; // 10000; + #endif + auto pp = [] __host__ __device__(multi::index ix, multi::index iy) -> double { return double(ix) * double(iy); }; + + std::chrono::microseconds mus{0}; + std::size_t FLOPs = 0; + + for(multi::size_t nx = 1; nx <= nmax; nx *= 10) { + for(multi::size_t ny = 1; ny <= maxsize; ny *= 5) { + + multi::thrust::device_array M = [&]() { + multi::thrust::universal_array ret({nx, ny}); + auto const [xs, ys] = ret.extensions(); + for(auto ix : xs) { + for(auto iy : ys) { + ret[ix][iy] = pp(ix, iy); + } + } + return ret; + }(); + cudaDeviceSynchronize(); + DoNotOptimize(M); + + auto start = std::chrono::high_resolution_clock::now(); + + multi::thrust::device_array sums(M.size()); + multi::thrust::reduce_by_index(M, sums); + + // auto sums = gpu::run(nx, gpu::reduce(ny), gpu::array_access{M.begin()}); + + cudaDeviceSynchronize(); + DoNotOptimize(sums); + + mus += std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start); + FLOPs += nx*ny; + } + } + std::cout << "time " << mus.count() << " µs FLOPS " << FLOPs << " flops/s " << FLOPs/(mus.count()/1e6)/1e9 << '\n'; + + { + auto nx = 13; + auto ny = 19; + + multi::thrust::device_array sums(nx); + multi::thrust::reduce_by_index( + [] __host__ __device__(multi::index ix, multi::index iy) -> double { return double(ix) * double(iy); } + ^ multi::extensions_t<2>(nx, ny), + sums + ); + } + return boost::report_errors(); +} diff --git a/include/multi/memory/adaptors/cuda_/test/saxpy_kernel.cu b/include/boost/multi/adaptors/thrust/test/saxpy_kernel.cu similarity index 100% rename from include/multi/memory/adaptors/cuda_/test/saxpy_kernel.cu rename to include/boost/multi/adaptors/thrust/test/saxpy_kernel.cu diff --git a/include/multi/adaptors/thrust/test/set_identity_kernel.cu b/include/boost/multi/adaptors/thrust/test/set_identity_kernel.cu similarity index 80% rename from include/multi/adaptors/thrust/test/set_identity_kernel.cu rename to include/boost/multi/adaptors/thrust/test/set_identity_kernel.cu index 7b273c1fd..c3720e992 100644 --- a/include/multi/adaptors/thrust/test/set_identity_kernel.cu +++ b/include/boost/multi/adaptors/thrust/test/set_identity_kernel.cu @@ -1,13 +1,19 @@ -#include -#include +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include #include #include +#include + namespace multi = boost::multi; #define CUDA_CHECKED(ans) { cudaAssert((ans), __FILE__, __LINE__); } -inline void cudaAssert(cudaError_t code, const char *file, int line, bool abort=true) { +inline void cudaAssert(cudaError_t code, const char *file, int line, bool /*abort*/=true) { if (code != cudaSuccess) { std::cerr<<"error: "<< cudaGetErrorString(code) <<" "<< file <<":"<< line < auto set_identity(Array2D&& arr) -> Array2D&&{ int xblock_dim = 16; auto [m, n] = arr.sizes(); - int xgrid_dim = (m + xblock_dim - 1) / xblock_dim; - int ygrid_dim = (n + xblock_dim - 1) / xblock_dim; + auto xgrid_dim = static_cast((m + xblock_dim - 1) / xblock_dim); + auto ygrid_dim = static_cast((n + xblock_dim - 1) / xblock_dim); dim3 block_dim(xblock_dim, xblock_dim); dim3 grid_dim(xgrid_dim, ygrid_dim); - kernel_setIdentity<<>>(arr.home(), m, n); + kernel_setIdentity<<>>(arr.home(), static_cast(m), static_cast(n)); CUDA_CHECKED(cudaGetLastError()); // CUDA_CHECKED(cudaDeviceSynchronize()); return std::forward(arr); @@ -72,7 +78,7 @@ int main() { } { - multi::array> A({10000, 10000}); + multi::array> A({10000, 10000}); auto const size = A.num_elements()*sizeof(T)/1e9; std::cout<<"size is "<< size << "GB\n"; diff --git a/include/boost/multi/adaptors/thrust/test/speed.cu b/include/boost/multi/adaptors/thrust/test/speed.cu new file mode 100644 index 000000000..e9c0cdebe --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/speed.cu @@ -0,0 +1,265 @@ +// Copyright 2023-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include +#include + +#include + +#include + +namespace multi = boost::multi; + +template<> +inline constexpr bool multi::force_element_trivial_default_construction> = true; + +template<> +inline constexpr bool multi::force_element_trivial_default_construction> = true; + +auto universal_memory_supported() -> bool { + std::cout << "testing for universal memory supported" << std::endl; + int d; + cudaGetDevice(&d); + int is_cma = 0; + cudaDeviceGetAttribute(&is_cma, cudaDevAttrConcurrentManagedAccess, d); + if(is_cma) { + std::cout << "universal memory is supported" << std::endl; + } else { + std::cout << "universal memory is NOT supported" << std::endl; + } + return (is_cma == 1) ? true : false; +} + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + + // BOOST_AUTO_TEST_CASE(warmup) + if(universal_memory_supported()) + { + using T = double; + + auto const n = 8000; + + multi::array> src({n, n}); + multi::array> dst(extensions(src)); + + auto const threshold = 0.30; + + auto const size = src.num_elements() * sizeof(T) / 1e9; + + auto const dummy = std::invoke([&] { + auto start_time = std::chrono::high_resolution_clock::now(); + cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; + auto rate = size / time.count(); + // std::cout<<"memcpy rate = "<< rate <<" GB/s (ratio = 1)\n"; + return rate; + }); + (void)dummy; + + auto const memcpy_rate = std::invoke([&] { + auto start_time = std::chrono::high_resolution_clock::now(); + cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; + auto rate = size / time.count(); + // std::cout<<"memcpy rate = "<< rate <<" GB/s (ratio = 1)\n"; + return rate; + }); + + { // cctor + auto tick = std::chrono::high_resolution_clock::now(); + + auto dst2 = src; + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + // std::cout<<"cctor rate = "<< rate <<" GB/s (ratio = "<< ratio <<")\n"; + if(ratio >= threshold) { + std::cout << "x"; + } + } + { // assign + auto tick = std::chrono::high_resolution_clock::now(); + + dst = src; + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + // std::cout << "assign rate = "<< rate <<" GB/s (ratio = "<< ratio <<")\n"; + if(ratio >= threshold) { + std::cout << "x"; + } + } + { // subarray assign + auto tick = std::chrono::high_resolution_clock::now(); + + dst({0, n - 2}, {0, n - 2}) = src({2, n}, {2, n}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + // std::cout << "subasssign rate = "<< rate <<" GB/s (ratio = "<< ratio << ")\n"; + if(ratio >= threshold) { + std::cout << "x"; + } + } + } + + // BOOST_AUTO_TEST_CASE(thrust_nonuniversal_speed) + { + using T = ::thrust::complex; + std::cout << typeid(T).name() << " ******************************************\n"; + + auto const n = 8000; + + using AllocatorT = thrust::cuda::allocator; + + multi::array src({n, n}); + multi::array dst(extensions(src)); + + auto const threshold = 0.10; + + auto const size = src.num_elements() * sizeof(T) / 1e9; + + auto const dummy = std::invoke([&] __host__ { + auto start_time = std::chrono::high_resolution_clock::now(); + cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; + auto rate = size / time.count(); + std::cout << "memcpy rate = " << rate << " GB/s (warmup)\n"; + return rate; + }); + (void)dummy; + + auto const memcpy_rate = std::invoke([&] __host__ { + auto start_time = std::chrono::high_resolution_clock::now(); + cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; + auto rate = size / time.count(); + std::cout << "memcpy rate = " << rate << " GB/s (ratio = 1)\n"; + return rate; + }); + + { // cctor + auto tick = std::chrono::high_resolution_clock::now(); + + auto dst2 = src; + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + std::cout << "cctor rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + BOOST_TEST(ratio >= threshold); + } + { // assign + auto tick = std::chrono::high_resolution_clock::now(); + + dst = src; + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + std::cout << "assign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + BOOST_TEST(ratio >= threshold); + } + { // subarray assign + auto tick = std::chrono::high_resolution_clock::now(); + + dst({0, n - 2}, {0, n - 2}) = src({2, n}, {2, n}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + std::cout << "subasssign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + BOOST_TEST(ratio >= threshold); + } + } + + // BOOST_AUTO_TEST_CASE(thrust_universal_speed) + if(universal_memory_supported()) + { + using T = ::thrust::complex; + std::cout << typeid(T).name() << " ******************************************\n"; + + auto const n = 8000; + + using AllocatorT = thrust::cuda::universal_allocator; + + multi::array src({n, n}); + multi::array dst(extensions(src)); + + auto const threshold = 0.10; + + auto const size = src.num_elements() * sizeof(T) / 1e9; + + auto const dummy = std::invoke([&] __host__ { + auto start_time = std::chrono::high_resolution_clock::now(); + cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; + auto rate = size / time.count(); + std::cout << "memcpy rate = " << rate << " GB/s (warmup)\n"; + return rate; + }); + (void)dummy; + + auto const memcpy_rate = std::invoke([&] __host__ { + auto start_time = std::chrono::high_resolution_clock::now(); + cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); + cudaDeviceSynchronize(); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; + auto rate = size / time.count(); + std::cout << "memcpy rate = " << rate << " GB/s (ratio = 1)\n"; + return rate; + }); + + { // cctor + auto tick = std::chrono::high_resolution_clock::now(); + + auto dst2 = src; + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + std::cout << "cctor rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + // BOOST_WARN(ratio >= threshold); + } + { // assign + auto tick = std::chrono::high_resolution_clock::now(); + + dst = src; + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + + std::cout << "assign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + BOOST_TEST(ratio >= threshold); + } + { // subarray assign + auto tick = std::chrono::high_resolution_clock::now(); + + dst({0, n - 2}, {0, n - 2}) = src({2, n}, {2, n}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + double ratio = rate / memcpy_rate; + std::cout << "subasssign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; + BOOST_TEST(ratio >= threshold); + } + } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/thrust/test/speed_algo.cu b/include/boost/multi/adaptors/thrust/test/speed_algo.cu new file mode 100644 index 000000000..d981907f1 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/speed_algo.cu @@ -0,0 +1,147 @@ +// Copyright 2022-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +#include + +#include + +#include + +namespace multi = boost::multi; +using complex = thrust::complex; + +template +void doNotOptimize(T const& val) { +#if defined(_MSC_VER) + _ReadWriteBarrier(); + (void)val; +#else + asm volatile("" : : "g"(val) : "memory"); +#endif +} + +auto universal_memory_supported() -> bool { + std::cout << "testing for universal memory supported" << std::endl; + int d; + cudaGetDevice(&d); + int is_cma = 0; + cudaDeviceGetAttribute(&is_cma, cudaDevAttrConcurrentManagedAccess, d); + if(is_cma) { + std::cout << "universal memory is supported" << std::endl; + } else { + std::cout << "universal memory is NOT supported" << std::endl; + } + return (is_cma == 1) ? true : false; +} + +int main() { + // BOOST_AUTO_TEST_CASE(thrust_universal_speed_algo) + if(universal_memory_supported()) { + auto const n = 8000; + { // cctor + auto const tick = std::chrono::high_resolution_clock::now(); + + multi::array> A({n, n}); + +#if(CUDART_VERSION < 13000) + // cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements() * sizeof(complex), 0); + cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements() * sizeof(complex), 0, 0); +// #else +// cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements() * sizeof(complex), cudaMemLocation{cudaMemLocationTypeHost, 0}, 0); +#endif + + auto size = A.num_elements() * sizeof(complex) / 1e9; + std::fill_n(raw_pointer_cast(A.data_elements()), A.num_elements(), complex{1.0}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + double rate = size / time.count(); + (void)rate; + } + { // cctor + auto tick = std::chrono::high_resolution_clock::now(); + + multi::array> A({n, n}); + + std::fill_n(raw_pointer_cast(A.data_elements()), A.num_elements(), complex{1.0}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + + auto size = A.num_elements() * sizeof(complex) / 1e9; + double rate = size / time.count(); + + std::cout << "no prefetch+cpu_algo rate = " << rate << " GB/s\n"; + } + { // cctor + auto tick = std::chrono::high_resolution_clock::now(); + + multi::array> A({n, n}); +#if(CUDART_VERSION < 13000) + // cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements() * sizeof(complex), 0); + cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements() * sizeof(complex), 0, cudaStream_t{}); +#endif + + thrust::fill_n(raw_pointer_cast(A.data_elements()), A.num_elements(), complex{1.0}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + + auto size = A.num_elements() * sizeof(complex) / 1e9; + double rate = size / time.count(); + + std::cout << "dev prefetch+cpu_algo rate = " << rate << " GB/s\n"; + } + { + auto tick = std::chrono::high_resolution_clock::now(); + + multi::array> A({n, n}); + + thrust::fill_n(A.data_elements(), A.num_elements(), complex{1.0}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + + auto size = A.num_elements() * sizeof(complex) / 1e9; + double rate = size / time.count(); + + std::cout << "no prefetch+gpu_algo rate = " << rate << " GB/s\n"; + } + { + auto tick = std::chrono::high_resolution_clock::now(); + + multi::array> A({n, n}); +#if(CUDART_VERSION < 13000) + cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements() * sizeof(complex), 0); +#endif + thrust::fill_n(A.data_elements(), A.num_elements(), complex{1.0}); + + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + + auto size = A.num_elements() * sizeof(complex) / 1e9; + double rate = size / time.count(); + + std::cout << "dev prefetch+gpu_algo rate = " << rate << " GB/s\n"; + } + { + auto tick = std::chrono::high_resolution_clock::now(); + + multi::array> A({n, n}, complex{0.0}); + doNotOptimize(A); + std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; + + auto size = A.num_elements() * sizeof(complex) / 1e9; + double rate = size / time.count(); + + std::cout << "fill constructor rate = " << rate << " GB/s\n"; + } + } + + // BOOST_AUTO_TEST_CASE(thrust_run) + { + multi::array> A(100); + } + + return boost::report_errors(); +} diff --git a/include/boost/multi/adaptors/thrust/test/universal.cu b/include/boost/multi/adaptors/thrust/test/universal.cu new file mode 100644 index 000000000..9b9699554 --- /dev/null +++ b/include/boost/multi/adaptors/thrust/test/universal.cu @@ -0,0 +1,107 @@ +// Copyright 2023-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include +#include + +#include + +// #include + +#include +#include +#include + +auto universal_memory_supported() -> bool { + std::cout << "testing for universal memory supported" << std::endl; + int d; + cudaGetDevice(&d); + int is_cma = 0; + cudaDeviceGetAttribute(&is_cma, cudaDevAttrConcurrentManagedAccess, d); + if(is_cma) { + std::cout << "universal memory is supported" << std::endl; + } else { + std::cout << "universal memory is NOT supported" << std::endl; + } + return (is_cma == 1)?true:false; +} + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + + // BOOST_AUTO_TEST_CASE(thrust_universal_ptr) + if(universal_memory_supported()) + { + multi::array Host({1024, 1024}); + std::iota(Host.data_elements(), Host.data_elements() + Host.num_elements(), 12.0); + + multi::array> Univ({1024, 1024}); + + Univ({0, 10}, {0, 20}) = Host({0, 10}, {0, 20}); + + multi::array Hos2({1024, 1024}); + Hos2({0, 10}, {0, 20}) = Univ({0, 10}, {0, 20}); + + BOOST_TEST( std::abs( Hos2[0][0] - 12.0 ) < 1e-10 ); + } + + // BOOST_AUTO_TEST_CASE(thrust_universal_ptr_initializer_list) + if(universal_memory_supported()) + { + multi::array Host = {1.0, 2.0, 3.0}; + BOOST_TEST( Host.size() == 3 ); + { + multi::array> Univ(multi::extensions_t<1>{3}); + Univ[0] = 3.0; + Univ[1] = 2.0; + Univ[2] = 1.0; + + Host() = Univ(); + + BOOST_TEST( Host[0] == 3.0 ); + } + { + multi::array tmp = { + 3.0, + 2.0, + 1.0, + }; + multi::array> Univ{multi::extensions_t<1>{3}}; + Univ = tmp; + + Host() = Univ(); + + BOOST_TEST( Host[0] == 3.0 ); + } + { + multi::array tmp = { + 3.0, + 2.0, + 1.0, + }; + multi::array> Univ{tmp}; + + Host() = Univ(); + + BOOST_TEST( Host[0] == 3.0 ); + } + { + multi::array> Univ = { + 3.0, + 2.0, + 1.0, + }; + + Host() = Univ(); + + std::cout << "host 0 " << Host[0] << '\n'; + BOOST_TEST( Host[0] == 3.0 ); + } + } + + return boost::report_errors(); +} diff --git a/include/multi/adaptors/thrust/test/vector.cu b/include/boost/multi/adaptors/thrust/test/vector.cu similarity index 55% rename from include/multi/adaptors/thrust/test/vector.cu rename to include/boost/multi/adaptors/thrust/test/vector.cu index aab63e419..401d7572e 100644 --- a/include/multi/adaptors/thrust/test/vector.cu +++ b/include/boost/multi/adaptors/thrust/test/vector.cu @@ -1,10 +1,16 @@ -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA thrust" -#include +// Copyright 2021-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt #include #include -BOOST_AUTO_TEST_CASE(vector){ +#include + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + +// BOOST_AUTO_TEST_CASE(vector) +{ // H has storage for 4 integers thrust::host_vector H(4); @@ -15,15 +21,15 @@ BOOST_AUTO_TEST_CASE(vector){ H[3] = 46; // H.size() returns the size of vector H - BOOST_TEST_REQUIRE( H.size() == 4 ); + BOOST_TEST( H.size() == 4 ); // print contents of H - BOOST_TEST_REQUIRE( H[2] == 38 ); + BOOST_TEST( H[2] == 38 ); // resize H H.resize(2); - BOOST_REQUIRE( H.size() == 2 ); + BOOST_TEST( H.size() == 2 ); // Copy host_vector H to device_vector D thrust::device_vector D = H; @@ -36,7 +42,11 @@ BOOST_AUTO_TEST_CASE(vector){ // thurst::device_ptr p = D.data(); // doesn't work with CUDA 11.8 thrust::cuda::pointer p = D.data(); // this works with thrust from CUDA 12.1 - BOOST_REQUIRE( p[0] == 99 ); + BOOST_TEST( p[0] == 99 ); + + BOOST_TEST( D[1] == 88 ); +} + +return boost::report_errors(); - BOOST_TEST_REQUIRE( D[1] == 88 ); } diff --git a/include/boost/multi/adaptors/totalview.hpp b/include/boost/multi/adaptors/totalview.hpp new file mode 100644 index 000000000..307f7a2c6 --- /dev/null +++ b/include/boost/multi/adaptors/totalview.hpp @@ -0,0 +1,121 @@ +// Copyright 2018-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ADAPTORS_TOTALVIEW_HPP +#define BOOST_MULTI_ADAPTORS_TOTALVIEW_HPP + +#include +#include // TODO(correaa) remove +#include + +#include +#include + +#include "../adaptors/../array.hpp" + +#include "../src/tv_data_display.c" // you have to find the directory with the totalview include files +#include "../src/sys/totalview/tv_data_display.h" // you have to find the directory with the totalview include files +// ^^^^^^^^^^^ this can produce problemas later with linking +// https://docs.roguewave.com/totalview/2018.1/html/index.html#page/Reference_Guide%2FCompilingAndLinkingTV_data_display.html%23ww1738654 + +template constexpr char const* pretty_name = "unknown"; + +template<> constexpr char const* pretty_name = "double"; +template<> constexpr char const* pretty_name = "float"; + +template<> constexpr char const* pretty_name> = "std::complex"; +template<> constexpr char const* pretty_name> = "std::complex"; + +template<> constexpr char const* pretty_name = "long"; +template<> constexpr char const* pretty_name = "int"; + +template +#ifdef __GCC__ +__attribute__((used)) +#endif +int +TV_ttf_display_type(boost::multi::array const* mad1P) { + if(! mad1P->is_empty()) { + std::array tname; + snprintf(tname.data(), tname.size(), "%s[%ld]", pretty_name, (long)mad1P->size()); //, (long)mad1P->stride()); + int result = TV_ttf_add_row("elements", tname.data(), mad1P->origin()); + if(result != 0) { + int res = fprintf(stderr, "TV_ttf_add_row returned error %d\n", result); + assert(res > -1); + return TV_ttf_format_failed; + } + } + return TV_ttf_format_ok_elide; +} + +template +#ifdef __GCC__ +__attribute__((used)) +#endif +int +TV_ttf_display_type(boost::multi::array const* mad2P) { + if(! mad2P->is_empty()) { + std::arra tname; + using std::get; + snprintf(tname.data(), tname.size(), "%s[%ld][%ld]", pretty_name, (long)get<0>(mad2P->sizes()), (long)get<1>(mad2P->sizes())); //, (long)mad1P->stride()); + int result = TV_ttf_add_row("elements", tname.data(), mad2P->origin()); + + if(result != 0) { + int res = fprintf(stderr, "TV_ttf_add_row returned error %d\n", result); + assert(res >= 0); + return TV_ttf_format_failed; + } + } + return TV_ttf_format_ok_elide; +} + +template +#ifdef __GCC__ +__attribute__((used)) +#endif +int +TV_ttf_display_type(boost::multi::subarray const* mad2P) { + boost::multi::array const value = *mad2P; + return TV_ttf_display_type(std::addressof(value)); +} + +template +#ifdef __GCC__ +__attribute__((used)) +#endif +int +TV_ttf_display_type(boost::multi::subarray const* mad2P) { + boost::multi::array const value = *mad2P; + return TV_ttf_display_type(std::addressof(value)); +} + +template int TV_ttf_display_type(boost::multi::array const*); +template int TV_ttf_display_type(boost::multi::array const*); +template int TV_ttf_display_type>(boost::multi::array, 1> const*); +template int TV_ttf_display_type>(boost::multi::array, 1> const*); +template int TV_ttf_display_type(boost::multi::array const*); +template int TV_ttf_display_type(boost::multi::array const*); + +template int TV_ttf_display_type(boost::multi::array const*); +template int TV_ttf_display_type(boost::multi::array const*); +template int TV_ttf_display_type>(boost::multi::array, 2> const*); +template int TV_ttf_display_type>(boost::multi::array, 2> const*); +template int TV_ttf_display_type(boost::multi::array const*); +template int TV_ttf_display_type(boost::multi::array const*); + +template int TV_ttf_display_type(boost::multi::subarray const*); +template int TV_ttf_display_type(boost::multi::subarray const*); +template int TV_ttf_display_type>(boost::multi::subarray, 1> const*); +template int TV_ttf_display_type>(boost::multi::subarray, 1> const*); +template int TV_ttf_display_type(boost::multi::subarray const*); +template int TV_ttf_display_type(boost::multi::subarray const*); + +template int TV_ttf_display_type(boost::multi::subarray const*); +template int TV_ttf_display_type(boost::multi::subarray const*); +template int TV_ttf_display_type>(boost::multi::subarray, 2> const*); +template int TV_ttf_display_type>(boost::multi::subarray, 2> const*); +template int TV_ttf_display_type(boost::multi::subarray const*); +template int TV_ttf_display_type(boost::multi::subarray const*); + +#endif // BOOST_MULTI_ADAPTORS_TOTALVIEW_HPP diff --git a/include/boost/multi/adaptors/totalview/test/CMakeLists.txt b/include/boost/multi/adaptors/totalview/test/CMakeLists.txt new file mode 100644 index 000000000..01bc37524 --- /dev/null +++ b/include/boost/multi/adaptors/totalview/test/CMakeLists.txt @@ -0,0 +1,47 @@ +# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- +#[=[Multi Test suite can be run like this: + mkdir -p build + cd build + cmake .. [-DENABLE_CUDA=1] + make -j + ctest -j --output-on-error [-T memcheck] + exit +#]=] +cmake_minimum_required(VERSION 3.11) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") + +set(CMAKE_VERBOSE_MAKEFILE ON) + +project( + boost-multi-adaptors-blis-test + VERSION 0.1 + LANGUAGES CXX +) + +find_package(Boost REQUIRED COMPONENTS unit_test_framework) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +enable_testing() + +include(CTest) + +include_directories(${CMAKE_BINARY_DIR}) + +include_directories(../../../../../include) + +set(TEST_SRCS matrix.cpp) + +add_executable(totalview.x totalview.cpp) + +target_compile_features(totalview.x PUBLIC cxx_std_17) + +# target_compile_definitions(totalview.x PRIVATE "BOOST_PP_VARIADICS") +target_compile_definitions(totalview.x PRIVATE ${Boost_DEFINITIONS}) +target_include_directories(totalview.x SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(totalview.x PRIVATE ${Boost_LIBRARIES}) +target_link_directories(totalview.x PRIVATE ${Boost_LIBRARY_DIRS}) + +add_test(NAME totalview.x COMMAND $) diff --git a/include/boost/multi/adaptors/totalview/test/totalview.cpp b/include/boost/multi/adaptors/totalview/test/totalview.cpp new file mode 100644 index 000000000..15d0a3ce2 --- /dev/null +++ b/include/boost/multi/adaptors/totalview/test/totalview.cpp @@ -0,0 +1,37 @@ +#define BOOST_TEST_MODULE "C++ Unit Tests for Multi TotalView adaptor" +#define BOOST_TEST_DYN_LINK + +// #include + +#include "multi/array.hpp" +#include "multi/utility.hpp" + +#include "../../../adaptors/totalview.hpp" + +#include // transform +#include +#include +#include // iota + +namespace multi = boost::multi; + +BOOST_AUTO_TEST_CASE(multi_1d) { + + std::vector V = {10, 20, 30}; + + multi::array const A = {1.0, 2.0, 3.0, 4.0, 5.0}; + auto&& Apart = A({1, 3}); + + multi::array const B = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + }; + + double sum = 0.0; + for(auto i : A.extension()) { + sum += A[i]; + } + + BOOST_REQUIRE( sum == 15.0 ); + BOOST_REQUIRE( B[1][0] == 4.0 ); +} diff --git a/include/multi/algorithms/gemm.hpp b/include/boost/multi/algorithms/gemm.hpp similarity index 77% rename from include/multi/algorithms/gemm.hpp rename to include/boost/multi/algorithms/gemm.hpp index b56224396..f4095624f 100644 --- a/include/multi/algorithms/gemm.hpp +++ b/include/boost/multi/algorithms/gemm.hpp @@ -1,11 +1,13 @@ -// Copyright 2021 Alfredo A. Correa +// Copyright 2021-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt // this header contains a generic gemm algorithm (not the blas one) // it is ~3 times slower than blas::gemm but it is more generic in the type and in the operations -// when compiled using -DCMAKE_CXX_FLAGS_RELEASE="-Ofast -DNDEBUG -mfpmath=sse -march=native -funroll-loops -fargument-noalias" +// when compiled using -DCMAKE_CXX_FLAGS_RELEASE="-Ofast -DNDEBUG -mfpmath=sse -march=native -funroll-loops" -#ifndef MULTI_ALGORITHM_GEMM_HPP -#define MULTI_ALGORITHM_GEMM_HPP +#ifndef BOOST_MULTI_ALGORITHM_GEMM_HPP +#define BOOST_MULTI_ALGORITHM_GEMM_HPP #include // for par // needs linking to TBB library #include // for inner_product and transform_reduce @@ -54,26 +56,26 @@ auto gemm(Talpha const& alpha, MatrixA const& A, MatrixB const& B, Tbeta const& assert( (~B).size() % N == 0); assert( B .size() % N == 0); - std::transform(std::execution::par, begin(A.chunked(N)), end(A.chunked(N)), begin(C.chunked(N)), begin(C.chunked(N)), [&](auto const& Afatrow, auto&& Cfatrow) { + std::transform(std::execution::par, A.chunked(N).begin(), A.chunked(N).end(), C.chunked(N).begin(), C.chunked(N).begin(), [&](auto const& Afatrow, auto&& Cfatrow) -> decltype(Cfatrow) { auto const& BfatcolsT = (~B).chunked(N); auto const& AblocksT = (~Afatrow).chunked(N); auto&& CblocksT = (~Cfatrow).chunked(N); - std::transform(std::execution::par, begin(BfatcolsT), end(BfatcolsT), begin(CblocksT), begin(CblocksT), [&](auto const& BfatcolT, auto&& CblockTR) { + std::transform(std::execution::par, BfatcolsT.begin(), BfatcolsT.end(), CblocksT.begin(), CblocksT.begin(), [&](auto const& BfatcolT, auto&& CblockTR) { auto const& Bblocks = (~BfatcolT).chunked(N); auto Cblock = +~CblockTR; - std::transform(std::execution::unseq, begin(Cblock.elements()), end(Cblock.elements()), begin(Cblock.elements()), [&](auto&& c) {return beta*std::forward(c);}); + std::transform(std::execution::unseq, begin(Cblock.elements()), end(Cblock.elements()), begin(Cblock.elements()), [&](auto&& c) { return beta*std::forward(c); }); return +~std::inner_product( - begin(AblocksT), end(AblocksT), begin(Bblocks), + AblocksT.begin(), AblocksT.end(), Bblocks.begin(), std::move(Cblock), - [&](auto&& ret, auto const& prod) {return prod(std::forward(ret));}, + [&](auto&& ret, auto const& prod) { return prod(std::forward(ret)); }, [&](auto const& AblockT, auto const& Bblock) { return [&, AbR = +~AblockT, BbTR = +~Bblock](auto&& into) {return detail::naive_gemm(alpha, AbR, ~BbTR, 1., std::forward(into), sum2, prod2);}; } ) ; }); - return std::move(Cfatrow); // NOLINT(bugprone-move-forwarding-reference) + return std::forward(Cfatrow); // NOLINT(bugprone-move-forwarding-reference) }); return std::forward(C); } @@ -86,4 +88,4 @@ auto gemm(Talpha const& alpha, MatrixA const& A, MatrixB const& B, Tbeta const& } // end namespace multi } // end namespace boost -#endif +#endif // BOOST_MULTI_ALGORITHM_GEMM_HPP diff --git a/include/boost/multi/algorithms/redux.hpp b/include/boost/multi/algorithms/redux.hpp new file mode 100644 index 000000000..ec2b20436 --- /dev/null +++ b/include/boost/multi/algorithms/redux.hpp @@ -0,0 +1,16 @@ +// Copyright 2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ALGORITHM_REDUX_HPP +#define BOOST_MULTI_ALGORITHM_REDUX_HPP + +// #include // for par // needs linking to TBB library +// #include // for inner_product and transform_reduce + +namespace boost::multi { + + +} // end namespace boost::multi + +#endif // BOOST_MULTI_ALGORITHM_GEMM_HPP diff --git a/include/boost/multi/array.hpp b/include/boost/multi/array.hpp new file mode 100644 index 000000000..813a5daf1 --- /dev/null +++ b/include/boost/multi/array.hpp @@ -0,0 +1,1634 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ARRAY_HPP_ +#define BOOST_MULTI_ARRAY_HPP_ + +#include // IWYU pragma: export +#include +#include +#include +#include + +#include // for std::sentinel_for +#include // for std::allocator_traits +#include +#include // needed by a deprecated function +#include // for std::common_reference +#include // for std::move + +#if __has_include() +#include +// Apple clang provides the header but not the compiled library prior to version 16 +#if (defined(__cpp_lib_memory_resource) && (__cpp_lib_memory_resource >= 201603)) && !(defined(__APPLE__) && defined(__clang_major__) && __clang_major__ <= 15) && (!defined(_LIBCPP_VERSION) || !(_LIBCPP_VERSION <= 160001)) +#define BOOST_MULTI_HAS_MEMORY_RESOURCE +#endif +#endif + +#if defined(__cplusplus) && (__cplusplus >= 202002L) && __has_include() && __has_include() +#include // for constructible_from // NOLINT(misc-include-cleaner) // IWYU pragma: keep +#include // IWYU pragma: keep +#endif + +// TODO(correaa) or should be (__CUDA__) or CUDA__ || HIP__ +#ifdef __NVCC__ +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4626) // assignment operator was implicitly defined as deleted +#endif + +namespace boost::multi { + +namespace detail { + +template +struct array_allocator { + using allocator_type = Allocator; + array_allocator() = default; + + private: + BOOST_MULTI_NO_UNIQUE_ADDRESS allocator_type alloc_; + + using allocator_traits = typename multi::allocator_traits; + using size_type_ = typename allocator_traits::size_type; + using pointer_ = typename allocator_traits::pointer; + + protected: + constexpr auto alloc() & -> auto& { return alloc_; } + constexpr auto alloc() const& -> allocator_type const& { return alloc_; } + + constexpr explicit array_allocator(allocator_type const& alloc) : alloc_{alloc} {} // NOLINT(modernize-pass-by-value) + + constexpr auto allocate(size_type_ n) -> pointer_ { + return n ? allocator_traits::allocate(alloc_, n) : pointer_{nullptr}; + } + constexpr auto allocate(size_type_ n, typename allocator_traits::const_void_pointer hint) -> pointer_ { + return n ? allocator_traits::allocate(alloc_, n, hint) : pointer_{nullptr}; + } + + constexpr auto uninitialized_fill_n(pointer_ first, size_type_ count, typename allocator_traits::value_type const& value) { + return adl_alloc_uninitialized_fill_n(alloc_, first, count, value); + } + + template + auto uninitialized_copy_n(It first, size_type count, pointer_ d_first) { +#if defined(__clang__) && defined(__CUDACC__) + if constexpr(!std::is_trivially_default_constructible_v::element_type> && !multi::force_element_trivial_default_construction::element_type>) { + adl_alloc_uninitialized_default_construct_n(alloc_, d_first, count); + } + return adl_copy_n(first, count, d_first); +#else + return adl_alloc_uninitialized_copy_n(alloc_, first, count, d_first); +#endif + } + + template + auto uninitialized_move_n(It first, size_type count, pointer_ d_first) { +#if defined(__clang__) && defined(__CUDACC__) + if constexpr(!std::is_trivially_default_constructible_v::element_type> && !multi::force_element_trivial_default_construction::element_type>) { + adl_alloc_uninitialized_default_construct_n(alloc_, d_first, count); + } + return adl_copy_n(std::make_move_iterator(first), count, d_first); +#else + return adl_alloc_uninitialized_move_n(alloc_, first, count, d_first); +#endif + } + + template + auto uninitialized_copy_n(EP&& ep, It first, size_type count, pointer_ d_first) { + return adl_uninitialized_copy_n(std::forward(ep), first, count, d_first); + } + + template + auto destroy_n(It first, size_type n) { return adl_alloc_destroy_n(this->alloc(), first, n); } + + public: + constexpr auto get_allocator() const -> allocator_type { return alloc_; } +}; + +} // end namespace detail + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template> // DummyAlloc mechanism allows using the convention array>, is an_allocator supports void template argument +struct static_array // NOLINT(fuchsia-multiple-inheritance) : multiple inheritance used for composition +: protected detail::array_allocator< + typename allocator_traits::template rebind_alloc> +, public array_ref::template rebind_alloc>::pointer> +, boost::multi::random_iterable::template rebind_alloc>> { + static_assert( + std::is_same_v< + std::remove_const_t::value_type>, + typename static_array::element_type> || + std::is_same_v< + std::remove_const_t::value_type>, + void>, // allocator template can be redundant or void (which can be a default for the allocator) + "allocator value type must match array value type" + ); + + protected: + using array_alloc = detail::array_allocator::template rebind_alloc>; + + public: + using detail::array_allocator::template rebind_alloc>::get_allocator; + + using allocator_type = typename detail::array_allocator::template rebind_alloc>::allocator_type; + using decay_type = array; + using layout_type = typename array_ref::pointer>::layout_type; + + using ref = array_ref< + T, D, + typename multi::allocator_traits::template rebind_alloc>::pointer>; + + auto operator new(std::size_t count) -> void* { return ::operator new(count); } + auto operator new(std::size_t count, void* ptr) -> void* { return ::operator new(count, ptr); } + + void operator delete(void* ptr) noexcept { ::operator delete(ptr); } // this overrides the deleted delete operator in reference (base) class subarray + + protected: + using alloc_traits = typename multi::allocator_traits; + + auto uninitialized_value_construct() { + return adl_alloc_uninitialized_value_construct_n(static_array::alloc(), this->base_, this->num_elements()); + } + + constexpr auto uninitialized_default_construct() { + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + return adl_alloc_uninitialized_default_construct_n(static_array::alloc(), this->base_, this->num_elements()); + } + } + + template auto uninitialized_copy_elements(It first) { + return array_alloc::uninitialized_copy_n(first, this->num_elements(), this->data_elements()); + } + + // template auto uninitialized_move_elements(It first) { + // return array_alloc::uninitialized_move_n(first, this->num_elements(), this->data_elements()); + // } + + template auto uninitialized_copy_elements(EP&& ep, It first) { + return array_alloc::uninitialized_copy_n(std::forward(ep), first, this->num_elements(), this->data_elements()); + } + + constexpr void destroy() { + if constexpr(!(std::is_trivially_destructible_v || multi::force_element_trivial_destruction)) { + array_alloc::destroy_n(this->data_elements(), this->num_elements()); + } + } + + void allocate() { + this->base_ = array_alloc::allocate(static_cast::size_type>(this->static_array::num_elements())); + } + + public: + using value_type = typename std::conditional_t< + (D > 1), // this parenthesis is needed + array, + typename static_array::element_type>; + + using typename ref::difference_type; + using typename ref::size_type; + + explicit static_array(allocator_type const& alloc) : array_alloc{alloc}, ref(nullptr, {}) {} + + using ref::operator(); + BOOST_MULTI_HD constexpr auto operator()() && -> decltype(auto) { return ref::element_moved(); } + + using ref::taked; + + constexpr auto taked(difference_type n) && -> decltype(auto) { return ref::taked(n).element_moved(); } + + using ref::dropped; + + constexpr auto dropped(difference_type n) && -> decltype(auto) { return ref::dropped(n).element_moved(); } + + static_array(static_array&& other) noexcept + : array_alloc{other.alloc()}, + ref{ + array_alloc::allocate(static_cast::size_type>(other.num_elements())), + other.extensions() + } { + adl_alloc_uninitialized_move_n( + this->alloc(), + other.data_elements(), + other.num_elements(), + this->data_elements() + ); + (void)std::move(other); + } + + constexpr static_array(decay_type&& other, allocator_type const& alloc) noexcept + : array_alloc{alloc}, ref(std::exchange(other.base_, nullptr), other.extensions()) { + std::move(other).layout_mutable() = typename static_array::layout_type(typename static_array::extensions_type{}); // = {}; careful! this is the place where layout can become invalid + } + + constexpr explicit static_array(decay_type&& other) noexcept + : static_array(std::move(other), allocator_type{}) {} // 6b + +#if __cplusplus >= 202002L && (!defined(__clang_major__) || (__clang_major__ != 10)) + template Sentinel = It, class = typename std::iterator_traits>::difference_type> + constexpr explicit static_array(It const& first, Sentinel const& last, allocator_type const& alloc) + : array_alloc{alloc} + , ref( + array_alloc::allocate(static_cast::size_type>(layout_type{index_extension(adl_distance(first, last)) * multi::extensions(*first)}.num_elements())), + index_extension(adl_distance(first, last)) * multi::extensions(*first) + ) { +#if defined(__clang__) && defined(__CUDACC__) + // TODO(correaa) add workaround for non-default constructible type and use adl_alloc_uninitialized_default_construct_n + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + adl_alloc_uninitialized_default_construct_n(static_array::alloc(), ref::data_elements(), ref::num_elements()); + } + adl_copy_n(first, last - first, ref::begin()); +#else + adl_alloc_uninitialized_copy(static_array::alloc(), first, last, ref::begin()); +#endif + } +#else + template>::difference_type> + constexpr explicit static_array(It const& first, It const& last, allocator_type const& alloc) + : array_alloc{alloc}, + ref( + array_alloc::allocate(static_cast::size_type>(layout_type{index_extension(adl_distance(first, last)) * multi::extensions(*first)}.num_elements())), + index_extension(adl_distance(first, last)) * multi::extensions(*first) + ) { +#if defined(__clang__) && defined(__CUDACC__) + // TODO(correaa) add workaround for non-default constructible type and use adl_alloc_uninitialized_default_construct_n + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + adl_alloc_uninitialized_default_construct_n(static_array::alloc(), ref::data_elements(), ref::num_elements()); + } + adl_copy_n(first, last - first, ref::begin()); +#else + adl_alloc_uninitialized_copy(static_array::alloc(), first, last, ref::begin()); +#endif + } +#endif + +#if __cplusplus >= 202002L && (!defined(__clang_major__) || (__clang_major__ != 10)) + template Sentinel, class = typename std::iterator_traits>::difference_type> + constexpr explicit static_array(It const& first, Sentinel const& last) + : static_array(first, last, allocator_type{}) {} +#else + template>::difference_type> + constexpr explicit static_array(It const& first, It const& last) : static_array(first, last, allocator_type{}) {} +#endif + +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) // && !defined(_MSC_VER) + template< + class Range, class = std::enable_if_t>{}>, + class = decltype(std::declval().begin()), + class = decltype(std::declval().end()), + // class = decltype(/*static_array*/ (std::declval().begin() - std::declval().end())), // instantiation of static_array here gives a compiler error in 11.0, partially defined type? + class = std::enable_if_t::value>> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 + requires std::is_convertible_v>>, T> explicit static_array(Range const& rng) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax // NOSONAR + : static_array({static_cast(rng.size()), static_cast((*rng.begin()).size())}) { + static_assert(D == 2); + // reextent({rng.size(), rng.begin()->size()}); + auto [is, js] = this->extensions(); + auto out1 = std::ranges::begin(rng); + for(auto i : is) { + assert((*out1).size() == (*rng.begin()).size()); + auto const& out1_range = *out1; + auto out2 = std::ranges::begin(out1_range); + for(auto j : js) { // NOLINT(altera-unroll-loops) TODO(correa) change to algorithm applied on elements + (*this)[i][j] = *out2; // rng[i][j]; + ++out2; + } + ++out1; + } + } +#endif + + template< + class Range, class = std::enable_if_t>{}>, + class = decltype(std::declval().begin()), + class = decltype(std::declval().end()), + // class = decltype(/*static_array*/ (std::declval().begin() - std::declval().end())), // instantiation of static_array here gives a compiler error in 11.0, partially defined type? + class = std::enable_if_t::value>> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 + // cppcheck-suppress noExplicitConstructor ; because I want to use equal for lazy assigments form range-expressions // NOLINTNEXTLINE(runtime/explicit) + static_array(Range const& rng) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax // NOSONAR + : static_array(std::begin(rng), std::end(rng)) {} // Sonar: Prefer free functions over member functions when handling objects of generic type "Range". + + template + auto uninitialized_fill_elements(TT const& value) { + return array_alloc::uninitialized_fill_n(this->data_elements(), this->num_elements(), value); + } + + template + static_array(array_ref const& other, allocator_type const& alloc) + : array_alloc{alloc}, + ref{ + array_alloc::allocate(static_cast::size_type>(other.num_elements())), + other.extensions() + } { +#if defined(__clang__) && defined(__CUDACC__) + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + adl_alloc_uninitialized_default_construct_n(static_array::alloc(), this->data_elements(), this->num_elements()); + } + adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); +#else + adl_alloc_uninitialized_copy_n(static_array::alloc(), other.data_elements(), other.num_elements(), this->data_elements()); +#endif + } + + static_array(typename static_array::extensions_type extensions, typename static_array::element_type const& elem, allocator_type const& alloc) // 2 + : array_alloc{alloc}, ref{array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t{extensions}.num_elements()), nullptr), extensions} { + array_alloc::uninitialized_fill_n(this->data_elements(), static_cast::size_type>(this->num_elements()), elem); + } + + template + explicit static_array( + Element const& elem, allocator_type const& alloc, + std::enable_if_t && (D == 0), int> /*dummy*/ = 0 // NOLINT(fuchsia-default-arguments-declarations) for classic sfinae, needed by MSVC? + ) + : static_array(typename static_array::extensions_type{}, elem, alloc) {} + + constexpr static_array(typename static_array::extensions_type exts, typename static_array::element_type const& elem) + : array_alloc{}, + array_ref::template rebind_alloc>::pointer>( + exts, + array_alloc::allocate( + static_cast::size_type>(typename static_array::layout_t(exts).num_elements()), + nullptr + ) + ) { + if constexpr(!std::is_trivially_default_constructible_v) { + array_alloc::uninitialized_fill_n(this->base(), static_cast::size_type>(this->num_elements()), elem); + } else { // this workaround allows constexpr arrays for simple types + adl_fill_n(this->base(), static_cast::size_type>(this->num_elements()), elem); + } + } + + template().extensions()), std::enable_if_t, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + explicit static_array(typename static_array::index_extension const& extension, ValueType const& value, allocator_type const& alloc) // fill constructor + : array_alloc{alloc}, ref(array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t(extension * value.extensions()).num_elements())), extension * value.extensions()) { + static_assert(std::is_trivially_default_constructible_v || multi::force_element_trivial_default_construction); // TODO(correaa) not implemented for non-trivial types, + adl_fill_n(this->begin(), this->size(), value); // TODO(correaa) implement via .elements()? substitute with uninitialized version of fill, uninitialized_fill_n? + } + + template().extensions()), std::enable_if_t, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + explicit static_array(typename static_array::index_extension const& extension, ValueType const& value) // fill constructor + : static_array(extension, value, allocator_type{}) {} + + explicit static_array(::boost::multi::extensions_t const& extensions, allocator_type const& alloc) + : array_alloc{alloc}, ref(array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t{extensions}.num_elements())), extensions) { + uninitialized_default_construct(); + } + + explicit static_array(::boost::multi::extensions_t const& exts) + : static_array(exts, allocator_type{}) {} + + template), int> = 0, // NOLINT(modernize-use-constraints) for C++20 + std::enable_if_t || multi::force_element_trivial_default_construction), int> = 0> // NOLINT(modernize-use-constraints) for C++20 + explicit constexpr static_array(::boost::multi::extensions_t const& extensions, UninitilazedTag /*unused*/, allocator_type const& alloc) + : static_array(extensions, alloc) {} + + template), int> = 0, // NOLINT(modernize-use-constraints) for C++20 + std::enable_if_t && !multi::force_element_trivial_default_construction), int> = 0> // NOLINT(modernize-use-constraints) for C++20 + [[deprecated("****element type cannot be partially formed (uninitialized), if you insists that this type should be treated as trivially constructible, consider opting-in to multi::force_trivial_default_construction at your own risk****")]] + explicit constexpr static_array(::boost::multi::extensions_t const& extensions, UninitilazedTag /*unusued*/) = delete /*[["****element type cannot be partially formed (uninitialized), if you insists that this type should be treated as trivially constructible, consider opting-in to multi::force_trivial_default_construction at your own risk****")]]*/; + + template), int> = 0, // NOLINT(modernize-use-constraints) for C++20 + std::enable_if_t && !multi::force_element_trivial_default_construction), int> = 0> // NOLINT(modernize-use-constraints) for C++20 + [[deprecated("****element type cannot be partially formed (uninitialized), if you insists that this type should be treated as trivially constructible, consider opting-in to multi::force_trivial_default_construction at your own risk****")]] + explicit constexpr static_array(::boost::multi::extensions_t const& extensions, UninitilazedTag /*unused*/, allocator_type const& /*alloc*/) = delete /*[["****element type cannot be partially formed (uninitialized), if you insists that this type should be treated as trivially constructible, consider opting-in to multi::force_trivial_default_construction at your own risk****"]]*/; + + template), int> = 0, // NOLINT(modernize-use-constraints) for C++20 + std::enable_if_t || multi::force_element_trivial_default_construction), int> = 0> // NOLINT(modernize-use-constraints) for C++20 + explicit constexpr static_array(::boost::multi::extensions_t const& extensions, UninitilazedTag /*unusued*/) : static_array(extensions) {} + + template::element_type>{}>, class = decltype(adl_copy(std::declval const&>().begin(), std::declval const&>().end(), std::declval()))> + constexpr static_array(multi::const_subarray const& other, allocator_type const& alloc) + : array_alloc{alloc}, + ref( + array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t{other.extensions()}.num_elements())), + other.extensions() + ) { + adl_alloc_uninitialized_copy_n(static_array::alloc(), other.elements().begin(), this->num_elements(), this->data_elements()); + } + + template // TODO(correaa) make more generic, e.g.: take ArrayWithElementsLike + constexpr static_array(multi::f_extensions_t const& other, allocator_type const& alloc) + : array_alloc{alloc}, + ref( + array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t{other.extensions()}.num_elements())), + other.extensions() + ) { + adl_alloc_uninitialized_copy_n(static_array::alloc(), other.elements().begin(), this->num_elements(), this->data_elements()); + } + + template // ArrayElementsLike, class = typename ArrayElementsLike::elements_t> + // cppcheck-suppress noExplicitConstructor // NOLINTNEXTLINE(runtime/explicit) + constexpr static_array(multi::f_extensions_t const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) to allow terse syntax + : static_array(other, allocator_type{}) {} + + template::element_type>{}>, class = decltype(adl_copy(std::declval const&>().begin(), std::declval const&>().end(), std::declval()))> + constexpr static_array(multi::subarray&& other, allocator_type const& alloc) + : array_alloc{alloc}, + ref( + array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t{other.extensions()}.num_elements())), + other.extensions() + ) { + adl_alloc_uninitialized_copy_n(static_array::alloc(), std::move(other).elements().begin(), this->num_elements(), this->data_elements()); + } + + template< + class TT, class EElementPtr, class LLayout, std::enable_if_t&>().base()), T>, int> = 0, + class = decltype(adl_copy(std::declval const&>().begin(), std::declval const&>().end(), std::declval()))> + explicit static_array(multi::const_subarray const& other) + : static_array(other, allocator_type{}) {} + + template< + class TT, class EElementPtr, class LLayout, std::enable_if_t const&>().base()), T>, int> = 0, + class = decltype(adl_copy(std::declval const&>().begin(), std::declval const&>().end(), std::declval()))> + // cppcheck-suppress noExplicitConstructor // NOLINTNEXTLINE(runtime/explicit) + constexpr /*implicit*/ static_array(multi::const_subarray const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR + : static_array(other, allocator_type{}) {} + + template< + class TT, class EElementPtr, class LLayout, std::enable_if_t const&>().base()), T>, int> = 0, + class = decltype(adl_copy(std::declval const&>().begin(), std::declval const&>().end(), std::declval()))> + // cppcheck-suppress noExplicitConstructor // NOLINTNEXTLINE(runtime/explicit) + constexpr /*implicit*/ static_array(multi::subarray&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR + : static_array(std::move(other), allocator_type{}) {} + + // cppcheck-suppress noExplicitConstructor ; see below + constexpr static_array(multi::subarray const&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : static_array(other, allocator_type{}) {} + + // cppcheck-suppress noExplicitConstructor ; see below + constexpr static_array(multi::const_subarray const&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : static_array(other, allocator_type{}) {} + + // cppcheck-suppress noExplicitConstructor ; see below + constexpr static_array(multi::subarray&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR + : static_array(std::move(other), allocator_type{}) {} + + template&>().base()), T>, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + // cppcheck-suppress noExplicitConstructor ; to allow terse syntax + /*mplct*/ static_array(array_ref& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR + : array_alloc{}, ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { + static_array::uninitialized_copy_elements(other.data_elements()); + } + + template&>().base()), T>, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + explicit static_array(array_ref& other) // moLINT(fuchsia-default-arguments-declarations) + : array_alloc{}, ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { + static_array::uninitialized_copy_elements(other.data_elements()); + } + + // NOLINTNEXTLINE(modernize-use-constraints) TODO(correaa) for C++20 + template&&>().base()), T>, int> = 0> + // cppcheck-suppress noExplicitConstructor ; to allow terse syntax + /*mplct*/ static_array(array_ref&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR + : array_alloc{}, ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { + assert(this->stride() != 0); + static_array::uninitialized_copy_elements(std::move(other).data_elements()); + } + + template&&>().base()), T>, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + explicit static_array(array_ref&& other) // NOLINT(fuchsia-default-arguments-declarations) + : array_alloc{}, ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { + assert(this->stride() != 0); + static_array::uninitialized_copy_elements(std::move(other).data_elements()); + } + + // NOLINTNEXTLINE(modernize-use-constraints) TODO(correaa) for C++20 + template const&>().base()), T>, int> = 0> + // cppcheck-suppress noExplicitConstructor ; to allow terse syntax + /*mplct*/ static_array(array_ref const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR + : array_alloc{}, ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { + assert(this->stride() != 0); + static_array::uninitialized_copy_elements(other.data_elements()); + } + + template const&>().base()), T>, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + explicit static_array(array_ref const& other) // NOLINT(fuchsia-default-arguments-declarations) + : array_alloc{}, + ref( + array_alloc::allocate(static_cast::size_type>(other.num_elements())), + other.extensions() + ) { + assert(this->stride() != 0); + static_array::uninitialized_copy_elements(std::move(other).data_elements()); + } + + static_array(static_array const& other) // 5b + : array_alloc{ + multi::allocator_traits::select_on_container_copy_construction(other.alloc()) + }, + ref{array_alloc::allocate(static_cast::size_type>(other.num_elements()) //, + ), + other.extensions()} { + assert(this->stride() != 0); + uninitialized_copy_elements(other.data_elements()); + } + + template, int> = 0> // NOLINT(modernize-use-constraints,modernize-type-traits) TODO(correaa) for C++20 + static_array(ExecutionPolicy&& policy, static_array const& other) + : array_alloc{multi::allocator_traits::select_on_container_copy_construction(other.alloc())}, ref{array_alloc::allocate(static_cast::size_type>(other.num_elements()), other.data_elements()), extensions(other)} { + assert(this->stride() != 0); + uninitialized_copy_elements(std::forward(policy), other.data_elements()); + } + + // cppcheck-suppress noExplicitConstructor ; to allow assignment-like construction of nested arrays + constexpr static_array(std::initializer_list::value_type> values) + : static_array{(values.size() == 0) ? array() : array(values.begin(), values.end())} {} // construct all with default constructor and copy to special memory at the end + + // template || (D == 2), int> =0> + // constexpr static_array(std::initializer_list> il) + // : static_array{(il.size() == 0) ? array() : array(il.begin(), il.end())} {} // construct all with default constructor and copy to special memory at the end + + static_array( + std::initializer_list::value_type> values, + allocator_type const& alloc + ) + : static_array{(values.size() == 0) ? static_array() : static_array(values.begin(), values.end()), alloc} {} + + template + constexpr explicit static_array(TT (&array)[N]) // @SuppressWarnings(cpp:S5945) NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backward compatibility // NOSONAR + : static_array(std::begin(array), std::end(array)) { + assert(this->stride() != 0); + } + + constexpr auto begin() const& noexcept -> typename static_array::const_iterator { return ref::begin(); } + constexpr auto end() const& noexcept -> typename static_array::const_iterator { return ref::end(); } + + constexpr auto begin() && noexcept -> typename static_array::move_iterator { return ref::begin(); } + constexpr auto end() && noexcept -> typename static_array::move_iterator { return ref::end(); } + + constexpr auto begin() & noexcept -> typename static_array::iterator { return ref::begin(); } + constexpr auto end() & noexcept -> typename static_array::iterator { return ref::end(); } + + using ref::operator[]; + + BOOST_MULTI_HD constexpr auto operator[](index idx) && -> decltype(auto) { + return multi::move(ref::operator[](idx)); + } + + constexpr auto max_size() const noexcept { return static_cast(multi::allocator_traits::max_size(this->alloc())); } // TODO(correaa) divide by nelements in under-dimensions? + + protected: + constexpr void deallocate() { + assert(this->stride() != 0); + if(this->num_elements()) { + multi::allocator_traits::deallocate(this->alloc(), this->base_, static_cast::size_type>(this->num_elements())); + } + } + + void clear() noexcept { + this->destroy(); + deallocate(); + this->layout_mutable() = typename static_array::layout_type(typename static_array::extensions_type{}); + assert(this->stride() != 0); + } + + public: + constexpr static_array() noexcept + : array_alloc{}, ref(nullptr, typename static_array::extensions_type{}) { + assert(this->stride() != 0); + assert(this->size() == 0); + } + +#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + constexpr ~static_array() +#else + ~static_array() +#endif + { + assert(this->stride() != 0); + destroy(); + assert(this->stride() != 0); + deallocate(); + } + + using element_const_ptr = typename std::pointer_traits::template rebind; + using element_move_ptr = multi::move_ptr; + + using reference = std::conditional_t< + (D > 1), + subarray, + std::conditional_t< + D == 1, + typename std::iterator_traits::reference, + void>>; + + using const_reference = std::conditional_t< + (D > 1), + const_subarray, // TODO(correaa) should be const_reference, but doesn't work witn rangev3? + std::conditional_t< + D == 1, + decltype(*std::declval()), + void>>; + + using iterator = multi::array_iterator; + using const_iterator = multi::array_iterator; + + friend auto get_allocator(static_array const& self) -> allocator_type { return self.get_allocator(); } + + // cppcheck-suppress duplInheritedMember ; to override + BOOST_MULTI_HD constexpr auto data_elements() const& -> element_const_ptr { return this->base_; } + + // cppcheck-suppress duplInheritedMember ; to override + BOOST_MULTI_HD constexpr auto data_elements() & -> typename static_array::element_ptr { return this->base_; } + + // cppcheck-suppress duplInheritedMember ; to override + BOOST_MULTI_HD constexpr auto data_elements() && -> typename static_array::element_move_ptr { return std::make_move_iterator(this->base_); } + + BOOST_MULTI_FRIEND_CONSTEXPR auto data_elements(static_array const& self) { return self.data_elements(); } + BOOST_MULTI_FRIEND_CONSTEXPR auto data_elements(static_array& self) { return self.data_elements(); } + BOOST_MULTI_FRIEND_CONSTEXPR auto data_elements(static_array&& self) { return std::move(self).data_elements(); } + + constexpr auto base() & -> typename static_array::element_ptr { return ref::base(); } + constexpr auto base() const& -> typename static_array::element_const_ptr { return typename static_array::element_const_ptr{ref::base()}; } + + constexpr auto origin() & -> typename static_array::element_ptr { return ref::origin(); } + constexpr auto origin() const& -> typename static_array::element_const_ptr { return ref::origin(); } + + BOOST_MULTI_FRIEND_CONSTEXPR auto origin(static_array& self) -> typename static_array::element_ptr { return self.origin(); } + BOOST_MULTI_FRIEND_CONSTEXPR auto origin(static_array const& self) -> typename static_array::element_const_ptr { return self.origin(); } + + template + auto operator=(multi::const_subarray const& other) -> static_array& { + ref::operator=(other); // TODO(correaa) : protect for self assigment + assert(this->stride() != 0); + return *this; + } + auto operator=(static_array const& other) & -> static_array& { + if(std::addressof(other) == this) { + return *this; + } // cert-oop54-cpp + assert(other.extensions() == this->extensions()); + adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); + assert(this->stride() != 0); + return *this; + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + constexpr auto operator=(static_array&& other) noexcept -> static_array& { // lints (cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + assert(extensions(other) == static_array::extensions()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : allow a constexpr-friendly assert + adl_move(other.data_elements(), other.data_elements() + other.num_elements(), this->data_elements()); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) there is no std::move_n algorithm + assert(this->stride() != 0); + return *this; + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + template + auto operator=(static_array const& other) & -> static_array& { + assert(extensions(other) == static_array::extensions()); + adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); + return *this; + } + + template + // cppcheck-suppress duplInheritedMember ; to override + void serialize(Archive& arxiv, unsigned int const version) { ref::serialize(arxiv, version); } + + private: + void swap_(static_array& other) noexcept { operator()().swap(other()); } + + public: + friend void swap(static_array& lhs, static_array& rhs) noexcept { lhs.swap_(rhs); } +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template +struct static_array // NOLINT(fuchsia-multiple-inheritance) : design +: protected detail::array_allocator +, public array_ref::allocator_type>::pointer> { + static_assert(std::is_same_v::value_type, typename static_array::element_type>, "allocator value type must match array value type"); + + private: + using array_alloc = detail::array_allocator; + + public: + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + // NOLINTNEXTLINE(runtime/operator) + constexpr auto operator&() && -> static_array* = delete; // NOSONAR(cpp:S877) NOLINT(google-runtime-operator) : delete to avoid taking address of temporary + // NOLINTNEXTLINE(runtime/operator) + constexpr auto operator&() & -> static_array* { return this; } // NOSONAR(cpp:S877) NOLINT(google-runtime-operator) : override from base + // NOLINTNEXTLINE(runtime/operator) + constexpr auto operator&() const& -> static_array const* { return this; } // NOSONAR(cpp:S877) NOLINT(google-runtime-operator) : override from base + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + using array_alloc::get_allocator; + using allocator_type = typename static_array::allocator_type; + using decay_type = array; + + template + void assign(Ptr data) & { + if(data) { + assert(this->num_elements() == 1); + adl_copy_n(data, this->num_elements(), this->base()); + } + } + + template< + class Singleton, std::enable_if_t && !std::is_same_v, int> = 0, + class = decltype(adl_copy_n(&std::declval(), 1, typename static_array::element_ptr{}))> + auto operator=(Singleton const& single) -> static_array& { + assign(&single); + return *this; + } + + protected: + using alloc_traits = typename multi::allocator_traits; + using ref = array_ref::template rebind_alloc>::pointer>; + + auto uninitialized_value_construct() { + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + return adl_alloc_uninitialized_value_construct_n(static_array::alloc(), this->base_, this->num_elements()); + } + } + + template auto uninitialized_copy(It first) { +#if defined(__clang__) && defined(__CUDACC__) + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + adl_alloc_uninitialized_default_construct_n(this->alloc(), this->data_elements(), this->num_elements()); + } + return adl_copy(first, this->num_elements(), this->data_elements()); +#else + return adl_alloc_uninitialized_copy_n(this->alloc(), first, this->num_elements(), this->data_elements()); +#endif + } + + template + auto uninitialized_move(It first) { + return adl_alloc_uninitialized_move_n(this->alloc(), first, this->num_elements(), this->data_elements()); + } + + constexpr void destroy() { + if constexpr(!(std::is_trivially_destructible_v || multi::force_element_trivial_destruction)) { + array_alloc::destroy_n(this->data_elements(), this->num_elements()); + } + } + + public: + using typename ref::difference_type; + using typename ref::size_type; + using typename ref::value_type; + constexpr explicit static_array(allocator_type const& alloc) : array_alloc{alloc} {} + + constexpr static_array(decay_type&& other, allocator_type const& alloc) // 6b + : array_alloc{alloc}, ref{other.base_, other.extensions()} { + std::move(other).ref::layout_t::operator=({}); + } + + using ref::operator==; + using ref::operator!=; + + static_array( + typename static_array::extensions_type const& extensions, + typename static_array::element const& elem, allocator_type const& alloc + ) + : array_alloc{alloc}, + ref(static_array::allocate( + static_cast::size_type>( + typename static_array::layout_t{extensions}.num_elements() + ) + ), + extensions) { + uninitialized_fill(elem); + } + + static_array(typename static_array::element_type const& elem, allocator_type const& alloc) + : static_array(typename static_array::extensions_type{}, elem, alloc) {} + + template + explicit static_array(multi::const_subarray const& other, allocator_type const& alloc) + : array_alloc{alloc}, ref(static_array::allocate(other.num_elements()), extensions(other)) { + assert(other.num_elements() <= 1); + if(other.num_elements()) { +#if defined(__clang__) && defined(__CUDACC__) + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + adl_alloc_uninitialized_default_construct_n(static_array::alloc(), this->data_elements(), this->num_elements()); + } + adl_copy(other.base(), other.base() + other.num_elements(), this->base()); +#else + adl_alloc_uninitialized_copy(static_array::alloc(), other.base(), other.base() + other.num_elements(), this->base()); +#endif + } + } + + template + explicit static_array(multi::static_array const& other, allocator_type const& alloc) // TODO(correaa) : call other constructor (above) + : array_alloc{alloc}, ref(static_array::allocate(static_cast::size_type>(other.num_elements())), extensions(other)) { +#if defined(__clang__) && defined(__CUDACC__) + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + adl_alloc_uninitialized_default_construct_n(static_array::alloc(), this->data_elements(), this->num_elements()); + } + adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); +#else + adl_alloc_uninitialized_copy_n(static_array::alloc(), other.data_elements(), other.num_elements(), this->data_elements()); +#endif + } + + template + explicit static_array(multi::static_array const& other) + : static_array(other, allocator_type{}) {} + + auto uninitialized_fill(typename static_array::element_type const& elem) { + array_alloc::uninitialized_fill_n( + this->base_, + static_cast::size_type>(this->num_elements()), + elem + ); + } + + template + auto operator=(multi::const_subarray const& other) -> static_array& { + adl_copy_n(other.base(), 1, this->base()); + return *this; + } + + static_array( + typename static_array::extensions_type const& extensions, + typename static_array::element_type const& elem + ) // 2 + : array_alloc{}, ref(static_array::allocate(static_cast::size_type>(typename static_array::layout_t{extensions}.num_elements()), nullptr), extensions) { + uninitialized_fill(elem); + } + + static_array() : static_array(multi::iextensions<0>{}) {} // TODO(correaa) a noexcept will force a partially formed state for zero dimensional arrays + + explicit static_array(typename static_array::element_type const& elem) + : static_array(multi::iextensions<0>{}, elem) {} + + template< + class Singleton, std::enable_if_t && !std::is_same_v, int> = 0, // NOLINT(modernize-type-traits) for C++20 + class = decltype(adl_copy_n(&std::declval(), 1, typename static_array::element_ptr{}))> + // cppcheck-suppress noExplicitConstructor ; to allow terse syntax // NOLINTNEXTLINE(runtime/explicit) + /*implict*/ static_array(Singleton const& single) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) this is used by the + : ref(static_array::allocate(1), typename static_array::extensions_type{}) { +#if defined(__clang__) && defined(__CUDACC__) + if constexpr(!std::is_trivially_default_constructible_v && !multi::force_element_trivial_default_construction) { + adl_alloc_uninitialized_default_construct_n(static_array::alloc(), this->data_elements(), this->num_elements()); + } + adl_copy_n(&single, typename multi::allocator_traits::size_type{1}, this->data_elements()); +#else + adl_alloc_uninitialized_copy_n(static_array::alloc(), &single, typename multi::allocator_traits::size_type{1}, this->data_elements()); +#endif + } + + template>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + explicit static_array(typename static_array::index_extension const& extension, ValueType const& value, allocator_type const& alloc) // 3 + : static_array(extension * extensions(value), alloc) { + assert(this->stride() != 0); + using std::fill; + fill(this->begin(), this->end(), value); + } + + template>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + explicit static_array(typename static_array::index_extension const& extension, ValueType const& value) // 3 + : static_array(extension, value, allocator_type{}) {} + // : static_array(extension * extensions(value)) { // TODO(correaa) : call other constructor (above) + // assert(this->stride() != 0); + // using std::fill; + // fill(this->begin(), this->end(), value); + // } + + explicit static_array(typename static_array::extensions_type const& extensions, allocator_type const& alloc) // 3 + : array_alloc{alloc}, ref(static_array::allocate(typename static_array::layout_t{extensions}.num_elements()), extensions) { + // assert(this->stride() != 0); + uninitialized_value_construct(); + } + explicit static_array(typename static_array::extensions_type const& extensions) // 3 + : static_array(extensions, allocator_type{}) { + // assert(this->stride() != 0); + } + + static_array(static_array const& other, allocator_type const& alloc) // 5b + : array_alloc{alloc}, ref(static_array::allocate(other.num_elements()), extensions(other)) { + assert(this->stride() != 0); + uninitialized_copy_(other.data_elements()); + } + + static_array(static_array const& other) // 5b + : array_alloc{other.get_allocator()}, ref{static_array::allocate(other.num_elements(), other.data_elements()), {}} { + assert(this->stride() != 0); + uninitialized_copy(other.data_elements()); + } + + static_array(static_array&& other) noexcept + : array_alloc{other.get_allocator()}, ref(std::exchange(other.base_, nullptr), other.extensions()) { + other.layout_mutable() = {}; + // other.layout_t<0>::operator=({}); + // , ref(static_array::allocate(static_cast::size_type>(other.num_elements()), other.data_elements()), other.extensions()) { + // adl_alloc_uninitialized_move_n( + // this->alloc(), + // other.data_elements(), + // other.num_elements(), + // this->data_elements() + // ); + (void)std::move(other); + } + + protected: + void deallocate() { // TODO(correaa) : move this to detail::array_allocator + if(this->num_elements() && this->base_) { + multi::allocator_traits::deallocate(this->alloc(), this->base_, static_cast::size_type>(this->num_elements())); + } + } + void clear() noexcept { + this->destroy(); + deallocate(); + layout_t<0>::operator=({}); + } + + public: + ~static_array() noexcept { + this->destroy(); + deallocate(); + } + using element_const_ptr = typename std::pointer_traits::template rebind; + + BOOST_MULTI_FRIEND_CONSTEXPR auto get_allocator(static_array const& self) -> allocator_type { return self.get_allocator(); } + + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + constexpr auto base() & -> typename static_array::element_ptr { return ref::base(); } + constexpr auto base() const& -> typename static_array::element_const_ptr { return ref::base(); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + BOOST_MULTI_FRIEND_CONSTEXPR auto base(static_array& self) -> typename static_array::element_ptr { return self.base(); } + BOOST_MULTI_FRIEND_CONSTEXPR auto base(static_array const& self) -> typename static_array::element_const_ptr { return self.base(); } + + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + constexpr auto origin() & -> typename static_array::element_ptr { return ref::origin(); } + constexpr auto origin() const& -> typename static_array::element_const_ptr { return ref::origin(); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + BOOST_MULTI_FRIEND_CONSTEXPR auto origin(static_array& self) -> typename static_array::element_ptr { return self.origin(); } + BOOST_MULTI_FRIEND_CONSTEXPR auto origin(static_array const& self) -> typename static_array::element_const_ptr { return self.origin(); } + + // NOSONAR + constexpr operator typename std::iterator_traits::reference() const& { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + return *(this->base_); + } + + // NOSONAR + constexpr operator std::add_rvalue_reference_t::reference>() && { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + return std::move(*(this->base_)); + } + + // NOSONAR + constexpr operator typename std::iterator_traits::reference() & { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + return *(this->base_); + } + + template, int> = 0> // NOLINT(modernize-use-constraints) + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr explicit operator OtherElement() const { + return static_cast(*(this->base_)); + } + + constexpr auto rotated() const& { // cppcheck-suppress duplInheritedMember ; to overwrite + typename static_array::layout_t new_layout = this->layout(); + new_layout.rotate(); + return subarray{new_layout, this->base_}; + } + + constexpr auto rotated() & { // cppcheck-suppress duplInheritedMember ; to overwrite + typename static_array::layout_t new_layout = this->layout(); + new_layout.rotate(); + return subarray{new_layout, this->base_}; + } + + constexpr auto rotated() && { // cppcheck-suppress duplInheritedMember ; to overwrite + typename static_array::layout_t new_layout = this->layout(); + new_layout.rotate(); + return subarray{new_layout, this->base_}; + } + + friend constexpr auto rotated(static_array& self) -> decltype(auto) { return self.rotated(); } + friend constexpr auto rotated(static_array const& self) -> decltype(auto) { return self.rotated(); } + + private: + constexpr auto unrotated_aux_() { + typename static_array::layout_t new_layout = *this; + new_layout.unrotate(); + return subarray{new_layout, this->base_}; + } + + public: + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + constexpr auto unrotated() & { return unrotated_aux_(); } + constexpr auto unrotated() const& { return unrotated_aux_().as_const(); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + friend constexpr auto unrotated(static_array& self) -> decltype(auto) { return self.unrotated(); } + friend constexpr auto unrotated(static_array const& self) -> decltype(auto) { return self.unrotated(); } + + constexpr auto operator=(static_array const& other) -> static_array& { + assert(extensions(other) == static_array::extensions()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : allow a constexpr-friendly assert + if(this == &other) { + return *this; + } // lints (cert-oop54-cpp) : handle self-assignment properly + adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); + return *this; + } + + private: + constexpr auto equal_extensions_if_(std::true_type /*true */, static_array const& other) { return this->extensions() == extensions(other); } + constexpr auto equal_extensions_if_(std::false_type /*false*/, static_array const& /*other*/) { return true; } + + public: +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + constexpr auto operator=(static_array&& other) noexcept -> static_array& { + assert(equal_extensions_if_(std::integral_constant{}, other)); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : allow a constexpr-friendly assert + adl_move(other.data_elements(), other.data_elements() + other.num_elements(), this->data_elements()); // there is no std::move_n algorithm // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + template{}>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + auto operator=(static_array const& other) & -> static_array& { + assert(extensions(other) == static_array::extensions()); + adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); + return *this; + } + + constexpr explicit operator subarray() & { // cppcheck-suppress duplInheritedMember ; to overwrite + // cppcheck-suppress duplInheritedMember ; to overwrite + return this->template static_array_cast(); // cppcheck-suppress duplInheritedMember ; to overwrite + // return static_array_cast(*this); + } + + template + void serialize(Archive& arxiv, unsigned int const version) { // cppcheck-suppress duplInheritedMember ; to overwrite + ref::serialize(arxiv, version); + } +}; + +template +struct array : static_array { + using static_array::static_array; + + using static_array::operator=; + +#if !defined(__NVCOMPILER) || (__NVCOMPILER_MAJOR__ > 22 || (__NVCOMPILER_MAJOR__ == 22 && __NVCOMPILER_MINOR__ > 5)) // bug in nvcc 22.5: error: "operator=" has already been declared in the current scope + template + auto operator=(multi::array const& other) & -> array& { + if(other.base()) { + adl_copy_n(other.base(), other.num_elements(), this->base()); + } + return *this; + } + + template + auto operator=(multi::array const& other) && -> array&& { // NOLINT(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) should assigment return auto& ? + if(other.base()) { + adl_copy_n(other.base(), other.num_elements(), this->base()); + } + return std::move(*this); + } +#endif + + auto reextent(typename array::extensions_type const& /*empty_extensions*/) -> array& { + return *this; + } + + // cppcheck-suppress duplInheritedMember ; to overwrite // NOLINTNEXTLINE(runtime/operator) + constexpr auto operator&() && -> array* = delete; // NOLINT(google-runtime-operator) //NOSONAR delete operator&& defined in base class to avoid taking address of temporary +}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template +struct array : static_array { + using static_ = static_array; + + static_assert( + std::is_same_v::value_type, T> || std::is_same_v::value_type, void>, + "only exact type of array element or void (default) is allowed as allocator value type" + ); + + // cppcheck-suppress duplInheritedMember ; to override // NOLINTNEXTLINE(runtime/operator) + BOOST_MULTI_HD constexpr auto operator&() && -> array* = delete; // NOLINT(google-runtime-operator) //NOSONAR delete operator&& defined in base class to avoid taking address of temporary + // cppcheck-suppress duplInheritedMember ; to override // NOLINTNEXTLINE(runtime/operator) + BOOST_MULTI_HD constexpr auto operator&() & -> array* { return this; } // NOLINT(google-runtime-operator) //NOSONAR delete operator&& defined in base class to avoid taking address of temporary + // cppcheck-suppress duplInheritedMember ; to override // NOLINTNEXTLINE(runtime/operator) + BOOST_MULTI_HD constexpr auto operator&() const& -> array const* { return this; } // NOLINT(google-runtime-operator) //NOSONAR delete operator&& defined in base class to avoid taking address of temporary + + template> + void serialize(Archive& arxiv, unsigned int const version) { // cppcheck-suppress duplInheritedMember ; to override + auto extensions_ = this->extensions(); + + arxiv& ArTraits::make_nvp("extensions", extensions_); // don't try `using ArTraits::make_nvp`, make_nvp is a static member + if(this->extensions() != extensions_) { + clear(); + this->reextent(extensions_); + } + static_::serialize(arxiv, version); + } + + // vvv workaround for MSVC 14.3 and ranges, TODO(correaa) good solution would be to inherit from const_subarray + BOOST_MULTI_HD operator subarray const&() const { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + return reinterpret_cast const&>(*this); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + // move this to static_array + template< + class Range, std::enable_if_t>::value, int> = 0, + class = decltype(Range{std::declval(), std::declval()})> + constexpr explicit operator Range() const { // cppcheck-suppress duplInheritedMember ; to overwrite + // vvv Range{...} needed by Windows GCC? + return Range{this->begin(), this->end()}; // NOLINT(fuchsia-default-arguments-calls) e.g. std::vector(it, it, alloc = {}) + } + + // move this to static_array + template, int> = 0> // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,modernize-use-constraints) for C++20 + constexpr explicit operator TTN const&() const& { return this->template to_carray_(); } // cppcheck-suppress duplInheritedMember ; to override + + template, int> = 0> // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,modernize-use-constraints) for C++20 + constexpr explicit operator TTN&() && { return this->template to_carray_(); } // cppcheck-suppress duplInheritedMember ; to override + + template, int> = 0> // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,modernize-use-constraints) for C++20 + constexpr explicit operator TTN&() & { return this->template to_carray_(); } // cppcheck-suppress duplInheritedMember ; to override + + // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) false positive in clang-tidy 17-20 ? + using static_array::static_array; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) passing c-arrays to base + using typename static_array::value_type; + + // #if defined(_MSC_VER) + // explicit array(typename array::extensions_type const& exts) // NOTE(correaa) if you think you need to implement this for MSVC is because MSVC needs to be compiled in permissive- mode for C++17 + // : static_array(exts) { } + // #endif + + // cppcheck-suppress noExplicitConstructor ; to allow assignment-like construction of nested arrays + constexpr array(std::initializer_list::value_type> ilv) + : static_{ + (ilv.size() == 0) ? array() + : array(ilv.begin(), ilv.end()) + } { + } + + template< + class OtherT, + class = std::enable_if_t::value_type, OtherT> && !std::is_convertible_v::value_type> && (D == 1)>> // NOLINT(modernize-use-constraints,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) TODO(correaa) for C++20 + constexpr explicit array(std::initializer_list ilv) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) inherit explicitness of conversion from the elements + : static_{ + (ilv.size() == 0) ? array()() + : array(ilv.begin(), ilv.end()).element_transformed([](auto const& elem) noexcept { return static_cast(elem); }) + } {} + + array() = default; + array(array const&) = default; + + ~array() = default; + + auto reshape(typename array::extensions_type extensions) & -> array& { + typename array::layout_t const new_layout{extensions}; // TODO(correaa) implement move-reextent in terms of reshape + assert(new_layout.num_elements() == this->num_elements()); + this->layout_mutable() = new_layout; + assert(this->stride() != 0); + return *this; + } + + auto clear() noexcept -> array& { // cppcheck-suppress duplInheritedMember ; to override + static_::clear(); + assert(this->stride() != 0); + return *this; + } + friend auto clear(array& self) noexcept -> array& { return self.clear(); } + + BOOST_MULTI_FRIEND_CONSTEXPR auto data_elements(array const& self) { return self.data_elements(); } + BOOST_MULTI_FRIEND_CONSTEXPR auto data_elements(array& self) { return self.data_elements(); } + BOOST_MULTI_FRIEND_CONSTEXPR auto data_elements(array&& self) { return std::move(self).data_elements(); } + + // auto move() & -> subarray> { + // subarray> + // ret = multi::static_array_cast>(*this); + + // layout_t::operator=({}); + + // assert(this->stride() != 0); + // return ret; + // } + // friend auto move(array& self) -> subarray> { + // return self.move(); + // } + + friend BOOST_MULTI_HD constexpr auto move(array& self) -> decltype(auto) { return std::move(self); } + friend BOOST_MULTI_HD constexpr auto move(array&& self) -> decltype(auto) { return std::move(self); } + + // typename array::allocator_type + array(array&& other, Alloc const& alloc) noexcept + : static_array{std::move(other), alloc} { + assert(this->stride() != 0); + } + array(array&& other) noexcept : array{std::move(other), other.get_allocator()} { + assert(this->stride() != 0); + } + + friend auto get_allocator(array const& self) -> typename array::allocator_type { return self.get_allocator(); } + + void swap(array& other) noexcept { + using std::swap; + if constexpr(multi::allocator_traits::propagate_on_container_swap::value) { + swap(this->alloc(), other.alloc()); + } + swap(this->base_, other.base_); + swap( + this->layout_mutable(), + other.layout_mutable() + ); + assert(this->stride() != 0); + } + +#ifndef NOEXCEPT_ASSIGNMENT + auto operator=(array&& other) noexcept -> array& { + if(this == std::addressof(other)) { + return *this; + } + clear(); + this->base_ = other.base_; + if constexpr(multi::allocator_traits::propagate_on_container_move_assignment::value) { + this->alloc() = std::move(other.alloc()); + } + this->layout_mutable() = std::exchange(other.layout_mutable(), typename array::layout_type(typename array::extensions_type{})); + assert(this->stride() != 0); + assert(other.stride() != 0); + return *this; + } + + auto operator=(array const& other) -> array& { + if(array::extensions() == other.extensions()) { + if(this == &other) { + return *this; + } // required by cert-oop54-cpp + if constexpr(multi::allocator_traits::propagate_on_container_copy_assignment::value) { + this->alloc() = other.alloc(); + } + static_::operator=(other); + } else { + clear(); + if constexpr(multi::allocator_traits::propagate_on_container_copy_assignment::value) { + this->alloc() = other.alloc(); + } + this->layout_mutable() = other.layout(); + array::allocate(); + array::uninitialized_copy_elements(other.data_elements()); + } + return *this; + } +#else + auto operator=(array o) noexcept -> array& { return swap(o), *this; } +#endif + + template + auto operator=(multi::const_subarray const& other) -> array& { + if(array::extensions() == other.extensions()) { + static_::operator=(other); // TODO(correaa) : protect for self assigment + } else { + operator=(array{other}); + } + return *this; + } + + template + auto operator=(multi::array const& other) -> array& { + if(array::extensions() == other.extensions()) { + static_::operator=(other); + } else if(this->num_elements() == other.extensions().num_elements()) { + reshape(other.extensions()); + static_::operator=(other); + } else { + operator=(static_cast(other)); + } + return *this; + } + + template< + class Range, class = decltype(std::declval().operator=(std::declval())), + std::enable_if_t>::value, int> = 0, + std::enable_if_t>::value, int> = 0, + std::enable_if_t>, int> = 0> // NOLINT(modernize-use-constraints,modernize-type-traits) for C++20 + auto operator=(Range&& other) -> array& { + if(array::extensions() == other.extensions()) { + this->operator()() = std::forward(other); + } else if(this->num_elements() == other.extensions().num_elements()) { + reshape(other.extensions()); + this->operator()() = std::forward(other); + } else { + operator=(static_cast(std::forward(other))); + } + return *this; + } + + template< + class Range, class = decltype(std::declval().operator=(std::declval())), + std::enable_if_t>, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + auto from(Range&& other) -> array& { // TODO(correaa) : check that LHS is not read-only? + if(array::extensions() == other.extensions()) { + this->operator()() = other; + } else if(this->num_elements() == other.extensions().num_elements()) { + reshape(other.extensions()); + this->operator()() = other; + } else { + operator=(static_cast(std::forward(other))); + } + return *this; + } + + friend void swap(array& self, array& other) noexcept(true /*noexcept(self.swap(other))*/) { self.swap(other); } + + void assign(typename array::extensions_type extensions, typename array::element_type const& elem) { + if(array::extensions() == extensions) { + adl_fill_n(this->base_, this->num_elements(), elem); + } else { + this->clear(); + (*this).array::layout_t::operator=(layout_t{extensions}); + this->base_ = this->static_::array_alloc::allocate(this->num_elements(), nullptr); + adl_alloc_uninitialized_fill_n(this->alloc(), this->base_, this->num_elements(), elem); + } + } + + template + auto assign(It first, It last) -> array& { // cppcheck-suppress duplInheritedMember ; to overwrite + using std::all_of; + using std::next; + if(adl_distance(first, last) == this->size()) { + static_::ref::assign(first); + } else { + this->operator=(array(first, last)); + } + return *this; + } + void assign(std::initializer_list values) { + if(values.size() != 0) { + assign(values.begin(), values.end()); + } + } + + template auto assign(Range&& other) & -> decltype(assign(adl_begin(std::forward(other)), adl_end(std::forward(other)))) { + return assign(adl_begin(std::forward(other)), adl_end(std::forward(other))); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + } + + auto operator=(std::initializer_list values) -> array& { + if(values.size() == 0) { + this->clear(); + } else { + assign(values.begin(), values.end()); + } + return *this; + } + + auto reextent(typename array::extensions_type const& extensions) && -> array&& { + if(extensions == this->extensions()) { + return std::move(*this); + } + + auto new_layout = typename array::layout_t{extensions}; + + if(new_layout.num_elements() != this->layout().num_elements()) { + this->destroy(); + this->deallocate(); + + this->layout_mutable() = new_layout; // typename array::layout_t{extensions}; + this->base_ = this->static_::array_alloc::allocate( + static_cast::size_type>( + new_layout.num_elements() + ), + this->data_elements() // used as hint + ); + + if constexpr(!(std::is_trivially_default_constructible_v || multi::force_element_trivial_default_construction)) { + adl_alloc_uninitialized_value_construct_n(this->alloc(), this->base_, this->num_elements()); + } + } else { + this->layout_mutable() = new_layout; + } + + return std::move(*this); + } + + auto reextent(typename array::extensions_type const& extensions) & -> array& { + if(extensions == this->extensions()) { + return *this; + } + auto&& tmp = typename array::ref( + this->static_::array_alloc::allocate( + static_cast::size_type>( + typename array::layout_t{extensions}.num_elements() + ), + this->data_elements() // used as hint + ), + extensions + ); + if constexpr(!(std::is_trivially_default_constructible_v || multi::force_element_trivial_default_construction)) { + adl_alloc_uninitialized_value_construct_n(this->alloc(), tmp.data_elements(), tmp.num_elements()); + } + auto const is = intersection(this->extensions(), extensions); + tmp.apply(is) = this->apply(is); // TODO(correaa) : use (and implement) `.move();` + this->destroy(); + this->deallocate(); + this->base_ = tmp.base(); + this->layout_mutable() = tmp.layout(); + return *this; + } + + [[nodiscard]] constexpr auto operator+() const& { return array{*this}; } // cppcheck-suppress duplInheritedMember ; to overwrite + [[nodiscard]] constexpr auto operator+() && { return array{*this}; } // cppcheck-suppress duplInheritedMember ; to overwrite + + auto reextent(typename array::extensions_type const& exs, typename array::element_type const& elem) & -> array& { + if(exs == this->extensions()) { + return *this; + } + + // array tmp(x, e, this->get_allocator()); // TODO(correaa) opportunity missed to use hint allocation + // auto const is = intersection(this->extensions(), x); + // tmp.apply(is) = this->apply(is); + // swap(tmp); + + // implementation with hint + auto&& tmp = typename array::ref( + this->static_::array_alloc::allocate( + static_cast::size_type>(typename array::layout_t{exs}.num_elements()), + this->data_elements() // use as hint + ), + exs + ); + this->uninitialized_fill_n(tmp.data_elements(), static_cast::size_type>(tmp.num_elements()), elem); + auto const is = intersection(this->extensions(), exs); + tmp.apply(is) = this->apply(is); + this->destroy(); + this->deallocate(); + this->base_ = tmp.base(); // TODO(correaa) : use (and implement) `.move();` + this->layout_mutable() = tmp.layout(); + // (*this).array::layout_t::operator=(tmp.layout()); + + return *this; + } + // template constexpr auto reindex(Indices... idxs) && -> array&& { + // this->layout_mutable() = this->layout_mutable().creindex(idxs...); + // return std::move(*this); + // } + // template constexpr auto reindex(Indices... idxs) & -> array& { + // this->layout_mutable() = this->layout_mutable().creindex(idxs...); + // // this->layout_mutable().reindex(idxs...); + // return *this; + // } + + // ~array() { + // assert(this->stride() != 0); + // } +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __cpp_deduction_guides + +#define BOOST_MULTI_IL std::initializer_list // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing, TODO(correaa) use template typedef instead of macro + +// vvv MSVC 14.3 in c++17 mode needs paranthesis in dimensionality_type(d) +template static_array(BOOST_MULTI_IL) -> static_array(1U), std::allocator>; // MSVC needs the allocator argument error C2955: 'boost::multi::static_array': use of class template requires template argument list +template static_array(BOOST_MULTI_IL>) -> static_array(2U), std::allocator>; +template static_array(BOOST_MULTI_IL>>) -> static_array(3U), std::allocator>; +template static_array(BOOST_MULTI_IL>>>) -> static_array(4U), std::allocator>; +template static_array(BOOST_MULTI_IL>>>>) -> static_array(5U), std::allocator>; + +// TODO(correaa) add zero dimensional case? +template array(BOOST_MULTI_IL) -> array(1U)>; +template array(BOOST_MULTI_IL>) -> array(2U)>; +template array(BOOST_MULTI_IL>>) -> array(3U)>; +template array(BOOST_MULTI_IL>>>) -> array(4U)>; +template array(BOOST_MULTI_IL>>>>) -> array(5U)>; + +#undef BOOST_MULTI_IL + +template array(T[]) -> array(1U)>; // NOSONAR(cpp:S5945) NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + +// vvv these are necessary to catch {n, m, ...} notation (or single integer notation) +template>> array(iextensions<0>, T) -> array(0U)>; // TODO(correaa) use some std::allocator_traits instead of is_allocator +template>> array(iextensions<1>, T) -> array(1U)>; +template>> array(iextensions<2>, T) -> array(2U)>; +template>> array(iextensions<3>, T) -> array(3U)>; +template>> array(iextensions<4>, T) -> array(4U)>; +template>> array(iextensions<5>, T) -> array(5U)>; + +// generalization, will not work with naked {n, m, ...} notation (or single integer notation) +template>> +array(iextensions, T) -> array; + +template +array(MatrixRef) -> array; + +template +array(MatValues) -> array; + +template, class = std::enable_if_t>> /// , class Alloc = typename DT::allocator_type> +array(MatValues, Alloc) -> array; + +template array(subarray) -> array; + +template< + class Range, std::enable_if_t::value, int> = 0, + typename V = decltype(*::std::begin(std::declval())) + // typename V = typename std::iterator_traits()))>::value_type + > +array(Range) -> array; + +template +auto operator+(Reference&& ref) -> decltype(array(std::forward(ref))) { + return array(std::forward(ref)); +} + +#endif // ends defined(__cpp_deduction_guides) + +template +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility +auto decay(T const (&arr)[N]) noexcept -> multi::array, std::rank_v> { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + return multi::array_cref, std::rank_v>(data_elements(arr), extensions(arr)); +} + +template +struct array_traits { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + using reference = T&; + using element = std::remove_all_extents_t; // NOSONAR(cpp:S5945) NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + using decay_type = multi::array; +}; + +} // end namespace boost::multi + +namespace boost::multi::pmr { + +#ifdef BOOST_MULTI_HAS_MEMORY_RESOURCE +template +using array = boost::multi::array>; +#else +template +struct [[deprecated("no PMR allocator")]] array; // your version of C++ doesn't provide polymorphic_allocators +#endif + +} // end namespace boost::multi::pmr + +// common_reference for compatibility with ranges +#if defined(__cpp_lib_common_reference) || defined(__cpp_lib_ranges) +// TODO(correaa) achieve this by normal inheritance +// NOLINTBEGIN(cert-dcl58-cpp) +// template struct std::common_reference::basic_const_array &&, ::boost::multi::array &> { using type = typename ::boost::multi::array::basic_const_array &&; }; +// template struct std::common_reference::basic_const_array &&, ::boost::multi::array const&> { using type = typename ::boost::multi::array::basic_const_array &&; }; +// template struct std::common_reference< ::boost::multi::array &, typename ::boost::multi::array::basic_const_array &&> { using type = typename ::boost::multi::array::basic_const_array &&; }; +// template struct std::common_reference< ::boost::multi::array const&, typename ::boost::multi::array::basic_const_array &&> { using type = typename ::boost::multi::array::basic_const_array &&; }; +// template struct std::common_reference::basic_const_array , ::boost::multi::array &> { using type = typename ::boost::multi::array::basic_const_array ; }; +// template struct std::common_reference< ::boost::multi::array const&, typename ::boost::multi::array::basic_const_array const&> { using type = typename ::boost::multi::array::basic_const_array const&; }; +// template struct std::common_reference::basic_const_array const&, ::boost::multi::array const&> { using type = typename ::boost::multi::array::basic_const_array const&; }; + +// template struct std::common_reference::basic_const_array &, ::boost::multi::array &> { using type = typename ::boost::multi::array::basic_const_array &; }; +// template struct std::common_reference< ::boost::multi::array &, typename ::boost::multi::array::basic_const_array &> { using type = typename ::boost::multi::array::basic_const_array &; }; +// NOLINTEND(cert-dcl58-cpp) +#endif + +namespace boost::serialization { + +template struct version; // in case serialization was not included before + +template +struct version> { + using type = std::integral_constant; // TODO(correaa) use constexpr variable here, not a macro + // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) for backward compatibility with Boost Serialization + enum /*class value_t*/ { value = type::value }; // NOSONAR(cpp:S3642) // https://community.sonarsource.com/t/suppress-issue-in-c-source-file/43154/24 +}; + +} // end namespace boost::serialization + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#undef BOOST_MULTI_HD + +#endif // BOOST_MULTI_ARRAY_HPP_ diff --git a/include/boost/multi/array_ref.hpp b/include/boost/multi/array_ref.hpp new file mode 100644 index 000000000..1282062dd --- /dev/null +++ b/include/boost/multi/array_ref.hpp @@ -0,0 +1,3891 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_ARRAY_REF_HPP_ +#define BOOST_MULTI_ARRAY_REF_HPP_ + +#include +#include // IWYU pragma: export + +#include + +#if defined(__cplusplus) && (__cplusplus >= 202002L) && __has_include() +#include // IWYU pragma: keep +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4623) // assignment operator was implicitly defined as deleted +#pragma warning(disable : 4626) // assignment operator was implicitly defined as deleted +#pragma warning(disable : 4625) // copy constructor was implicitly defined as deleted +#endif + +namespace boost::multi { + +template +inline constexpr bool force_element_trivial = false; + +template +inline constexpr bool force_element_trivial_destruction = force_element_trivial; + +template +inline constexpr bool force_element_trivial_default_construction = force_element_trivial; + +#ifdef _BOOST_MULTI_FORCE_TRIVIAL_STD_COMPLEX +template +inline constexpr bool force_element_trivial> = std::is_trivial_v; + +template +inline constexpr bool force_element_trivial_destruction> = std::is_trivially_default_constructible_v; + +template +inline constexpr bool force_element_trivial_default_construction> = std::is_trivially_destructible_v; + +template<> inline constexpr bool force_element_trivial> = true; +template<> inline constexpr bool force_element_trivial_default_construction> = true; +template<> inline constexpr bool force_element_trivial_destruction> = true; + +template<> inline constexpr bool force_element_trivial> = true; +template<> inline constexpr bool force_element_trivial_default_construction> = true; +template<> inline constexpr bool force_element_trivial_destruction> = true; +#endif + +} // end namespace boost::multi + +#include // TODO(correaa) remove instantiation of force_element_trivial in this header +#include +#include // IWYU pragma: export +#include // for pointer_traits +#include // for random_iterable +#include // IWYU pragma: export +#include +#include // for dimensionality_type // IWYU pragma: export + +#include // fpr copy_n +#include +#include // for std::memset in reinterpret_cast +#include // for std::invoke +#include // for std::next +#include // for std::pointer_traits +#include // for std::launder + +#if __has_include() +#if !defined(_MSVC_LANG) || (_MSVC_LANG > 202002L) +#include +#endif +#if defined(__cpp_lib_span) && __cpp_lib_span >= 202002L && !defined(_MSVC_LANG) +#define BOOST_MULTI_HAS_SPAN +#endif +#endif + +#include // for forward + +#ifdef __NVCC__ +#define BOOST_MULTI_FRIEND_CONSTEXPR template friend constexpr // workaround nvcc +#else +#define BOOST_MULTI_FRIEND_CONSTEXPR friend constexpr +#endif + +#ifdef __NVCC__ +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#define BOOST_MULTI_IGNORED_UNSAFE_BUFFER_USAGE_PUSH() \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunsafe-buffer-usage\"") +#else +#define BOOST_MULTI_IGNORED_UNSAFE_BUFFER_USAGE_PUSH() +#endif + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#define BOOST_MULTI_IGNORED_UNSAFE_BUFFER_USAGE_POP() _Pragma("clang diagnostic pop") +#else +#define BOOST_MULTI_IGNORED_UNSAFE_BUFFER_USAGE_POP() +#endif + +namespace boost::multi { + +template> +struct const_subarray; + +template::difference_type>> +class subarray; + +template::difference_type>> +class move_subarray; + +template +constexpr auto is_subarray_aux(const_subarray const&) -> std::true_type; +constexpr auto is_subarray_aux(...) -> std::false_type; + +template struct is_subarray : decltype(is_subarray_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + +template +struct of_dim { + template + static constexpr auto is_subarray_of_dim_aux(subarray const&) -> std::true_type; + static constexpr auto is_subarray_of_dim_aux(...) -> std::false_type; + + template struct is_subarray_of_dim : decltype(is_subarray_of_dim_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) +}; + +// template> struct array; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template::size_type>>> +struct array_types : private Layout { // cppcheck-suppress syntaxError ; false positive in cppcheck + using element = T; + using element_type = element; // this follows more closely https://en.cppreference.com/w/cpp/memory/pointer_traits + + using element_ptr = ElementPtr; + using element_const_ptr = typename std::pointer_traits::template rebind; + using element_move_ptr = multi::move_ptr; + + using element_ref = typename std::iterator_traits::reference; + + using layout_t = Layout; + + using rank = typename layout_t::rank; + + using layout_t::rank_v; + + using dimensionality_type = typename layout_t::dimensionality_type; + using layout_t::dimensionality; + + using layout_t::stride; + using typename layout_t::stride_type; + + using layout_t::num_elements; + using layout_t::offset; + + using typename layout_t::index; + using typename layout_t::index_extension; + using typename layout_t::index_range; + + using typename layout_t::strides_type; + + BOOST_MULTI_HD constexpr auto strides() const { return detail::convertible_tuple(layout_t::strides()); } + + using typename layout_t::difference_type; + + using layout_t::size; + using typename layout_t::size_type; + + using layout_t::nelems; + + using layout_t::extension; + using typename layout_t::extension_type; + + using layout_t::extensions; + using typename layout_t::extensions_type; + + BOOST_MULTI_HD constexpr auto extensions() const -> extensions_type { return static_cast(*this).extensions(); } + + using layout_t::empty; + using layout_t::is_empty; + + using layout_t::sub; + + using layout_t::sizes; + using typename layout_t::sizes_type; + + using typename layout_t::indexes; + + [[deprecated("This is for compatiblity with Boost.MultiArray, you can use `rank` member type or `dimensionality` static member variable")]] + static constexpr auto num_dimensions() { return dimensionality; } + + [[deprecated("This is for compatiblity with Boost.MultiArray, you can use `offsets` member function")]] + auto index_bases() const -> std::ptrdiff_t const*; // = delete; this function is not implemented, it can give a linker error + + [[deprecated("This is for compatiblity with Boost.MultiArray, you can use `offsets` member function")]] + constexpr auto shape() const { return detail::convertible_tuplesizes())>(this->sizes()); } + + using layout_t::is_compact; + + friend constexpr auto size(array_types const& self) noexcept -> size_type { return self.size(); } + friend BOOST_MULTI_HD constexpr auto extension(array_types const& self) noexcept -> extension_type { return self.extension(); } + friend constexpr auto is_empty(array_types const& self) noexcept -> bool { return self.is_empty(); } + friend constexpr auto num_elements(array_types const& self) noexcept -> size_type { return self.num_elements(); } + + friend constexpr auto extensions(array_types const& self) noexcept -> extensions_type { return self.extensions(); } + friend constexpr auto sizes(array_types const& self) noexcept -> sizes_type { return self.sizes(); } + + // TODO(correaa) [[deprecated("use member syntax for non-salient properties")]] + friend constexpr auto stride(array_types const& self) noexcept -> stride_type { return self.stride(); } + + // TODO(correaa) [[deprecated("use member syntax for non-salient properties")]] + friend constexpr auto strides(array_types const& self) noexcept /*-> strides_type*/ { return self.strides(); } + + protected: + constexpr auto layout_mutable() -> layout_t& { return static_cast(*this); } + + public: + using value_type = typename std::conditional_t< + (D > 1), + array::default_allocator_type>, + element>; + + using reference = typename std::conditional_t< + (D > 1), + subarray, + typename std::iterator_traits::reference>; + + using const_reference = typename std::conditional_t< + (D > 1), + const_subarray, + typename std::iterator_traits::reference>; + + // cppcheck-suppress duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto base() const -> element_const_ptr { return base_; } + + BOOST_MULTI_HD constexpr auto mutable_base() const -> element_ptr { return base_; } + + BOOST_MULTI_HD constexpr auto cbase() const -> element_const_ptr { return base_; } + BOOST_MULTI_HD constexpr auto mbase() const& -> element_ptr& { return base_; } + + BOOST_MULTI_HD constexpr auto layout() const -> layout_t const& { return *this; } + friend constexpr auto layout(array_types const& self) -> layout_t const& { return self.layout(); } + + BOOST_MULTI_IGNORED_UNSAFE_BUFFER_USAGE_PUSH() + // [[clang::unsafe_buffer_usage]] + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto origin() const& -> decltype(auto) { return base_ + Layout::origin(); } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + BOOST_MULTI_IGNORED_UNSAFE_BUFFER_USAGE_POP() + + friend constexpr auto origin(array_types const& self) -> decltype(auto) { return self.origin(); } + + protected: +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // warning C4820: '7' bytes padding added after data member 'boost::multi::array_types::base_' [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\test\array_fancyref.cpp.x.vcxproj] +#endif + BOOST_MULTI_NO_UNIQUE_ADDRESS + element_ptr base_; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes,misc-non-private-member-variables-in-classes) : TODO(correaa) try to make it private, [static_]array needs mutation +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template friend struct array_iterator; + + using derived = subarray; + BOOST_MULTI_HD constexpr explicit array_types(std::nullptr_t) : Layout{}, base_(nullptr) {} + + public: + array_types() = default; // cppcheck-suppress uninitMemberVar ; base_ not initialized + + BOOST_MULTI_HD constexpr array_types(layout_t const& lyt, element_ptr const& data) + : Layout{lyt}, base_{data} {} + + protected: + template< + class ArrayTypes, + typename = std::enable_if_t>{}>, decltype(multi::detail::explicit_cast(std::declval().base_))* = nullptr> + // underlying pointers are explicitly convertible + BOOST_MULTI_HD constexpr explicit array_types(ArrayTypes const& other) + : Layout{other.layout()}, base_{other.base_} {} + + template< + class ArrayTypes, + typename = std::enable_if_t>{}>, + decltype(multi::detail::implicit_cast(std::declval().base_))* = nullptr> + // cppcheck-suppress noExplicitConstructor ; because underlying pointers are implicitly convertible + BOOST_MULTI_HD constexpr /*implt*/ array_types(ArrayTypes const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : inherit behavior of underlying pointer + : Layout{other.layout()}, base_{other.base_} {} + + template< + typename ElementPtr2, + typename = decltype(Layout{std::declval const&>().layout()}), + typename = decltype(element_ptr{std::declval const&>().base_})> + BOOST_MULTI_HD constexpr explicit array_types(array_types const& other) + : Layout{other.layout()}, base_{other.base_} {} + + template friend struct array_types; +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template +struct subarray_ptr; + +template> +using const_subarray_ptr = subarray_ptr; + +template, bool IsConst = false> +struct subarray_ptr // NOLINT(fuchsia-multiple-inheritance) : to allow mixin CRTP +: boost::multi::iterator_facade< + subarray_ptr, void, std::random_access_iterator_tag, + subarray const&, typename Layout::difference_type> { + + private: + Layout layout_; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) //'boost::multi::subarray_ptr,boost::multi::layout_t<1,boost::multi::size_type>,true>': '7' bytes padding added after data member 'boost::multi::subarray_ptr,boost::multi::layout_t<1,boost::multi::size_type>,true>::base_' +#endif + + ElementPtr base_; + typename std::iterator_traits::difference_type offset_; // = []() { assert(0); return 0; } (); + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + public: + template friend struct subarray_ptr; + template friend struct array_iterator; + + // ~subarray_ptr() = default; // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + + using pointer = subarray*; + using element_type = typename subarray::decay_type; + using difference_type = typename Layout::difference_type; + + using value_type = element_type; + + using reference = std::conditional_t< + IsConst, + const_subarray, + subarray>; + + using iterator_category = std::random_access_iterator_tag; + + // cppcheck-suppress noExplicitConstructor + BOOST_MULTI_HD constexpr subarray_ptr(std::nullptr_t nil) : layout_{}, base_{nil}, offset_{0} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) terse syntax and functionality by default + + subarray_ptr() = default; // cppcheck-suppress uninitMemberVar ; base_ is not initialized + + template friend struct subarray_ptr; + + BOOST_MULTI_HD constexpr subarray_ptr(typename reference::element_ptr base, layout_t lyt) : layout_{lyt}, base_{base}, offset_{0} {} + + template = 0> // NOLINT(modernize-use-constraints) for C++20 + // cppcheck-suppress noExplicitConstructor ; see below + BOOST_MULTI_HD constexpr /*mplct*/ subarray_ptr(subarray_ptr const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : propagate implicitness of pointer + : layout_{other.layout_}, base_{other.base_}, offset_{other.offset_} {} + + template< + typename OtherT, multi::dimensionality_type OtherD, typename OtherEPtr, class OtherLayout, bool OtherIsConst, + decltype(multi::detail::implicit_cast(std::declval()))* = nullptr // propagate implicitness of pointer + > + // cppcheck-suppress noExplicitConstructor ; because underlying pointer is implicitly convertible + BOOST_MULTI_HD constexpr /*mplct*/ subarray_ptr(subarray_ptr const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR + : layout_{other.layout_}, base_{other.base_} {} + + template< + class ElementPtr2, + std::enable_if_t && (D == 0), int> = 0 // NOLINT(modernize-use-constraints) for C++20 + > + BOOST_MULTI_HD constexpr explicit subarray_ptr(ElementPtr2 const& other) : layout_{}, base_{other} {} + + BOOST_MULTI_HD constexpr explicit operator bool() const { return static_cast(base()); } + + // cppcheck-suppress duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto operator*() const -> reference { return reference(layout_, base_); } + + BOOST_MULTI_HD constexpr auto operator->() const { + class proxy { + reference ref_; + + public: + BOOST_MULTI_HD constexpr explicit proxy(reference&& ref) : ref_{std::move(ref)} {} + BOOST_MULTI_HD constexpr auto operator->() && -> reference* { return std::addressof(this->ref_); } + }; + return proxy{operator*()}; + } + + BOOST_MULTI_HD constexpr auto operator[](difference_type n) const -> reference { return *(*this + n); } + + BOOST_MULTI_HD constexpr auto operator<(subarray_ptr const& other) const -> bool { return distance_to(other) > 0; } + + BOOST_MULTI_HD constexpr subarray_ptr(typename reference::element_ptr base, Layout const& lyt) : layout_{lyt}, base_{base} {} + + template friend struct const_subarray; + + BOOST_MULTI_HD constexpr auto base() const -> typename reference::element_ptr { return base_; } + + friend BOOST_MULTI_HD constexpr auto base(subarray_ptr const& self) { return self.base(); } + + template, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + constexpr auto operator==(OtherSubarrayPtr const& other) const + -> decltype((base_ == other.base_) && (layout_ == other.layout_)) { + return (base_ == other.base_) && (layout_ == other.layout_); + } + + template, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + constexpr auto operator!=(OtherSubarrayPtr const& other) const + -> decltype((base_ != other.base_) || (layout_ != other.layout_)) { + return (base_ != other.base_) || (layout_ != other.layout_); + } + + constexpr auto operator==(subarray_ptr const& other) const -> bool { + return (base_ == other.base_) && (layout_ == other.layout_); + } + + constexpr auto operator!=(subarray_ptr const& other) const -> bool { + return (base_ != other.base_) || (layout_ != other.layout_); + } + + template< + typename OtherT, multi::dimensionality_type OtherD, typename OtherEPtr, class OtherL, bool OtherIsConst, + std::enable_if_t>, int> = 0 // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + > + friend BOOST_MULTI_HD constexpr auto operator==(subarray_ptr const& self, subarray_ptr const& other) -> bool { + BOOST_MULTI_ASSERT((!self || !other) || (self->layout() == other->layout())); // comparing array ptrs of different provenance is undefined + return self->base() == other->base(); + } + + template< + typename OtherT, multi::dimensionality_type OtherD, typename OtherEPtr, class OtherL, bool OtherIsConst, + std::enable_if_t>, int> = 0 // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + > + friend BOOST_MULTI_HD constexpr auto operator!=(subarray_ptr const& self, subarray_ptr const& other) -> bool { + BOOST_MULTI_ASSERT((!self || !other) || (self->layout() == other->layout())); // comparing array ptrs of different provenance is undefined + return self->base() != other->base(); + } + + protected: + BOOST_MULTI_HD constexpr void increment() { base_ += layout_.nelems(); } + BOOST_MULTI_HD constexpr void decrement() { base_ -= layout_.nelems(); } + + BOOST_MULTI_HD constexpr void advance(difference_type n) { base_ += layout_.nelems() * n; } + BOOST_MULTI_HD constexpr auto distance_to(subarray_ptr const& other) const -> difference_type { + BOOST_MULTI_ASSERT(layout_.nelems() == other.layout_.nelems()); + // assert( Ref::nelems() == other.Ref::nelems() && Ref::nelems() != 0 ); + // assert( (other.base() - base())%Ref::nelems() == 0); + BOOST_MULTI_ASSERT(layout_ == other.layout_); + return (other.base_ - base_) / layout_.nelems(); + } + + public: + BOOST_MULTI_HD constexpr auto operator+=(difference_type n) -> subarray_ptr& { + advance(n); + return *this; + } +}; + +template::difference_type> +struct array_iterator; + +template +struct array_iterator // NOLINT(fuchsia-multiple-inheritance) for facades +: boost::multi::iterator_facade< + array_iterator, void, std::random_access_iterator_tag, + subarray const&, typename layout_t::difference_type> +, multi::decrementable> +, multi::incrementable> +, multi::affine, multi::difference_type> +, multi::totally_ordered2, void> { + ~array_iterator() = default; // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + + constexpr auto operator=(array_iterator&&) // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + noexcept // lints(hicpp-noexcept-move,performance-noexcept-move-constructor) + -> array_iterator& = default; + + array_iterator(array_iterator&&) noexcept // lints(hicpp-noexcept-move,performance-noexcept-move-constructor) + = default; // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + + using difference_type = typename layout_t::difference_type; + using element = Element; + using element_ptr = ElementPtr; + using element_const_ptr = typename std::pointer_traits::template rebind; + using value_type = typename subarray::decay_type; + + using pointer = subarray*; + using reference = std::conditional_t< + IsConst, + const_subarray, + subarray>; + using const_reference = const_subarray; // TODO(correaa) should be const_subarray (base of subarray) + + template + using rebind = array_iterator, D, typename std::pointer_traits::template rebind, IsConst, IsMove, Stride>; + + using iterator_category = std::random_access_iterator_tag; + + constexpr static dimensionality_type rank_v = D; + using rank = std::integral_constant; // TODO(correaa) make rank a function for compat with mdspan? + + using ptr_type = subarray_ptr, true>; + + using stride_type = index; + using layout_type = typename reference::layout_type; // layout_t + + // BOOST_MULTI_HD constexpr explicit array_iterator(std::nullptr_t nil) : ptr_{nil} {} + // BOOST_MULTI_HD constexpr array_iterator() : array_iterator{nullptr} {} + BOOST_MULTI_HD constexpr array_iterator() : ptr_{}, stride_{} {} // = default; // TODO(correaa) make = default, now it is not compiling + + template friend struct array_iterator; + + template< + class EElement, typename PPtr, + decltype(multi::detail::explicit_cast(std::declval>().base()))* = nullptr> + BOOST_MULTI_HD constexpr explicit array_iterator(array_iterator const& other) + : ptr_{element_ptr{other.base()}, other.ptr_->layout()}, stride_{other.stride_} {} + + template(std::declval>().base()))* = nullptr // propagate implicitness of pointer + > + // cppcheck-suppress noExplicitConstructor ; because underlying pointer is implicitly convertible + BOOST_MULTI_HD constexpr /*mplct*/ array_iterator(array_iterator const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR + : ptr_(other.ptr_), stride_{other.stride_} {} + + array_iterator(array_iterator const&) = default; + auto operator=(array_iterator const&) -> array_iterator& = default; + + BOOST_MULTI_HD constexpr explicit operator bool() const { return ptr_->base(); } // TODO(correaa) implement bool conversion for subarray_ptr + BOOST_MULTI_HD constexpr auto operator*() const -> reference { return *ptr_; } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wlarge-by-value-copy" // TODO(correaa) can it be returned by reference? +#endif + + BOOST_MULTI_HD constexpr auto operator->() const -> decltype(auto) { return ptr_; } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + BOOST_MULTI_HD constexpr auto operator+(difference_type n) const -> array_iterator { + array_iterator ret{*this}; + ret += n; + return ret; + } + BOOST_MULTI_HD constexpr auto operator[](difference_type n) const -> subarray { return *((*this) + n); } + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + BOOST_MULTI_HD constexpr auto operator==(array_iterator const& other) const -> bool { + // BOOST_MULTI_ASSERT( this->stride_ == other.stride_ ); + // BOOST_MULTI_ASSERT( this->ptr_->layout() == other.ptr_->layout() ); + return (this->ptr_ == other.ptr_) && (this->stride_ == other.stride_) && ((*(this->ptr_)).layout() == (*(other.ptr_)).layout()); + } + + BOOST_MULTI_HD constexpr auto operator==(array_iterator const& other) const -> bool { + BOOST_MULTI_ASSERT(this->stride_ == other.stride_); + BOOST_MULTI_ASSERT(this->ptr_->layout() == other.ptr_->layout()); + return (this->ptr_ == other.ptr_); // && (this->stride_ == other.stride_) && ( (*(this->ptr_)).layout() == (*(other.ptr_)).layout() ); + } + + BOOST_MULTI_HD constexpr auto operator!=(array_iterator const& other) const -> bool { + return !operator==(other); + } + + BOOST_MULTI_HD constexpr auto operator<(array_iterator const& other) const -> bool { + // BOOST_MULTI_ASSERT((*ptr_).layout() == (*(other.ptr_)).layout()); + // BOOST_MULTI_ASSERT(stride_ != 0); + // return + // ((0 < stride_) && (ptr_.base() - other.ptr_.base() < 0)) + // || ((stride_ < 0) && (0 < ptr_.base() - other.ptr_.base())); // TODO(correaa) consider the case where stride_ is negative + return 0 < other - *this; + } + + BOOST_MULTI_HD constexpr explicit array_iterator(typename subarray::element_ptr base, layout_t const& lyt, index stride) + : ptr_(base, lyt), stride_{stride} {} + + template friend struct const_subarray; + + template + BOOST_MULTI_HD constexpr auto operator()(index idx, As... args) const -> decltype(auto) { return this->operator[](idx)(args...); } + BOOST_MULTI_HD constexpr auto operator()(index idx) const -> decltype(auto) { return this->operator[](idx); } + + private: + template + static BOOST_MULTI_HD constexpr auto apply_impl_(Self&& self, Tuple const& tuple, std::index_sequence /*012*/) -> decltype(auto) { + using std::get; // for C++17 compatibility + return std::forward(self)(get(tuple)...); + } + + public: + template BOOST_MULTI_HD constexpr auto apply(Tuple const& tpl) const -> decltype(auto) { return apply_impl_(*this, tpl, std::make_index_sequence>()); } + + private: + ptr_type ptr_; + stride_type stride_; // = {1}; // nice non-zero default // TODO(correaa) use INT_MAX? // TODO(correaa) remove to make type trivial + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + BOOST_MULTI_HD constexpr void decrement_() { ptr_.base_ -= stride_; } + BOOST_MULTI_HD constexpr void advance_(difference_type n) { ptr_.base_ += stride_ * n; } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + public: + BOOST_MULTI_HD constexpr auto base() const -> element_ptr { return ptr_.base_; } + BOOST_MULTI_HD constexpr auto stride() const -> stride_type { return stride_; } + + friend /*constexpr*/ auto base(array_iterator const& self) -> element_ptr { return self.base(); } // TODO(correaa) remove + friend constexpr auto stride(array_iterator const& self) -> stride_type { return self.stride_; } // TODO(correaa) remove + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + constexpr auto operator++() -> array_iterator& { + ptr_.base_ += stride_; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + constexpr auto operator--() -> array_iterator& { + ptr_.base_ -= stride_; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + friend constexpr auto operator-(array_iterator const& self, array_iterator const& other) -> difference_type { + BOOST_MULTI_ASSERT(self.stride_ == other.stride_); // LCOV_EXCL_LINE + BOOST_MULTI_ASSERT(self.stride_ != 0); // LCOV_EXCL_LINE + return (self.ptr_.base() - other.ptr_.base()) / self.stride_; + } + + constexpr auto operator+=(difference_type n) -> array_iterator& { + advance_(+n); + return *this; + } + constexpr auto operator-=(difference_type n) -> array_iterator& { + advance_(-n); + return *this; + } +}; + +template +struct cursor_t { + using difference_type = typename std::iterator_traits::difference_type; + using strides_type = StridesType; + + using element_ptr = ElementPtr; + using element_ref = typename std::iterator_traits::reference; + using element_type = typename std::iterator_traits::value_type; + + using pointer = element_ptr; + using reference = element_ref; + + using indices_type = typename extensions_t::indices_type; + + cursor_t() = default; + + private: +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // '7' bytes padding added after data member 'boost::multi::array_types::base_' [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\test\array_fancyref.cpp.x.vcxproj] +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + + strides_type strides_; + element_ptr base_; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template friend struct const_subarray; + template friend struct cursor_t; + + BOOST_MULTI_HD constexpr cursor_t(element_ptr base, strides_type const& strides) : strides_{strides}, base_{base} {} + + template(std::declval().base()))> + // cppcheck-suppress noExplicitConstructor + BOOST_MULTI_HD constexpr cursor_t(OtherCursor const& other) : strides_{other.strides()}, base_{other.base()} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + template + BOOST_MULTI_HD constexpr explicit cursor_t(OtherCursor const& other) : strides_{other.strides()}, base_{other.base()} {} + + public: + BOOST_MULTI_HD constexpr auto operator[](difference_type n) const -> decltype(auto) { + using std::get; // for C++17 compatibility + if constexpr(D != 1) { + return cursor_t< + ElementPtr, + D - 1, + std::decay_t>{ + base_ + get<0>(strides_) * n, + strides_.tail() + }; + } else { + return base_[get<0>(strides_) * n]; + } + } + + BOOST_MULTI_HD constexpr auto operator()(difference_type n) const -> decltype(auto) { + return operator[](n); + } + template + BOOST_MULTI_HD constexpr auto operator()(difference_type n, Ns... rest) const -> decltype(auto) { + return operator[](n)(rest...); + } + + private: + template + BOOST_MULTI_HD constexpr auto apply_impl_(Tuple const& tup, std::index_sequence /*012*/) const -> decltype(auto) { + using std::get; // for C++17 compatibility + return ((get(tup) * get(strides_)) + ...); + } + + public: + template + BOOST_MULTI_HD constexpr auto operator+=(Tuple const& tup) -> cursor_t& { + base_ += apply_impl_(tup, std::make_index_sequence>{}); + return *this; + } + BOOST_MULTI_HD constexpr auto operator*() const -> reference { return *base_; } + BOOST_MULTI_HD constexpr auto operator->() const -> pointer { return base_; } + + BOOST_MULTI_HD constexpr auto base() const -> pointer { return base_; } + BOOST_MULTI_HD constexpr auto strides() const -> strides_type { return strides_; } + template + BOOST_MULTI_HD constexpr auto stride() const { + using std::get; + return get

(strides_); + } +}; + +template +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) +struct elements_iterator_t +// : boost::multi::random_accessable, typename std::iterator_traits::difference_type, typename std::iterator_traits::reference> +{ + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = Pointer; + using reference = std::remove_const_t::reference>; // TODO(correaa) investigate why top-level const reaches here + using iterator_category = std::random_access_iterator_tag; + + using const_pointer = typename std::pointer_traits::template rebind; + + using layout_type = LayoutType; + + private: + pointer base_; + layout_type l_; + difference_type n_ = 0; + extensions_t xs_; + + using indices_type = typename extensions_t::indices_type; + indices_type ns_ = {}; + + template friend struct elements_iterator_t; + template friend struct elements_range_t; + + BOOST_MULTI_HD constexpr elements_iterator_t(pointer base, layout_type const& lyt, difference_type n) + : base_{base}, l_{lyt}, n_{n}, xs_{l_.extensions()}, ns_{lyt.is_empty() ? indices_type{} : xs_.from_linear(n)} {} + + public: + elements_iterator_t() = default; + + BOOST_MULTI_HD constexpr auto base() -> pointer { return base_; } + BOOST_MULTI_HD constexpr auto base() const -> const_pointer { return base_; } + + BOOST_MULTI_HD constexpr auto layout() const -> layout_type { return l_; } + + template(std::declval().base_))* = nullptr> + // cppcheck-suppress noExplicitConstructor + BOOST_MULTI_HD constexpr /*impl*/ elements_iterator_t(Other const& other) : elements_iterator_t{other.base_, other.l_, other.n_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + template + BOOST_MULTI_HD constexpr explicit elements_iterator_t(Other const& other) : elements_iterator_t{other.base_, other.l_, other.n_} {} + + elements_iterator_t(elements_iterator_t const&) = default; + + BOOST_MULTI_HD constexpr auto operator=(elements_iterator_t const& other) -> elements_iterator_t& { // fixes (?) warning: definition of implicit copy assignment operator for 'elements_iterator_t *, boost::multi::layout_t<1>>' is deprecated because it has a user-declared copy constructor [-Wdeprecated-copy] + if(&other == this) { + return *this; + } // for cert-oop54-cpp + base_ = other.base_; + xs_ = other.xs_; + n_ = other.n_; + return *this; + } + + BOOST_MULTI_HD constexpr auto operator++() -> elements_iterator_t& { + apply([&xs = this->xs_](auto&... idxs) { return xs.next_canonical(idxs...); }, ns_); + // std::apply([&xs = this->xs_](auto&... idxs) { return xs.next_canonical(idxs...); }, ns_); + ++n_; + return *this; + } + BOOST_MULTI_HD constexpr auto operator--() -> elements_iterator_t& { + std::apply([&xs = this->xs_](auto&... idxs) { return xs.prev_canonical(idxs...); }, ns_); + --n_; + return *this; + } + + BOOST_MULTI_HD constexpr auto operator+=(difference_type n) -> elements_iterator_t& { + auto const nn = apply(xs_, ns_); + ns_ = xs_.from_linear(nn + n); + n_ += n; + return *this; + } + BOOST_MULTI_HD constexpr auto operator-=(difference_type n) -> elements_iterator_t& { + // auto const nn = std::apply(xs_, ns_); + // ns_ = xs_.from_linear(nn - n); + n_ -= n; + return *this; + } + + BOOST_MULTI_HD constexpr auto operator-(elements_iterator_t const& other) const -> difference_type { + BOOST_MULTI_ASSERT(base_ == other.base_ && l_ == other.l_); + return n_ - other.n_; + } + + // BOOST_MULTI_HD constexpr auto n() const { return n_; } + + BOOST_MULTI_HD constexpr auto operator<(elements_iterator_t const& other) const -> bool { + BOOST_MULTI_ASSERT(base_ == other.base_ && l_ == other.l_); + return n_ < other.n_; + } + + BOOST_MULTI_HD constexpr auto operator<=(elements_iterator_t const& other) const -> bool { return ((*this) < other) || ((*this) == other); } + + BOOST_MULTI_HD constexpr auto operator>(elements_iterator_t const& other) const -> bool { return other < (*this); } + BOOST_MULTI_HD constexpr auto operator>=(elements_iterator_t const& other) const -> bool { return !((*this) < other); } + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + BOOST_MULTI_HD constexpr auto current() const -> pointer { return base_ + std::apply(l_, ns_); } + + // BOOST_MULTI_HD constexpr auto operator->() const -> pointer { return base_ + std::apply(l_, ns_); } + + // cppcheck-suppress duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto operator*() const -> reference /*decltype(base_[0])*/ { + return base_[apply(l_, ns_)]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + + BOOST_MULTI_HD constexpr auto operator[](difference_type const& n) const -> reference { + auto const nn = apply(xs_, ns_); + return base_[apply(l_, xs_.from_linear(nn + n))]; + } // explicit here is necessary for nvcc/thrust + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + BOOST_MULTI_HD constexpr auto operator+(difference_type n) const -> elements_iterator_t { + auto ret{*this}; + ret += n; + return ret; + } + BOOST_MULTI_HD constexpr auto operator-(difference_type n) const -> elements_iterator_t { + auto ret{*this}; + ret -= n; + return ret; + } + + BOOST_MULTI_HD constexpr auto operator==(elements_iterator_t const& other) const -> bool { + BOOST_MULTI_ASSERT(base_ == other.base_ && l_ == other.l_); // TODO(correaa) calling host function from host device + return n_ == other.n_; // and base_ == other.base_ and l_ == other.l_; + } + BOOST_MULTI_HD constexpr auto operator!=(elements_iterator_t const& other) const -> bool { + BOOST_MULTI_ASSERT(base_ == other.base_ && l_ == other.l_); // TODO(correaa) calling host function from host device + return n_ != other.n_; + } +}; + +template +struct elements_range_t { + using pointer = Pointer; + using layout_type = LayoutType; + + using value_type = typename std::iterator_traits::value_type; + using const_pointer = typename std::pointer_traits::template rebind; + + using reference = typename std::iterator_traits::reference; + using const_reference = typename std::iterator_traits::reference; + + using size_type = typename std::iterator_traits::difference_type; + using difference_type = typename std::iterator_traits::difference_type; + + using iterator = elements_iterator_t; + using const_iterator = elements_iterator_t; + + using element = value_type; + + private: + pointer base_; + layout_type l_; + + public: + template(std::declval().base_))* = nullptr> + // cppcheck-suppress noExplicitConstructor ; because underlying pointer is implicitly convertible // NOLINTNEXTLINE(runtime/explicit) + constexpr /*impl*/ elements_range_t(OtherRange const& other) : base_{other.base}, l_{other.l_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) to reproduce the implicitness of the argument + template(std::declval().base_))* = nullptr> + constexpr explicit elements_range_t(OtherRange const& other) : elements_range_t{other} {} + + constexpr elements_range_t(pointer base, layout_type const& lyt) : base_{base}, l_{lyt} {} + + constexpr auto base() -> pointer { return base_; } + constexpr auto base() const -> const_pointer { return base_; } + + constexpr auto layout() const -> layout_type { return l_; } + + private: +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + constexpr auto at_aux_(difference_type n) const -> reference { + BOOST_MULTI_ASSERT(!is_empty()); + return base_[std::apply(l_, l_.extensions().from_linear(n))]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + public: + BOOST_MULTI_HD constexpr auto operator[](difference_type n) const& -> const_reference { return at_aux_(n); } + BOOST_MULTI_HD constexpr auto operator[](difference_type n) && -> reference { return at_aux_(n); } + BOOST_MULTI_HD constexpr auto operator[](difference_type n) & -> reference { return at_aux_(n); } + + constexpr auto size() const -> size_type { return l_.num_elements(); } + + using extension_type = multi::extension_t; + + BOOST_MULTI_HD constexpr auto extension() const { return extension_type{0, size()}; } + + [[nodiscard]] + constexpr auto empty() const -> bool { return l_.empty(); } + constexpr auto is_empty() const -> bool { return l_.is_empty(); } + + elements_range_t(elements_range_t const&) = delete; + elements_range_t(elements_range_t&&) = delete; + + template auto operator==(elements_range_t const& other) const -> bool { + return size() == other.size() && adl_equal(other.begin(), other.end(), begin()); // mull-ignore: cxx_eq_to_ne // false positive bug in mull-18 + } + template auto operator!=(elements_range_t const& other) const -> bool { + // if(is_empty() && other.is_empty()) { return false; } + return size() != other.size() || !adl_equal(other.begin(), other.end(), begin()); + } + + template void swap(elements_range_t& other) & noexcept { + BOOST_MULTI_ASSERT(size() == other.size()); + adl_swap_ranges(begin(), end(), other.begin()); + } + template void swap(elements_range_t& other) && noexcept { + BOOST_MULTI_ASSERT(size() == other.size()); + adl_swap_ranges(begin(), end(), other.begin()); + } + template void swap(elements_range_t&& other) & noexcept { + BOOST_MULTI_ASSERT(size() == other.size()); + adl_swap_ranges(begin(), end(), std::move(other).begin()); + } + template void swap(elements_range_t&& other) && noexcept { + BOOST_MULTI_ASSERT(size() == other.size()); + adl_swap_ranges(begin(), end(), std::move(other).begin()); + } + + ~elements_range_t() = default; + + private: + BOOST_MULTI_HD constexpr auto begin_aux_() const { return iterator{base_, l_, 0}; } + BOOST_MULTI_HD constexpr auto end_aux_() const { return iterator{base_, l_, l_.num_elements()}; } + + public: + BOOST_MULTI_HD constexpr auto begin() const& -> const_iterator { return begin_aux_(); } + BOOST_MULTI_HD constexpr auto end() const& -> const_iterator { return end_aux_(); } + + BOOST_MULTI_HD constexpr auto begin() && -> iterator { return begin_aux_(); } + BOOST_MULTI_HD constexpr auto end() && -> iterator { return end_aux_(); } + + BOOST_MULTI_HD constexpr auto begin() & -> iterator { return begin_aux_(); } + BOOST_MULTI_HD constexpr auto end() & -> iterator { return end_aux_(); } + + BOOST_MULTI_HD constexpr auto front() const& -> const_reference { return *begin(); } + BOOST_MULTI_HD constexpr auto back() const& -> const_reference { return *std::prev(end(), 1); } + + BOOST_MULTI_HD constexpr auto front() && -> reference { return *begin(); } + BOOST_MULTI_HD constexpr auto back() && -> reference { return *std::prev(end(), 1); } + + BOOST_MULTI_HD constexpr auto front() & -> reference { return *begin(); } + BOOST_MULTI_HD constexpr auto back() & -> reference { return *std::prev(end(), 1); } + + auto operator=(elements_range_t const&) -> elements_range_t& = delete; + + auto operator=(elements_range_t&& other) noexcept -> elements_range_t& { // cannot be =delete in NVCC? + if(!is_empty()) { + adl_copy(other.begin(), other.end(), this->begin()); + } + return *this; + } + + template()), std::end(std::declval()), std::declval()))> + auto operator=(OtherElementRange&& other) & -> elements_range_t& { // NOLINT(cppcoreguidelines-missing-std-forward) std::forward(other) creates a problem with move-only elements + BOOST_MULTI_ASSERT(size() == other.size()); + if(!is_empty()) { + adl_copy(std::begin(other), std::end(other), begin()); + } + return *this; + } + + template()), std::end(std::declval()), std::declval()))> + constexpr auto operator=(OtherElementRange&& other) && -> elements_range_t& { // NOLINT(cppcoreguidelines-missing-std-forward) std::forward(other) creates a problem with move-only elements + BOOST_MULTI_ASSERT(size() == other.size()); + if(!is_empty()) { + adl_copy(std::begin(other), std::end(other), begin()); + } + return *this; + } + + auto operator=(std::initializer_list values) && -> elements_range_t& { + operator=(values); + return *this; + } + auto operator=(std::initializer_list values) & -> elements_range_t& { + BOOST_MULTI_ASSERT(static_cast(values.size()) == size()); + adl_copy_n(values.begin(), values.size(), begin()); + return *this; + } +}; + +template +[[deprecated("remove")]] BOOST_MULTI_HD constexpr auto ref(It begin, It end) + -> multi::subarray { + return multi::subarray{begin, end}; +} + +template struct static_array; // this might be needed by MSVC 14.3 in c++17 mode + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template +struct const_subarray : array_types { + using types = array_types; + using ref_ = const_subarray; + + using array_types::rank_v; + + friend struct const_subarray; + + using typename types::element_type; + using types::layout; + + using layout_type = Layout; + + // cppcheck-suppress-begin duplInheritedMember ; TODO(correaa) eliminate array_types base + BOOST_MULTI_HD constexpr auto layout() const -> decltype(auto) { return array_types::layout(); } + + using basic_const_array = subarray::template rebind, Layout>; + + const_subarray() = default; + auto operator=(const_subarray const&) -> const_subarray& = delete; + auto operator=(const_subarray&&) -> const_subarray& = delete; + + BOOST_MULTI_HD constexpr const_subarray(layout_type const& layout, ElementPtr const& base) + : array_types{layout, base} {} + + protected: + // using types::types; + BOOST_MULTI_HD constexpr explicit const_subarray(std::nullptr_t nil) : types{nil} {} + + template friend struct static_array; + + template friend struct subarray_ptr; + + // TODO(correaa) vvv consider making it explicit (seems that in C++23 it can prevent auto s = a[0];) + // const_subarray(const_subarray const&) = default; // NOTE: reference type cannot be copied. perhaps you want to return by std::move or std::forward if you got the object from a universal reference argument + + public: + const_subarray(const_subarray const&) = delete; + + using element = typename types::element; + using element_ptr = typename types::element_ptr; + using element_const_ptr = typename types::element_const_ptr; + using element_ref = typename types::element_ref; + using element_cref = typename std::iterator_traits::reference; + + using elements_iterator = elements_iterator_t; + using celements_iterator = elements_iterator_t; + + using elements_range = elements_range_t; + using const_elements_range = elements_range_t; + + using index_gen [[deprecated("here to fulfill MultiArray concept")]] = char*; + using extent_gen [[deprecated("here to fulfill MultiArray concept")]] = void; + using extent_range [[deprecated("here to fulfill MultiArray concept")]] = void; + + private: + constexpr auto elements_aux_() const { return elements_range(this->base_, this->layout()); } + + public: + const_subarray(const_subarray&&) noexcept = default; // lints(readability-redundant-access-specifiers) + + constexpr auto elements() & -> elements_range { return elements_aux_(); } + constexpr auto elements() && -> elements_range { return elements_aux_(); } + constexpr auto elements() const& { return const_elements_range(this->base(), this->layout()); } + constexpr auto const_elements() const -> const_elements_range { return elements_aux_(); } + + constexpr auto hull() const -> std::pair { + return {this->base(), std::abs(this->hull_size())}; + } + + ~const_subarray() = default; // this lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + + BOOST_MULTI_FRIEND_CONSTEXPR auto sizes(const_subarray const& self) noexcept -> typename const_subarray::sizes_type { return self.sizes(); } // needed by nvcc + BOOST_MULTI_FRIEND_CONSTEXPR auto size(const_subarray const& self) noexcept -> typename const_subarray::size_type { return self.size(); } // needed by nvcc + + // template friend constexpr auto reinterpret_array_cast(const_subarray && self) {return std::move(self).template reinterpret_array_cast::template rebind>();} + // template friend constexpr auto reinterpret_array_cast(const_subarray const& self) {return self .template reinterpret_array_cast::template rebind>();} + + friend constexpr auto dimensionality(const_subarray const& /*self*/) { return D; } + + using typename types::reference; + + using default_allocator_type = typename multi::pointer_traits::default_allocator_type; + + constexpr auto get_allocator() const -> default_allocator_type { + using multi::get_allocator; + return get_allocator(this->base()); + } + + BOOST_MULTI_FRIEND_CONSTEXPR auto get_allocator(const_subarray const& self) -> default_allocator_type { return self.get_allocator(); } + + using decay_type = array::default_allocator_type>; + + friend constexpr auto decay(const_subarray const& self) -> decay_type { return self.decay(); } + constexpr auto decay() const& -> decay_type { + decay_type ret{*this}; + return ret; + } + + constexpr auto operator+() const -> decay_type { return decay(); } + using typename types::const_reference; + + private: + template friend class subarray; + + BOOST_MULTI_HD constexpr auto at_aux_(index idx) const { + BOOST_MULTI_ASSERT((this->stride() == 0 || (this->extension().contains(idx))) && ("out of bounds")); + + // clang-format off + #if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span + #endif + return const_reference( + this->layout().sub(), + this->base_ + (idx * this->layout().stride() - this->layout().offset()) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ); // cppcheck-suppress syntaxError ; bug in cppcheck 2.5 +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + } + + public: + // clang-format off + #if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span + #endif + // clang-format on + + BOOST_MULTI_HD constexpr auto operator[](index idx) const& -> const_reference { + BOOST_MULTI_ASSERT((this->stride() == 0 || (this->extension().contains(idx))) && ("out of bounds")); // N_O_L_I_N_T(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + return const_reference( + this->layout().sub(), + this->base_ + (idx * this->layout().stride() - this->layout().offset()) // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ); // cppcheck-suppress syntaxError ; bug in cppcheck 2.5 + // return at_aux_(idx); // TODO(correaa) use at_aux + } // TODO(correaa) use return type to cast + + // clang-format off + #if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) + #pragma clang diagnostic pop + #endif + // clang-format on + + // template(D)>, + // typename = std::enable_if_t<(std::tuple_size::value > 1)> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + // > + // BOOST_MULTI_HD constexpr auto operator[](Tuple const& tup) const + // -> decltype(operator[](detail::head(tup))[detail::tuple_tail(tup)]) { + // return operator[](detail::head(tup))[detail::tuple_tail(tup)]; + // } + + // template::value == 1)>> // NOLINT(modernize-use-constraints) TODO(correaa) + // BOOST_MULTI_HD constexpr auto operator[](Tuple const& tup) const + // -> decltype(operator[](detail::head(tup))) { + // return operator[](detail::head(tup)); + // } + + constexpr auto front() const& -> const_reference { return *begin(); } + constexpr auto back() const& -> const_reference { return *(end() - 1); } // std::prev(end(), 1);} + + using typename types::index; + + constexpr auto reindexed(index first) const& { + // typename types::layout_t new_layout = this->layout(); + // new_layout.reindex(first); + return const_subarray(this->layout().reindex(first), types::base_); + } + constexpr auto reindexed(index first) & { + return const_subarray(this->layout().reindex(first), types::base_); + } + constexpr auto reindexed(index first) && { return const_subarray(this->layout().reindex(first), types::base_); } + + // TODO(correaa) : implement reindexed_aux + template + constexpr auto reindexed(index first, Indexes... idxs) const& -> const_subarray { + return ((reindexed(first).rotated()).reindexed(idxs...)).unrotated(); + } + + private: + constexpr auto taked_aux_(difference_type n) const { + BOOST_MULTI_ASSERT(n <= this->size()); + return const_subarray(this->layout().take(n), this->base_); + } + + public: + constexpr auto taked(difference_type n) const& -> basic_const_array { return taked_aux_(n); } + + private: + BOOST_MULTI_HD constexpr auto halved_aux_() const { + auto new_layout = this->layout().halve(); + return subarray(new_layout, this->base_); + } + + public: + BOOST_MULTI_HD constexpr auto halved() const& -> const_subarray { return halved_aux_(); } + + private: + constexpr auto dropped_aux_(difference_type n) const { + BOOST_MULTI_ASSERT(n <= this->size()); + typename types::layout_t const new_layout{ + this->layout().sub(), + this->layout().stride(), + this->layout().offset(), + this->stride() * (this->size() - n) + }; + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + return const_subarray(new_layout, this->base_ + n * this->layout().stride() /*- this->layout().offset()*/); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + } + + public: + constexpr auto dropped(difference_type n) const& -> basic_const_array { return dropped_aux_(n); } + constexpr auto dropped(difference_type n) && -> const_subarray { return dropped_aux_(n); } + constexpr auto dropped(difference_type n) & -> const_subarray { return dropped_aux_(n); } + + private: + BOOST_MULTI_HD constexpr auto sliced_aux_(index first, index last) const { + // TODO(correaa) remove first == last condition + BOOST_MULTI_ASSERT(((first == last) || this->extension().contains(first)) && ("sliced first out of bounds")); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + BOOST_MULTI_ASSERT(((first == last) || this->extension().contains(last - 1)) && ("sliced last out of bounds")); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + typename types::layout_t new_layout = this->layout(); + new_layout.nelems() = this->stride() * (last - first); // TODO(correaa) : reconstruct layout instead of mutating it + BOOST_MULTI_ASSERT(this->base_ || ((first * this->layout().stride() - this->layout().offset()) == 0)); // it is UB to offset a nullptr + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + return const_subarray(new_layout, this->base_ + (first * this->layout().stride() - this->layout().offset())); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + + public: + BOOST_MULTI_HD constexpr auto sliced(index first, index last) const& -> const_subarray { return sliced_aux_(first, last); } + + constexpr auto blocked(index first, index last) const& -> basic_const_array { return sliced(first, last).reindexed(first); } + constexpr auto blocked(index first, index last) & -> const_subarray { return sliced(first, last).reindexed(first); } + + using iextension = typename const_subarray::index_extension; + + constexpr auto stenciled(iextension iex) & -> const_subarray { return blocked(iex.first(), iex.last()); } + constexpr auto stenciled(iextension iex, iextension iex1) & -> const_subarray { return ((stenciled(iex).rotated()).stenciled(iex1)).unrotated(); } + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2) & -> const_subarray { return ((stenciled(iex).rotated()).stenciled(iex1, iex2)).unrotated(); } + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3) & -> const_subarray { return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3)).unrotated(); } + template + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3, Xs... iexs) & -> const_subarray { return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3, iexs...)).unrotated(); } + + constexpr auto stenciled(iextension iex) && -> const_subarray { return blocked(iex.first(), iex.last()); } + constexpr auto stenciled(iextension iex, iextension iex1) && -> const_subarray { return ((stenciled(iex).rotated()).stenciled(iex1)).unrotated(); } + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2) && -> const_subarray { return ((stenciled(iex).rotated()).stenciled(iex1, iex2)).unrotated(); } + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3) && -> const_subarray { return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3)).unrotated(); } + template + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3, Xs... iexs) && -> const_subarray { return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3, iexs...)).unrotated(); } + + constexpr auto stenciled(iextension iex) const& -> basic_const_array { return blocked(iex.first(), iex.last()); } + constexpr auto stenciled(iextension iex, iextension iex1) const& -> basic_const_array { return ((stenciled(iex).rotated()).stenciled(iex1)).unrotated(); } + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2) const& -> basic_const_array { return ((stenciled(iex).rotated()).stenciled(iex1, iex2)).unrotated(); } + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3) const& -> basic_const_array { return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3)).unrotated(); } + + template + constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3, Xs... iexs) const& -> basic_const_array { + return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3, iexs...)).unrotated(); + } + + constexpr auto elements_at(size_type idx) const& -> decltype(auto) { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + auto const sub_num_elements = this->begin()->num_elements(); + return operator[](idx / sub_num_elements).elements_at(idx % sub_num_elements); + } + constexpr auto elements_at(size_type idx) && -> decltype(auto) { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + auto const sub_num_elements = this->begin()->num_elements(); + return operator[](idx / sub_num_elements).elements_at(idx % sub_num_elements); + } + constexpr auto elements_at(size_type idx) & -> decltype(auto) { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + auto const sub_num_elements = this->begin()->num_elements(); + return operator[](idx / sub_num_elements).elements_at(idx % sub_num_elements); + } + + private: + constexpr auto strided_aux_(difference_type diff) const { + // auto new_layout = this->layout().do_stride(); + typename types::layout_t const new_layout{this->layout().sub(), this->layout().stride() * diff, this->layout().offset(), this->layout().nelems()}; + // template + return subarray(new_layout, types::base_); + } + + public: + constexpr auto strided(difference_type diff) const& { return strided_aux_(diff).as_const(); } + // constexpr auto strided(difference_type diff) && -> const_subarray { return strided_aux_(diff); } + // constexpr auto strided(difference_type diff) & -> const_subarray { return strided_aux_(diff); } + + constexpr auto sliced( + typename types::index first, typename types::index last, typename types::index stride_ + ) const& -> const_subarray { + return sliced(first, last).strided(stride_); + } + + using index_range = typename const_subarray::index_range; + + BOOST_MULTI_HD constexpr auto range(index_range irng) const& -> decltype(auto) { return sliced(irng.front(), irng.front() + irng.size()); } + // constexpr auto range(index_range irng) && -> decltype(auto) {return std::move(*this).sliced(irng.front(), irng.front() + irng.size());} + // constexpr auto range(index_range irng) & -> decltype(auto) {return sliced(irng.front(), irng.front() + irng.size());} + + [[deprecated("is_flattable will be a property of the layout soon")]] + constexpr auto is_flattable() const -> bool { + return (this->size() <= 1) || (this->stride() == this->layout().sub().nelems()); + } + + // friend constexpr auto flatted(const_subarray const& self) {return self.flatted();} + constexpr auto flatted() const& { + multi::layout_t new_layout{this->layout().sub()}; + new_layout.nelems() *= this->size(); // TODO(correaa) : use immutable layout + return const_subarray{new_layout, types::base_}; + } + + auto flattened() const { + auto new_layout = this->layout().flatten(); + return boost::multi::const_subarray{ + new_layout, this->base_ + }; + } + + constexpr auto broadcasted() const& { + // TODO(correaa) introduce a broadcasted_layout? + multi::layout_t const new_layout(layout(), 0, 0); //, (std::numeric_limits::max)()); // paren for MSVC macros + return const_subarray{new_layout, types::base_}; + } + + private: + constexpr auto diagonal_aux_() const -> subarray { + using boost::multi::detail::get; + auto square_size = (std::min)(get<0>(this->sizes()), get<1>(this->sizes())); // paren for MSVC macros + multi::layout_t new_layout{(*this)({0, square_size}, {0, square_size}).layout().sub()}; + new_layout.nelems() += (*this)({0, square_size}, {0, square_size}).layout().nelems(); // TODO(correaa) : don't use mutation + new_layout.stride() += (*this)({0, square_size}, {0, square_size}).layout().stride(); // TODO(correaa) : don't use mutation + return {new_layout, types::base_}; + } + + public: + // TODO(correaa) : define a diagonal_aux + // constexpr auto diagonal() && {return this->diagonal();} + + // constexpr auto diagonal() & -> const_subarray { + // using boost::multi::detail::get; + // auto square_size = (std::min)(get<0>(this->sizes()), get<1>(this->sizes())); // paren for MSVC macros + // multi::layout_t new_layout{(*this)({0, square_size}, {0, square_size}).layout().sub()}; + // new_layout.nelems() += (*this)({0, square_size}, {0, square_size}).layout().nelems(); // TODO(correaa) : don't use mutation + // new_layout.stride() += (*this)({0, square_size}, {0, square_size}).layout().stride(); // TODO(correaa) : don't use mutation + // return {new_layout, types::base_}; + // } + + template 1) && sizeof(Dummy*), int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + constexpr auto diagonal() const& -> const_subarray { + return this->diagonal_aux_(); + } + + // template 1) && sizeof(Dummy*), int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) + // constexpr auto diagonal() const& -> const_subarray { + // using std::get; // for C++17 compatibility + // auto const square_size = (std::min)(get<0>(this->sizes()), get<1>(this->sizes())); // parenthesis min for MSVC macros + // multi::layout_t new_layout{(*this)({0, square_size}, {0, square_size}).layout().sub()}; + // new_layout.nelems() += (*this)({0, square_size}, {0, square_size}).layout().nelems(); + // new_layout.stride() += (*this)({0, square_size}, {0, square_size}).layout().stride(); // cppcheck-suppress arithOperationsOnVoidPointer ; false positive D == 1 doesn't happen here + // return {new_layout, types::base_}; + // } + + // friend constexpr auto diagonal(const_subarray const& self) {return self .diagonal();} + // friend constexpr auto diagonal(const_subarray& self) {return self .diagonal();} + // friend constexpr auto diagonal(const_subarray&& self) {return std::move(self).diagonal();} + + // using partitioned_type = const_subarray; + // using partitioned_const_type = const_subarray; + + private: + BOOST_MULTI_HD constexpr auto partitioned_aux_(size_type n) const { + BOOST_MULTI_ASSERT(n != 0); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + // vvv TODO(correaa) should be size() here? + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) normal in a constexpr function + BOOST_MULTI_ASSERT((this->layout().nelems() % n) == 0); // if you get an assertion here it means that you are partitioning an array with an incommunsurate partition + multi::layout_t new_layout{this->layout(), this->layout().nelems() / n, 0, this->layout().nelems()}; + new_layout.sub().nelems() /= n; + return subarray(new_layout, types::base_); + } + + public: + BOOST_MULTI_HD constexpr auto partitioned(size_type n) const& -> const_subarray { return partitioned_aux_(n); } + + private: + BOOST_MULTI_HD constexpr auto chunked_aux_(size_type count) const { + BOOST_MULTI_ASSERT(this->size() % count == 0); + return partitioned_aux_(this->size() / count); + } + + public: // in Mathematica this is called Partition https://reference.wolfram.com/language/ref/Partition.html in RangesV3 it is called chunk + BOOST_MULTI_HD constexpr auto chunked(size_type count) const& -> const_subarray { return chunked_aux_(count); } + + constexpr auto tiled(size_type count) const& { + BOOST_MULTI_ASSERT(count != 0); + struct divided_type { + const_subarray quotient; + const_subarray remainder; + }; + return divided_type{ + this->taked(this->size() - (this->size() % count)).chunked(count), + this->dropped(this->size() - (this->size() % count)) + }; + } + + private: + constexpr auto reversed_aux_() const { return const_subarray(layout().reverse(), types::base_); } + + public: + constexpr auto reversed() const& -> basic_const_array { return reversed_aux_(); } + constexpr auto reversed() & -> const_subarray { return reversed_aux_(); } + constexpr auto reversed() && -> const_subarray { return reversed_aux_(); } + + private: + BOOST_MULTI_HD constexpr auto transposed_aux_() const { + return const_subarray(layout().transpose(), types::base_); + } + + public: + BOOST_MULTI_HD constexpr auto transposed() const& -> const_subarray { return transposed_aux_(); } + + BOOST_MULTI_FRIEND_CONSTEXPR BOOST_MULTI_HD auto operator~(const_subarray const& self) -> const_subarray { return self.transposed(); } + + private: + BOOST_MULTI_HD constexpr auto rotated_aux_() const { + return const_subarray(layout().rotate(), types::base_); + } + BOOST_MULTI_HD constexpr auto unrotated_aux_() const { + return const_subarray(layout().unrotate(), types::base_); + } + + public: + BOOST_MULTI_HD constexpr auto rotated() const& -> const_subarray { return rotated_aux_(); } + BOOST_MULTI_HD constexpr auto unrotated() const& -> const_subarray { return unrotated_aux_(); } + + private: + template friend struct const_subarray; + + BOOST_MULTI_HD constexpr auto paren_aux_() const& { return const_subarray(this->layout(), this->base_); } + + public: + BOOST_MULTI_HD constexpr auto operator()() const& -> const_subarray { return paren_aux_(); } + + // clang-format off + #if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + BOOST_MULTI_HD constexpr auto operator[]() const& -> const_subarray { return paren_aux_(); } + #endif + // clang-format on + + template class Container = std::vector, template class ContainerSub = std::vector, class... As> + constexpr auto to(As&&... as) const& { + using inner_value_type = typename const_subarray::value_type::value_type; + using container_type = Container>; + + return container_type(this->begin(), this->end(), std::forward(as)...); + } + + private: + template BOOST_MULTI_HD constexpr auto paren_aux_(index_range rng, As... args) const& { return range(rng).rotated().paren_aux_(args...).unrotated(); } + template BOOST_MULTI_HD constexpr auto paren_aux_(intersecting_range inr, As... args) const& -> decltype(auto) { return paren_aux_(intersection(this->extension(), inr), args...); } + template BOOST_MULTI_HD constexpr auto paren_aux_(index idx, As... args) const& -> decltype(auto) { return operator[](idx).paren_aux_(args...); } + + template BOOST_MULTI_HD constexpr auto brckt_aux_(index_range rng, As... args) const& { return range(rng).rotated().paren_aux_(args...).unrotated(); } + template BOOST_MULTI_HD constexpr auto brckt_aux_(intersecting_range inr, As... args) const& -> decltype(auto) { return paren_aux_(intersection(this->extension(), inr), args...); } + template BOOST_MULTI_HD constexpr auto brckt_aux_(index idx, As... args) const& -> decltype(auto) { return operator[](idx).paren_aux_(args...); } + + public: + // vvv DO NOT remove default parameter `= irange` : the default template parameters below help interpret the expression `{first, last}` syntax as index ranges + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1) const& -> decltype(auto) { return paren_aux_(arg1); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2) const& -> decltype(auto) { return paren_aux_(arg1, arg2); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3) const& -> decltype(auto) { return paren_aux_(arg1, arg2, arg3); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) const& -> decltype(auto) { return paren_aux_(arg1, arg2, arg3, arg4, args...); } + + // clang-format off + #if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + // vvv DO NOT remove default parameter `= irange` : the default template parameters below help interpret the expression `{first, last}` syntax as index ranges + // template BOOST_MULTI_HD constexpr auto operator[](A1 arg1) const& -> decltype(auto) { return paren_aux_(arg1); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2) const& -> decltype(auto) { return brckt_aux_(arg1, arg2); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2, A3 arg3) const& -> decltype(auto) { return brckt_aux_(arg1, arg2, arg3); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) const& -> decltype(auto) { return brckt_aux_(arg1, arg2, arg3, arg4, args...); } + #endif + // clang-format on + + private: + template BOOST_MULTI_HD constexpr auto apply_impl_(Tuple const& tuple, std::index_sequence /*012*/) const& -> decltype(auto) { + using std::get; + return this->operator()(get(tuple)...); + } + + public: + template BOOST_MULTI_HD constexpr auto apply(Tuple const& tuple) const& -> decltype(auto) { return apply_impl_(tuple, std::make_index_sequence>{}); } + + using iterator = array_iterator; + using const_iterator = array_iterator; + using move_iterator = array_iterator; + + // using move_iterator = array_iterator; + + // using reverse_iterator [[deprecated]] = std::reverse_iterator< iterator>; + // using const_reverse_iterator [[deprecated]] = std::reverse_iterator; + + const_subarray(const_iterator first, const_iterator last) + : const_subarray(layout_type(first->layout(), first.stride(), 0, (last - first) * first->size()), first.base()) { + BOOST_MULTI_ASSERT(first->layout() == last->layout()); + } + + private: + friend BOOST_MULTI_HD constexpr auto ref(iterator begin, iterator end) -> multi::subarray; + + public: + using ptr = subarray_ptr; + using const_ptr = const_subarray_ptr; // TODO(correaa) add const_subarray_ptr + + using pointer = ptr; + using const_pointer = const_ptr; + + private: + constexpr auto addressof_aux_() const { return ptr(this->base_, this->layout()); } + + public: + constexpr auto addressof() && -> ptr { return addressof_aux_(); } + constexpr auto addressof() & -> ptr { return addressof_aux_(); } + constexpr auto addressof() const& -> const_ptr { return addressof_aux_(); } + + // NOLINTBEGIN(google-runtime-operator) //NOSONAR + + // operator& is not defined for r-values anyway + constexpr auto operator&() && { return addressof(); } // NOLINT(runtime/operator) //NOSONAR + // [[deprecated("controversial")]] + constexpr auto operator&() & { return addressof(); } // NOLINT(runtime/operator) //NOSONAR + // [[deprecated("controversial")]] + constexpr auto operator&() const& { return addressof(); } // NOLINT(runtime/operator) //NOSONAR + + // NOLINTEND(google-runtime-operator) + + private: +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + BOOST_MULTI_HD constexpr auto begin_aux_() const { return iterator(types::base_, this->sub(), this->stride()); } + BOOST_MULTI_HD constexpr auto end_aux_() const { return iterator(types::base_ + this->nelems(), this->sub(), this->stride()); } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + public: + BOOST_MULTI_HD constexpr auto begin() const& -> const_iterator { return begin_aux_(); } + BOOST_MULTI_HD constexpr auto end() const& -> const_iterator { return end_aux_(); } + // friend /*constexpr*/ auto begin(const_subarray const& self) -> const_iterator { return self.begin(); } // NOLINT(whitespace/indent) constexpr doesn't work with nvcc friend + // friend /*constexpr*/ auto end(const_subarray const& self) -> const_iterator { return self.end(); } // NOLINT(whitespace/indent) constexpr doesn't work with nvcc friend + + BOOST_MULTI_HD constexpr auto cbegin() const& { return begin(); } + BOOST_MULTI_HD constexpr auto cend() const& { return end(); } + // friend constexpr auto cbegin(const_subarray const& self) { return self.cbegin(); } + // friend constexpr auto cend(const_subarray const& self) { return self.cend(); } + + using cursor = cursor_t; + using const_cursor = cursor_t; + + private: + BOOST_MULTI_HD constexpr auto home_aux_() const { return cursor(this->base_, this->strides()); } + + public: + BOOST_MULTI_HD constexpr auto home() const& -> const_cursor { return home_aux_(); } + + template< + class Range, + std::enable_if_t>::value, int> = 0, + // std::enable_if_t, int> =0, + class = decltype(Range(std::declval(), std::declval()))> + constexpr explicit operator Range() const { return Range(begin(), end()); } // NOLINT(fuchsia-default-arguments-calls) for example std::vector(it, ti, alloc = {}) + + template + friend constexpr auto operator==(const_subarray const& self, const_subarray const& other) -> bool { + return (self.extension() == other.extension()) && (self.elements() == other.elements()); + } + template + friend constexpr auto operator!=(const_subarray const& self, const_subarray const& other) -> bool { + return (self.extension() != other.extension()) || (self.elements() != other.elements()); + } + + constexpr auto operator==(const_subarray const& other) const -> bool { + return (this->extension() == other.extension()) && (this->elements() == other.elements()); + } + constexpr auto operator!=(const_subarray const& other) const -> bool { + return (this->extension() != other.extension()) || (this->elements() != other.elements()); + } + + friend constexpr auto lexicographical_compare(const_subarray const& self, const_subarray const& other) -> bool { + if(self.extension().first() > other.extension().first()) { + return true; + } + if(self.extension().first() < other.extension().first()) { + return false; + } + return adl_lexicographical_compare( + self.begin(), self.end(), + other.begin(), other.end() + ); + } + + constexpr auto operator<(const_subarray const& other) const& -> bool { return lexicographical_compare(*this, other); } + constexpr auto operator<=(const_subarray const& other) const& -> bool { return *this == other || lexicographical_compare(*this, other); } + constexpr auto operator>(const_subarray const& other) const& -> bool { return other < *this; } + + template::template rebind, std::enable_if_t::element_type>, int> = 0 // NOLINT(modernize-use-constraints) TODO(correaa) + > + constexpr auto static_array_cast() const& { // name taken from std::static_pointer_cast + return subarray(this->layout(), static_cast(this->base_)); // TODO(correaa) might violate constness + } + + template::template rebind, std::enable_if_t::element_type>, int> = 0 // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + > + [[deprecated("violates constness")]] + constexpr auto static_array_cast() const& { // name taken from std::static_pointer_cast + return subarray(this->layout(), static_cast(this->base_)); // TODO(correaa) might violate constness + } + + template::template rebind> + constexpr auto static_array_cast() && { // name taken from std::static_pointer_cast + return subarray(this->layout(), static_cast(this->base_)); + } + + template::template rebind> + constexpr auto static_array_cast() & { // name taken from std::static_pointer_cast + return subarray(this->layout(), static_cast(this->base_)); + } + + private: + template::template rebind, class... Args> + constexpr auto static_array_cast_(Args&&... args) const& { // name taken from std::static_pointer_cast + return subarray(this->layout(), P2{this->base_, std::forward(args)...}); + } + + public: + template + constexpr auto element_transformed(UF&& fun) const& { + return static_array_cast_< + // std::remove_cv_t>>, + std::decay_t>, + transform_ptr< + // std::remove_cv_t>>, + std::decay_t>, + UF, element_const_ptr, std::invoke_result_t>>(std::forward(fun)); + } + template + constexpr auto element_transformed(UF&& fun) & { + return static_array_cast_< + std::decay_t>, + transform_ptr< + std::decay_t>, + UF, element_ptr, std::invoke_result_t>>(std::forward(fun)); + } + template + constexpr auto element_transformed(UF&& fun) && { return element_transformed(std::forward(fun)); } + + template< + class T2, class P2 = typename std::pointer_traits::template rebind, + class Element = typename const_subarray::element, + class PM = T2 Element::*> + constexpr auto member_cast(PM member) const& -> subarray { + static_assert(sizeof(T) % sizeof(T2) == 0, "array_member_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. " + "Use custom alignas structures (to the interesting member(s) sizes) or custom pointers to allow reintrepreation of array elements."); + + return subarray{this->layout().scale(sizeof(T), sizeof(T2)), static_cast(&(this->base_->*member))}; + } + + template< + class T2, class P2 = typename std::pointer_traits::template rebind, + class Element = typename const_subarray::element, + class PM = T2 Element::*> + constexpr auto member_cast(PM member) & -> subarray { + static_assert(sizeof(T) % sizeof(T2) == 0, "array_member_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. " + "Use custom alignas structures (to the interesting member(s) sizes) or custom pointers to allow reintrepreation of array elements"); + + return subarray{this->layout().scale(sizeof(T), sizeof(T2)), static_cast(&(this->base_->*member))}; + } + + template< + class T2, class P2 = typename std::pointer_traits::template rebind, + class Element = typename const_subarray::element, + class PM = T2 Element::*> + constexpr auto member_cast(PM member) && -> subarray { + return this->member_cast(member); + } + + template::template rebind> + using rebind = subarray, D, P2>; + + template< + class T2 = std::remove_const_t, + class P2 = typename std::pointer_traits::template rebind, + std::enable_if_t< // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + std::is_same_v< // check that pointer family is not changed + typename std::pointer_traits::template rebind, + typename std::pointer_traits::template rebind> && + std::is_same_v< // check that only constness is changed + std::remove_const_t::element_type>, std::remove_const_t>, + int> = 0> + constexpr auto const_array_cast() const { + if constexpr(std::is_pointer_v) { + return rebind(this->layout(), const_cast(this->base_)); // NOLINT(cppcoreguidelines-pro-type-const-cast) + } else { + return rebind(this->layout(), reinterpret_cast(this->base_)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) //NOSONAR + } + } + + constexpr auto as_const() const { + return rebind{this->layout(), this->base()}; + } + + private: + template + constexpr auto reinterpret_array_cast_aux_() const -> rebind { + // static_assert( sizeof(T)%sizeof(T2) == 0, + // "error: reinterpret_array_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. Use custom pointers to allow reintrepreation of array elements in other cases" ); + + return { + this->layout().scale(sizeof(T), sizeof(T2)), // NOLINT(bugprone-sizeof-expression) : sizes are compatible according to static assert above + reinterpret_pointer_cast(this->base_) // if ADL gets confused here (e.g. multi:: and thrust::) then adl_reinterpret_pointer_cast will be necessary + }; + } + + public: + template::template rebind> + constexpr auto reinterpret_array_cast() const& { return reinterpret_array_cast_aux_().as_const(); } + + template< + class T2, + class P2 = + std::conditional_t< + std::is_const_v::element_type>, + typename std::pointer_traits::template rebind, + typename std::pointer_traits::template rebind>> + constexpr auto reinterpret_array_cast(size_type count) const& { + static_assert(sizeof(T) % sizeof(T2) == 0, "error: reinterpret_array_cast is limited to integral stride values"); + + BOOST_MULTI_ASSERT(sizeof(T) == sizeof(T2) * static_cast(count)); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : checck implicit size compatibility + + if constexpr(std::is_pointer_v) { + using void_ptr_like = std::conditional_t< + std::is_const_vbase_)>::element_type>, + typename std::pointer_traits::template rebind, + typename std::pointer_traits::template rebind>; + return const_subarray( + layout_t(this->layout().scale(sizeof(T), sizeof(T2)), 1, 0, count).rotate(), + static_cast(static_cast(this->base_)) // NOLINT(bugprone-casting-through-void) direct reinterepret_cast doesn't work here for some exotic pointers (e.g. thrust::pointer) + ); + } else { // TODO(correaa) try to unify both if-branches + return const_subarray( + layout_t(this->layout().scale(sizeof(T), sizeof(T2)), 1, 0, count).rotate(), + reinterpret_cast(this->base_) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-casting-through-void) direct reinterepret_cast doesn't work here + ); + } + } + + template + auto serialize(Archive& arxiv, unsigned int /*version*/) { + using AT = multi::archive_traits; + // if(version == 0) { + // std::for_each(this->begin(), this->end(), [&](reference&& item) {arxiv & AT ::make_nvp("item", std::move(item));}); + // } else { + std::for_each(this->elements().begin(), this->elements().end(), [&](element const& elem) { arxiv& AT ::make_nvp("elem", elem); }); + // } + // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & cereal::make_nvp("item", item);}); + // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & item ;}); + } +}; + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + +template +BOOST_MULTI_HD constexpr auto move(T&& val) noexcept -> decltype(auto) { + if constexpr(has_member_move::value) { + return std::forward(val).move(); + } else { + return std::move(std::forward(val)); + } +} + +template +class move_subarray : public subarray { + // cppcheck-suppress noExplicitConstructor ; see below + BOOST_MULTI_HD constexpr move_subarray(subarray& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) TODO(correa) check if this is necessary + : subarray(other.layout(), other.mutable_base()) {} + + friend class subarray; + + public: + using subarray::operator[]; + BOOST_MULTI_HD constexpr auto operator[](index idx) && -> decltype(auto) { + return multi::move(subarray::operator[](idx)); + } + + using subarray::begin; + using subarray::end; + + BOOST_MULTI_HD constexpr auto begin() && { return this->mbegin(); } + BOOST_MULTI_HD constexpr auto end() && { return this->mend(); } +}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template +class subarray : public const_subarray { + // cppcheck-suppress noExplicitConstructor ; see below + BOOST_MULTI_HD constexpr subarray(const_subarray const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) TODO(correa) check if this is necessary + : subarray(other.layout(), other.mutable_base()) {} + + template friend class subarray; + template friend struct subarray_ptr; + + template friend struct array_iterator; + + public: + subarray(subarray const&) = delete; + + BOOST_MULTI_HD constexpr auto move() { return move_subarray(*this); } + friend BOOST_MULTI_HD constexpr auto move(subarray& self) { return self.move(); } + friend BOOST_MULTI_HD constexpr auto move(subarray&& self) { return std::move(self).move(); } + + using move_iterator = array_iterator; + + subarray(subarray&&) noexcept = default; + ~subarray() = default; + + using ptr = subarray_ptr; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wlarge-by-value-copy" // TODO(correaa) use checked span +#endif + + // NOLINTNEXTLINE(runtime/operator) + BOOST_MULTI_HD constexpr auto operator&() && { return subarray_ptr(this->base_, this->layout()); } // NOLINT(google-runtime-operator) : taking address of a reference-like object should be allowed //NOSONAR + // cppcheck-suppress duplInheritedMember ; to overwrite // NOLINTNEXTLINE(runtime/operator) + BOOST_MULTI_HD constexpr auto operator&() & { return subarray_ptr(this->base_, this->layout()); } // NOLINT(google-runtime-operator) : taking address of a reference-like object should be allowed //NOSONAR + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + using const_subarray::operator&; + // NOLINTNEXTLINE(runtime/operator) + // BOOST_MULTI_HD constexpr auto operator&() const& {return subarray_ptr{this->base_, this->layout()};} // NOLINT(google-runtime-operator) extend semantics //NOSONAR + + using const_subarray::const_subarray; + + using const_subarray::begin; + // cppcheck-suppress duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto begin() && noexcept { return this->begin_aux_(); } + BOOST_MULTI_HD constexpr auto begin() & noexcept { return this->begin_aux_(); } + + using const_subarray::end; + BOOST_MULTI_HD constexpr auto end() && noexcept { return this->end_aux_(); } + // cppcheck-suppress duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto end() & noexcept { return this->end_aux_(); } + + BOOST_MULTI_HD constexpr auto mbegin() { return move_iterator{this->begin()}; } + BOOST_MULTI_HD constexpr auto mend() { return move_iterator{this->end()}; } + + using const_subarray::home; + BOOST_MULTI_HD constexpr auto home() && { return this->home_aux_(); } + BOOST_MULTI_HD constexpr auto home() & { return this->home_aux_(); } + + template constexpr auto assign(It first) & -> It { + adl_copy_n(first, this->size(), begin()); + std::advance(first, this->size()); + return first; + } + template BOOST_MULTI_HD constexpr auto assign(It first) && -> It { return assign(first); } + + template + constexpr auto fill(TT const& value) & -> decltype(auto) { + return adl_fill_n(this->begin(), this->size(), value), *this; + } + constexpr auto fill() & -> decltype(auto) { return fill(typename subarray::element_type{}); } + + template + [[deprecated]] constexpr auto fill(TT const& value) && -> decltype(auto) { return std::move(this->fill(value)); } + [[deprecated]] constexpr auto fill() && -> decltype(auto) { + return std::move(*this).fill(typename subarray::element_type{}); + } + + using const_subarray::strided; + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + constexpr auto strided(difference_type diff) && { return this->strided_aux_(diff); } + constexpr auto strided(difference_type diff) & { return this->strided_aux_(diff); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + using const_subarray::taked; + constexpr auto taked(difference_type count) && -> subarray { return this->taked_aux_(count); } + constexpr auto taked(difference_type count) & -> subarray { return this->taked_aux_(count); } + + using const_subarray::dropped; + // cppcheck-suppress-begin duplInheritedMember ; to ovewrite + constexpr auto dropped(difference_type count) && -> subarray { return this->dropped_aux_(count); } + constexpr auto dropped(difference_type count) & -> subarray { return this->dropped_aux_(count); } + // cppcheck-suppress-end duplInheritedMember ; to ovewrite + + using const_subarray::rotated; + // cppcheck-suppress-begin duplInheritedMember ; to ovewrite + BOOST_MULTI_HD constexpr auto rotated() && -> subarray { return const_subarray::rotated(); } + BOOST_MULTI_HD constexpr auto rotated() & -> subarray { return const_subarray::rotated(); } + // cppcheck-suppress-end duplInheritedMember ; to ovewrite + + using const_subarray::unrotated; + BOOST_MULTI_HD constexpr auto unrotated() && -> subarray { return const_subarray::unrotated(); } + BOOST_MULTI_HD constexpr auto unrotated() & -> subarray { return const_subarray::unrotated(); } + + using const_subarray::transposed; + BOOST_MULTI_HD constexpr auto transposed() && -> subarray { return const_subarray::transposed(); } + BOOST_MULTI_HD constexpr auto transposed() & -> subarray { return const_subarray::transposed(); } + + // BOOST_MULTI_FRIEND_CONSTEXPR BOOST_MULTI_HD + // auto operator~ (subarray const& self) { return self.transposed(); } + BOOST_MULTI_FRIEND_CONSTEXPR BOOST_MULTI_HD auto operator~(subarray& self) { return self.transposed(); } + BOOST_MULTI_FRIEND_CONSTEXPR BOOST_MULTI_HD auto operator~(subarray&& self) { return std::move(self).transposed(); } + + using const_subarray::reindexed; + + template + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto reindexed(index first, Indexes... idxs) & -> subarray { + return const_subarray::reindexed(first, idxs...); + // return ((this->reindexed(first).rotated()).reindexed(idxs...)).unrotated(); + } + template + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto reindexed(index first, Indexes... idxs) && -> subarray { + return const_subarray::reindexed(first, idxs...); + // return ((std::move(*this).reindexed(first).rotated()).reindexed(idxs...)).unrotated(); + } + + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto base() const& -> typename subarray::element_const_ptr { return this->base_; } + BOOST_MULTI_HD constexpr auto base() & -> ElementPtr { return this->base_; } + BOOST_MULTI_HD constexpr auto base() && -> ElementPtr { return this->base_; } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto operator=(const_subarray const& other) & -> subarray& { + if(this == std::addressof(other)) { + return *this; + } + BOOST_MULTI_ASSERT(this->extension() == other.extension()); + this->elements() = other.elements(); + return *this; + } + + constexpr void swap(subarray&& other) && noexcept { + BOOST_MULTI_ASSERT(this->extension() == other.extension()); + adl_swap_ranges(this->elements().begin(), this->elements().end(), std::move(other).elements().begin()); + } + friend constexpr void swap(subarray&& self, subarray&& other) noexcept { std::move(self).swap(std::move(other)); } + + // template>>> friend constexpr void swap(subarray&& self, A&& other) noexcept { std::move(self).swap(std::forward(other)); } + // template>>> friend constexpr void swap(A&& other, subarray&& self) noexcept { std::move(self).swap(std::forward(other)); } + + // template constexpr void swap(Array&& other) && noexcept { + // assert( std::move(*this).extension() == other.extension() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + // this->elements().swap(std::forward(other).elements()); + // // adl_swap_ranges(this->begin(), this->end(), adl_begin(std::forward(o))); + // } + // template constexpr void swap(A&& other) & noexcept {return swap(std::forward(other));} + + // friend constexpr void swap(subarray&& self, subarray&& other) noexcept {std::move(self).swap(std::move(other));} + + // template constexpr void swap(subarray& self, Array&& other) noexcept {self.swap(std::forward(other));} // TODO(correaa) remove + // template constexpr void swap(Array&& other, subarray& self) noexcept {self.swap(std::forward(other));} + + // fix mutation + // template constexpr auto operator=(const_subarray const& other) && -> decltype(auto) {operator=( other ); return *this;} + template constexpr auto operator=(const_subarray const& other) & -> subarray& { + BOOST_MULTI_ASSERT(other.extensions() == this->extensions()); + this->elements() = other.elements(); + return *this; + } + + // fix mutation + template constexpr auto operator=(const_subarray&& other) && -> subarray& { + operator=(std::move(other)); + return *this; + } + template constexpr auto operator=(const_subarray&& other) & -> subarray& { + BOOST_MULTI_ASSERT(this->extensions() == other.extensions()); + this->elements() = std::move(other).elements(); + return *this; + } + + template< + class Range, + class = std::enable_if_t>, // NOLINT(modernize-type-traits) TODO(correaa) in C++20 + class = std::enable_if_t::value>, // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + std::enable_if_t::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + constexpr auto operator=(Range const& rng) & -> subarray& { // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) + BOOST_MULTI_ASSERT(this->size() == static_cast(adl_size(rng))); // TODO(correaa) or use std::cmp_equal? + adl_copy_n(adl_begin(rng), adl_size(rng), this->begin()); + return *this; + } + + template< + class MultiRange, + class = std::enable_if_t>, // NOLINT(modernize-type-traits) TODO(correaa) in C++20 + class = std::enable_if_t::value>, // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + std::enable_if_t::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + constexpr auto operator=(MultiRange const& mrng) & -> subarray& { // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) + BOOST_MULTI_ASSERT(this->extensions() == mrng.extensions()); // TODO(correaa) or use std::cmp_equal? + adl_copy_n(mrng.elements().begin(), this->num_elements(), this->elements().begin()); + return *this; + } + + template< + class Range, + class = std::enable_if_t>, // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 + class = std::enable_if_t::value> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 + > + constexpr auto operator=(Range const& rng) && -> subarray& { + operator=(rng); + return *this; + } + + template + constexpr auto operator=(const_subarray const& other) && -> subarray& { + BOOST_MULTI_ASSERT(this->extension() == other.extension()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + this->elements() = other.elements(); + return *this; + } + + template + constexpr auto operator=(subarray&& other) & -> subarray& { + BOOST_MULTI_ASSERT(this->extension() == other.extension()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + this->elements() = std::move(other).elements(); + return *this; + } + + constexpr auto operator=(const_subarray const& other) const&& -> subarray&; // for std::indirectly_writable + + constexpr auto operator=(subarray const& other) & -> subarray& { + if(this == std::addressof(other)) { + return *this; + } + BOOST_MULTI_ASSERT(this->extension() == other.extension()); + this->elements() = other.elements(); + return *this; + } + constexpr auto operator=(subarray&& other) & noexcept -> subarray& { // TODO(correaa) make conditionally noexcept + // if(this == std::addressof(other)) { return *this; } + BOOST_MULTI_ASSERT(this->extension() == other.extension()); + this->elements() = std::move(other).elements(); + return *this; + } + + auto operator=(std::initializer_list values) && -> subarray& { + operator=(values); + return *this; + } + auto operator=(std::initializer_list values) & -> subarray& { + BOOST_MULTI_ASSERT(static_cast(values.size()) == this->size()); + if(values.size() != 0) { + adl_copy_n(values.begin(), values.size(), this->begin()); + } + return *this; + } + + // BOOST_MULTI_HD constexpr auto operator[](index idx) const& { return static_cast(this->at_aux_(idx)); } // TODO(correaa) use return type to cast + using const_subarray::operator[]; + // BOOST_MULTI_HD constexpr auto operator[](index idx) const& { return const_subarray::operator[](idx); } + + // cppcheck-suppress-start duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto operator[](index idx) && -> typename subarray::reference { return this->at_aux_(idx); } + BOOST_MULTI_HD constexpr auto operator[](index idx) & -> typename subarray::reference { return this->at_aux_(idx); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + using const_subarray::diagonal; + + // template 1) && sizeof(Dummy*), int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress-begin duplInheritedMember ; to override + constexpr auto diagonal() & { return this->diagonal_aux_(); } + constexpr auto diagonal() && { return this->diagonal_aux_(); } + // cppcheck-suppress-end duplInheritedMember ; to override + + using const_subarray::sliced; + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto sliced(index first, index last) && -> subarray { return const_subarray::sliced(first, last); } + BOOST_MULTI_HD constexpr auto sliced(index first, index last) & -> subarray { return const_subarray::sliced(first, last); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + using const_subarray::range; + BOOST_MULTI_HD constexpr auto range(index_range irng) && -> decltype(auto) { return std::move(*this).sliced(irng.front(), irng.front() + irng.size()); } + BOOST_MULTI_HD constexpr auto range(index_range irng) & -> decltype(auto) { return sliced(irng.front(), irng.front() + irng.size()); } + + private: + using const_subarray::paren_aux_; + + BOOST_MULTI_HD constexpr auto paren_aux_() & { return subarray(this->layout(), this->base_); } + BOOST_MULTI_HD constexpr auto paren_aux_() && { return subarray(this->layout(), this->base_); } + + template BOOST_MULTI_HD constexpr auto paren_aux_(index idx) & -> decltype(auto) { return operator[](idx); } + template BOOST_MULTI_HD constexpr auto paren_aux_(index idx) && -> decltype(auto) { return operator[](idx); } + + template BOOST_MULTI_HD constexpr auto paren_aux_(index idx, As... args) & -> decltype(auto) { return operator[](idx).paren_aux_(args...); } + template BOOST_MULTI_HD constexpr auto paren_aux_(index idx, As... args) && -> decltype(auto) { return operator[](idx).paren_aux_(args...); } + + template + BOOST_MULTI_HD constexpr auto paren_aux_(index_range irng, As... args) & { + return this->range(irng).rotated().paren_aux_(args...).unrotated(); + } + template + BOOST_MULTI_HD constexpr auto paren_aux_(index_range irng, As... args) && { + return std::move(*this).range(irng).rotated().paren_aux_(args...).unrotated(); + } + + template BOOST_MULTI_HD constexpr auto paren_aux_(intersecting_range inr, As... args) & -> decltype(auto) { return paren_aux_(intersection(this->extension(), inr), args...); } + template BOOST_MULTI_HD constexpr auto paren_aux_(intersecting_range inr, As... args) && -> decltype(auto) { return paren_aux_(intersection(this->extension(), inr), args...); } + + public: + using const_subarray::operator(); + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto operator()() & -> subarray { return this->paren_aux_(); } + BOOST_MULTI_HD constexpr auto operator()() && -> subarray { return this->paren_aux_(); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1) & -> decltype(auto) { return this->paren_aux_(arg1); } + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2) & -> decltype(auto) { return this->paren_aux_(arg1, arg2); } + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3) & -> decltype(auto) { return this->paren_aux_(arg1, arg2, arg3); } + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) & -> decltype(auto) { return this->paren_aux_(arg1, arg2, arg3, arg4, args...); } + + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1) && -> decltype(auto) { return std::move(*this).paren_aux_(arg1); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2) && -> decltype(auto) { return std::move(*this).paren_aux_(arg1, arg2); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3) && -> decltype(auto) { return std::move(*this).paren_aux_(arg1, arg2, arg3); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) && -> decltype(auto) { return std::move(*this).paren_aux_(arg1, arg2, arg3, arg4, args...); } // NOLINT(whitespace/line_length) pattern line + + // clang-format off + #if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + // template BOOST_MULTI_HD constexpr auto operator[](A1 arg1) & -> decltype(auto) { return this->paren_aux_(arg1); } + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2) & -> decltype(auto) { return this->paren_aux_(arg1, arg2); } + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2, A3 arg3) & -> decltype(auto) { return this->paren_aux_(arg1, arg2, arg3); } + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) & -> decltype(auto) { return this->paren_aux_(arg1, arg2, arg3, arg4, args...); } + + // template BOOST_MULTI_HD constexpr auto operator[](A1 arg1) && -> decltype(auto) { return std::move(*this).paren_aux_(arg1); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2) && -> decltype(auto) { return std::move(*this).paren_aux_(arg1, arg2); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2, A3 arg3) && -> decltype(auto) { return std::move(*this).paren_aux_(arg1, arg2, arg3); } // NOLINT(whitespace/line_length) pattern line + template BOOST_MULTI_HD constexpr auto operator[](A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) && -> decltype(auto) { return std::move(*this).paren_aux_(arg1, arg2, arg3, arg4, args...); } // NOLINT(whitespace/line_length) pattern line + #endif + // clang-format on + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + private: + template + static BOOST_MULTI_HD constexpr auto apply_impl_(Self&& self, Tuple const& tuple, std::index_sequence /*012*/) -> decltype(auto) { + using std::get; // for C++17 compatibility + return std::forward(self)(get(tuple)...); + } + + public: + using const_subarray::apply; + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + template BOOST_MULTI_HD constexpr auto apply(Tuple const& tpl) && -> decltype(auto) { return apply_impl_(std::move(*this), tpl, std::make_index_sequence>()); } + template BOOST_MULTI_HD constexpr auto apply(Tuple const& tpl) & -> decltype(auto) { return apply_impl_(*this, tpl, std::make_index_sequence>()); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + using const_subarray::partitioned; + // cppcheck-suppress duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto partitioned(size_type size) & -> subarray { return this->partitioned_aux_(size); } + + // cppcheck-suppress duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto partitioned(size_type size) && -> subarray { return this->partitioned_aux_(size); } + + using const_subarray::flatted; + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto flatted() & { + // assert(is_flattable() && "flatted doesn't work for all layouts!"); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + multi::layout_t new_layout{this->layout().sub()}; + new_layout.nelems() *= this->size(); // TODO(correaa) : use immutable layout + return subarray(new_layout, this->base_); + } + constexpr auto flatted() && { return this->flatted(); } // cppcheck-suppress duplInheritedMember ; to override + + using const_subarray::reinterpret_array_cast; + + template::template rebind> + constexpr auto reinterpret_array_cast() & { // cppcheck-suppress duplInheritedMember ; to override + // static_assert( sizeof(T)%sizeof(T2) == 0, + // "error: reinterpret_array_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. Use custom pointers to allow reintrepreation of array elements in other cases" ); + + return subarray( + this->layout().scale(sizeof(T), sizeof(T2)), // NOLINT(bugprone-sizeof-expression) : sizes are compatible according to static assert above + reinterpret_pointer_cast(this->base_) // if ADL gets confused here (e.g. multi:: and thrust::) then adl_reinterpret_pointer_cast will be necessary + ); + } + + template::template rebind> + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto reinterpret_array_cast() && { + // static_assert( sizeof(T)%sizeof(T2) == 0, + // "error: reinterpret_array_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. Use custom pointers to allow reintrepreation of array elements in other cases" ); + + return subarray( + this->layout().scale(sizeof(T), sizeof(T2)), // NOLINT(bugprone-sizeof-expression) : sizes are compatible according to static assert above + reinterpret_pointer_cast(this->base_) // if ADL gets confused here (e.g. multi:: and thrust::) then adl_reinterpret_pointer_cast will be necessary + ); + } + + private: + template + constexpr static auto reinterpret_pointer_cast_(ElementPtr const& ptr) -> decltype(auto) { + if constexpr(std::is_pointer_v) { + return static_cast(static_cast(ptr)); // NOLINT(bugprone-casting-through-void) direct reinterepret_cast doesn't work here + } else { + return reinterpret_cast(ptr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-casting-through-void) direct reinterepret_cast doesn't work here + } + } + + public: + template::template rebind> + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto reinterpret_array_cast(size_type count) & { + static_assert(sizeof(T) % sizeof(T2) == 0, "error: reinterpret_array_cast is limited to integral stride values"); + + BOOST_MULTI_ASSERT(sizeof(T) == sizeof(T2) * static_cast(count)); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : checck implicit size compatibility + + layout_t const l1{this->layout().scale(sizeof(T), sizeof(T2)), 1, 0, count}; + auto const l2 = l1.rotate(); + return subarray( + l2, + reinterpret_pointer_cast_(this->base_) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-casting-through-void) direct reinterepret_cast doesn't work here + ); + } + + template::template rebind> + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto reinterpret_array_cast(size_type count) && { + static_assert(sizeof(T) % sizeof(T2) == 0, "error: reinterpret_array_cast is limited to integral stride values"); + + BOOST_MULTI_ASSERT(sizeof(T) == sizeof(T2) * static_cast(count)); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : checck implicit size compatibility + + return subarray( + layout_t(this->layout().scale(sizeof(T), sizeof(T2)), 1, 0, count).rotate(), + reinterpret_pointer_cast_(this->base_) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-casting-through-void) direct reinterepret_cast doesn't work here + ); + } + + using element_move_ptr = multi::move_ptr; + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + constexpr auto element_moved() & { return subarray(this->layout(), element_move_ptr{this->base_}); } + constexpr auto element_moved() && { return element_moved(); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + template + auto serialize(Archive& arxiv, unsigned int /*version*/) { // cppcheck-suppress duplInheritedMember ; to override + using AT = multi::archive_traits; + // if(version == 0) { + // std::for_each(this->begin(), this->end(), [&](typename subarray::reference item) {arxiv & AT ::make_nvp("item", item);}); + // } else { + std::for_each(this->elements().begin(), this->elements().end(), [&](typename subarray::element& elem) { arxiv& AT ::make_nvp("elem", elem); }); + //} + // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & cereal::make_nvp("item", item);}); + // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & item ;}); + } +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template struct array_iterator {}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template +struct array_iterator // NOLINT(fuchsia-multiple-inheritance,cppcoreguidelines-pro-type-member-init,hicpp-member-init) stride_ is not initialized in some constructors +: boost::multi::iterator_facade< + array_iterator, + Element, std::random_access_iterator_tag, + std::conditional_t< + IsConst, + typename std::iterator_traits::template rebind>::reference, + std::conditional_t< + IsMove, + std::add_rvalue_reference_t::reference>>, + typename std::iterator_traits::reference>>, + multi::difference_type> +// , multi::affine, multi::difference_type> +// , multi::decrementable> +// , multi::incrementable> +// , multi::totally_ordered2, void> +{ + using affine = multi::affine, multi::difference_type>; + + using pointer = std::conditional_t< + IsConst, + typename std::pointer_traits::template rebind, + Ptr>; + + private: + using reference_aux = std::conditional_t< + IsConst, + typename std::iterator_traits::template rebind>::reference, + typename std::iterator_traits::reference>; + + public: + using stride_type = Stride; // multi::index; + using reference = std::conditional_t< + IsMove, + std::add_rvalue_reference_t>, + reference_aux>; + + using difference_type = typename affine::difference_type; + using iterator_category = typename stride_traits::category; + using iterator_concept = typename stride_traits::category; + using element_type = typename std::pointer_traits::element_type; // workaround for clang 15 and libc++ in c++20 mode + + template + using rebind = array_iterator, 1, typename std::pointer_traits::template rebind, IsConst, IsMove, Stride>; + + static constexpr dimensionality_type dimensionality = 1; + +#if defined(__cplusplus) && __cplusplus >= 202002L && (!defined(__clang__) || __clang_major__ != 10) + // template, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + constexpr explicit operator Ptr() const& { + static_assert(std::is_base_of_v, "iterator must be continuous"); + return ptr_; + } +#endif + + array_iterator() = default; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + using layout_type = multi::layout_t<0>; + + template< + bool OtherIsConst, std::enable_if_t = 0 // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + > + BOOST_MULTI_HD constexpr explicit array_iterator(array_iterator const& other) + : ptr_{other.base()}, stride_{other.stride()} {} + + template< + class Other, + decltype(multi::detail::implicit_cast(typename Other::pointer{}))* = nullptr, + decltype(std::declval().base())* = nullptr> + // cppcheck-suppress noExplicitConstructor ; because underlying pointer is implicitly convertible + BOOST_MULTI_HD constexpr /*mplct*/ array_iterator(Other const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to reproduce the implicitness of the argument + : ptr_{other.base()}, stride_{other.stride()} {} + + template< + class Other, + decltype(multi::detail::explicit_cast(typename Other::pointer{}))* = nullptr, + decltype(std::declval().data_)* = nullptr> + constexpr explicit array_iterator(Other const& other) + : ptr_{other.data_}, stride_{other.stride_} {} + + template friend struct array_iterator; + + template< + class EElement, typename PPtr, + typename = decltype(multi::detail::implicit_cast(std::declval>().data_))> + BOOST_MULTI_HD constexpr /*impl*/ array_iterator(array_iterator const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to reproduce the implicitness of original pointer + : ptr_{other.base()}, stride_{other.stride_} {} + + BOOST_MULTI_HD constexpr explicit operator bool() const { return static_cast(this->ptr_); } + + BOOST_MULTI_HD constexpr auto operator[](typename array_iterator::difference_type n) const -> decltype(auto) { + return *((*this) + n); + } + + constexpr auto operator->() const { return static_cast(ptr_); } + + using element = Element; + using element_ptr = Ptr; + + static constexpr dimensionality_type rank_v = 1; + + using rank = std::integral_constant; + + BOOST_MULTI_HD explicit constexpr array_iterator(Ptr ptr, Stride stride) + : ptr_{ptr}, stride_{stride} {} + + private: + friend struct const_subarray; // TODO(correaa) fix template parameters + + element_ptr ptr_; +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // warning C4820: '7' bytes padding added after data member 'boost::multi::array_types::base_' [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\test\array_fancyref.cpp.x.vcxproj] +#endif + BOOST_MULTI_NO_UNIQUE_ADDRESS + stride_type stride_; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + public: + BOOST_MULTI_HD constexpr auto operator+(difference_type n) const { return array_iterator{*this} += n; } + BOOST_MULTI_HD constexpr auto operator-(difference_type n) const { return array_iterator{*this} -= n; } + + BOOST_MULTI_HD constexpr auto base() const { return static_cast(ptr_); } + + [[deprecated("use base() for iterator")]] + BOOST_MULTI_HD constexpr auto data() const { return base(); } + + BOOST_MULTI_FRIEND_CONSTEXPR auto base(array_iterator const& self) { return self.base(); } + + BOOST_MULTI_HD constexpr auto stride() const -> stride_type { return stride_; } + friend constexpr auto stride(array_iterator const& self) -> stride_type { return self.stride_; } + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + BOOST_MULTI_HD constexpr auto operator++() -> array_iterator& { + ptr_ += stride_; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + BOOST_MULTI_HD constexpr auto operator--() -> array_iterator& { + ptr_ -= stride_; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + + BOOST_MULTI_HD constexpr auto operator+=(difference_type n) -> array_iterator& { + ptr_ += stride_ * n; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + BOOST_MULTI_HD constexpr auto operator-=(difference_type n) -> array_iterator& { + ptr_ -= stride_ * n; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + BOOST_MULTI_HD constexpr auto operator-(array_iterator const& other) const -> difference_type { + BOOST_MULTI_ASSERT(stride() != 0); + BOOST_MULTI_ASSERT(stride() == other.stride()); + BOOST_MULTI_ASSERT((ptr_ - other.ptr_) % stride() == 0); + return (ptr_ - other.ptr_) / stride(); // with struct-overflow=3 error: assuming signed overflow does not occur when simplifying `X - Y > 0` to `X > Y` [-Werror=strict-overflow] + } + + BOOST_MULTI_HD constexpr auto operator==(array_iterator const& other) const noexcept { + BOOST_MULTI_ASSERT(this->stride_ == other.stride_); + return this->ptr_ == other.ptr_; + } + + BOOST_MULTI_HD constexpr auto operator!=(array_iterator const& other) const noexcept { + BOOST_MULTI_ASSERT(this->stride_ == other.stride_); + return this->ptr_ != other.ptr_; + } + + template = 0> // NOLINT(modernize-use-constraints) for C++20 + BOOST_MULTI_HD constexpr auto operator==(array_iterator const& other) const -> bool { + BOOST_MULTI_ASSERT(this->stride_ == other.stride_); + BOOST_MULTI_ASSERT(stride_ != 0); + return this->ptr_ == other.ptr_; + } + + BOOST_MULTI_HD constexpr auto operator<(array_iterator const& other) const -> bool { + return 0 < other - *this; + } + + BOOST_MULTI_HD constexpr auto operator*() const noexcept -> reference { + return static_cast(*ptr_); + } +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template +using iterator = array_iterator; + +template +class const_subarray +: public array_types { + public: + using types = array_types; + using types::types; + + using element = typename types::element; + using element_ref = typename std::iterator_traits::reference; + using element_cref = typename std::iterator_traits::reference; + using iterator = array_iterator; + + using layout_type = Layout; + + constexpr auto operator=(element const& elem) & -> const_subarray& { + // MULTI_MARK_SCOPE(std::string{"multi::operator= D=0 from "}+typeid(T).name()+" to "+typeid(T).name() ); + adl_copy_n(&elem, 1, this->base_); + return *this; + } + constexpr auto operator=(element const& elem) && -> const_subarray& { + operator=(elem); + return *this; // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) + } + + constexpr auto operator==(element const& elem) const -> bool { + BOOST_MULTI_ASSERT(this->num_elements() == 1); + return adl_equal(&elem, std::next(&elem, this->num_elements()), this->base()); + } + constexpr auto operator!=(element const& elem) const { return !operator==(elem); } + + template + constexpr auto operator=(Range0 const& rng) & -> const_subarray& { + adl_copy_n(&rng, 1, this->base_); + return *this; + } + + constexpr auto elements_at(size_type idx [[maybe_unused]]) const& -> element_cref { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + return *(this->base_); + } + constexpr auto elements_at(size_type idx [[maybe_unused]]) && -> element_ref { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + return *(this->base_); + } + constexpr auto elements_at(size_type idx [[maybe_unused]]) & -> element_ref { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + return *(this->base_); + } + + constexpr auto operator!=(const_subarray const& other) const { return !adl_equal(other.base_, other.base_ + 1, this->base_); } + constexpr auto operator==(const_subarray const& other) const { return adl_equal(other.base_, other.base_ + 1, this->base_); } + + constexpr auto operator<(const_subarray const& other) const { + return adl_lexicographical_compare( + this->base_, this->base_ + this->num_elements(), + other.base_, other.base_ + other.num_elements() + ); + } + + using decay_type = typename types::element; + + BOOST_MULTI_HD constexpr auto operator()() const& -> element_ref { return *(this->base_); } // NOLINT(hicpp-explicit-conversions) + + constexpr operator element_ref() && noexcept { return *(this->base_); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax + constexpr operator element_ref() & noexcept { return *(this->base_); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax + constexpr operator element_cref() const& noexcept { return *(this->base_); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax + + constexpr auto begin() const& = delete; + constexpr auto end() const& = delete; + + template + constexpr auto operator[](IndexType const&) const& = delete; + + auto diagonal() const = delete; + constexpr auto sliced() const& = delete; + constexpr auto partitioned() const& = delete; + + constexpr auto strided(difference_type) const& = delete; + + constexpr auto taked(difference_type) const& = delete; + constexpr auto dropped(difference_type) const& = delete; + + BOOST_MULTI_HD constexpr auto reindexed() const& { return operator()(); } + BOOST_MULTI_HD constexpr auto rotated() const& { return operator()(); } + BOOST_MULTI_HD constexpr auto unrotated() const& { return operator()(); } + + auto transposed() const& = delete; + auto flatted() const& = delete; + auto range() const& -> const_subarray = delete; + + using cursor = cursor_t; + using const_cursor = cursor_t; + + private: + BOOST_MULTI_HD constexpr auto home_aux_() const { return cursor(this->base_, this->strides()); } + + public: + BOOST_MULTI_HD constexpr auto home() const& -> const_cursor { return home_aux_(); } + + private: + template friend class subarray; + + auto paren_aux_() const& { return operator()(); } + + public: + template + BOOST_MULTI_HD constexpr auto apply(Tuple const& /*unused*/) const { + static_assert(std::tuple_size_v == 0); + return operator()(); + } + + BOOST_MULTI_HD constexpr auto operator&() const& { // NOLINT(google-runtime-operator) + return /*TODO(correaa) add const*/ subarray_ptr(this->base_, this->layout()); + } // NOLINT(google-runtime-operator) extend semantics //NOSONAR + + template::template rebind> + constexpr auto reinterpret_array_cast() const& { + return const_subarray{ + typename const_subarray::layout_type{this->layout()}, + reinterpret_pointer_cast(this->base_) + }; + } + + constexpr auto broadcasted() const& { + multi::layout_t<1> const new_layout(this->layout(), 0, 0); // , (std::numeric_limits::max)()); // paren for MSVC macros + return subarray(new_layout, types::base_); + } + + template + auto serialize(Archive& arxiv, unsigned int const /*version*/) const { + using AT = multi::archive_traits; + auto& element_ = *(this->base_); + arxiv& AT::make_nvp("element", element_); + // arxiv & cereal::make_nvp("element", element_); + // arxiv & element_ ; + } +}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template +struct const_subarray // NOLINT(fuchsia-multiple-inheritance) to define operators via CRTP +: multi::random_iterable> +, array_types { + ~const_subarray() = default; // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + + template, int> = 0> // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,modernize-use-constraints) for C++20 + BOOST_MULTI_HD constexpr const_subarray(std::initializer_list const& il) + : array_types( + layout_type(multi::extensions_t<1>( + {0, static_cast(std::size(il))} + )), + std::data(il) + ) {} + + // boost serialization needs `delete`. void boost::serialization::extended_type_info_typeid::destroy(const void*) const [with T = boost::multi::subarray >] + // void operator delete(void* ptr) noexcept = delete; + // void operator delete(void* ptr, void* place ) noexcept = delete; // NOLINT(bugprone-easily-swappable-parameters) + + static constexpr dimensionality_type rank_v = 1; + + using types = array_types; + using types::types; + + using rank = std::integral_constant; + using layout_type = Layout; + using ref_ = const_subarray; + + using element_type = T; + + using element_ptr = typename types::element_ptr; + using element_const_ptr = typename std::pointer_traits::template rebind; + using element_move_ptr = multi::move_ptr; + using element_ref = typename types::element_ref; + using element_cref = typename std::iterator_traits::reference; + + using const_pointer = element_const_ptr; + using pointer = element_ptr; + using const_reference = typename array_types::const_reference; + using reference = typename array_types::reference; + + using default_allocator_type = typename multi::pointer_traits::default_allocator_type; + + constexpr auto get_allocator() const -> default_allocator_type { return default_allocator_of(const_subarray::base()); } + BOOST_MULTI_FRIEND_CONSTEXPR + auto get_allocator(const_subarray const& self) -> default_allocator_type { return self.get_allocator(); } + + using decay_type = array::default_allocator_type>; + + constexpr auto decay() const -> decay_type { return decay_type{*this}; } + BOOST_MULTI_FRIEND_CONSTEXPR auto decay(const_subarray const& self) -> decay_type { return self.decay(); } + + using basic_const_array = const_subarray< + T, 1, + typename std::pointer_traits::template rebind, + Layout>; + + protected: + template constexpr void intersection_assign(A&& other) && { intersection_assign(std::forward(other)); } + template constexpr void intersection_assign(A&& other) & { // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved,cppcoreguidelines-missing-std-forward) false positive clang-tidy 17 + std::for_each( + intersection(types::extension(), extension(other)).begin(), + intersection(types::extension(), extension(other)).end(), + [&](auto const idx) { operator[](idx) = std::forward(other)[idx]; } + ); + } + + template friend struct const_subarray; + template friend struct static_array; // TODO(correaa) check if this is necessary + + template + friend constexpr auto static_array_cast(subarray const&) -> decltype(auto); + + public: + const_subarray(const_subarray const&) = delete; + + friend constexpr auto sizes(const_subarray const& self) noexcept -> typename const_subarray::sizes_type { return self.sizes(); } // needed by nvcc + friend constexpr auto size(const_subarray const& self) noexcept -> typename const_subarray::size_type { return self.size(); } // needed by nvcc + + constexpr auto operator+() const { return decay(); } + + const_subarray(const_subarray&&) noexcept = default; // in C++ 14 this was necessary to return array references from functions + // in c++17 things changed and non-moveable non-copyable types can be returned from functions and captured by auto + + protected: + template friend struct subarray_ptr; + template friend struct array_iterator; + + public: + friend constexpr auto dimensionality(const_subarray const& /*self*/) -> dimensionality_type { return 1; } + + BOOST_MULTI_HD constexpr auto operator&() const& { return const_subarray_ptr{this->base_, this->layout()}; } // NOLINT(google-runtime-operator) extend semantics //NOSONAR + + BOOST_MULTI_HD constexpr void assign(std::initializer_list values) const { + BOOST_MULTI_ASSERT(values.size() == static_cast(this->size())); + if(values.size() != 0) { + assign(values.begin(), values.end()); + } + } + template + constexpr auto assign(It first) & -> It { + adl_copy_n(first, this->size(), this->begin()); + std::advance(first, this->size()); + return first; + } + template + constexpr auto assign(It first) && -> It { return assign(first); } + template + constexpr void assign(It first, It last) & { + BOOST_MULTI_ASSERT(std::distance(first, last) == this->size()); + (void)last; // N_O_L_I_N_T(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + assign(first); + } + template + constexpr void assign(It first, It last) && { assign(first, last); } + + // constexpr auto operator=(const_subarray &&) const& noexcept -> const_subarray const&; // UNIMPLEMENTABLE! TO PASS THE viewable_range CONCEPT!!!, can't be = delete; + constexpr auto operator=(const_subarray&&) & noexcept -> const_subarray&; // UNIMPLEMENTABLE! TO PASS THE viewable_range CONCEPT!!!, can't be = delete; + constexpr auto operator=(const_subarray const&) const -> const_subarray const& = delete; + + template< + class ECPtr, + class = std::enable_if_t && !std::is_same_v> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + > + constexpr auto operator=(const_subarray const& other) const&& -> const_subarray& { + assert(0); + operator=(other); + return *this; + } // required by https://en.cppreference.com/w/cpp/iterator/indirectly_writable for std::ranges::copy_n + + using cursor = cursor_t; + using const_cursor = cursor_t; + + auto diagonal() const = delete; + + private: + BOOST_MULTI_HD constexpr auto home_aux_() const { return cursor(this->base_, this->strides()); } + + public: + BOOST_MULTI_HD constexpr auto home() const& -> const_cursor { return home_aux_(); } + + private: + template friend class subarray; + + BOOST_MULTI_HD constexpr auto at_aux_(index idx) const -> typename const_subarray::reference { // NOLINT(readability-const-return-type) fancy pointers can deref into const values to avoid assignment + if constexpr(std::is_integral_vstride())>) { + BOOST_MULTI_ASSERT((this->stride() == 0 || (this->extension().contains(idx))) && ("out of bounds")); + } + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + return *((this->stride() * idx - this->offset()) + this->base_); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + } + + public: + constexpr auto broadcasted() const& { + // multi::layout_t<1> const self_layout{this->layout()}; + // TODO(correaa) introduce a broadcasted_layout? + multi::layout_t<2> const new_layout(this->layout(), 0, 0, 1); // , (std::numeric_limits::max)()}; + return const_subarray>(new_layout, types::base_); + } + + template class Container = std::vector, class... As> + constexpr auto to(As&&... as) const& { + using inner_value_type = typename const_subarray::value_type; + using container_type = Container; + return container_type(this->begin(), this->end(), std::forward(as)...); + } + + BOOST_MULTI_HD constexpr auto operator[](index idx) const& -> typename const_subarray::const_reference { return at_aux_(idx); } // NOLINT(readability-const-return-type) fancy pointers can deref into const values to avoid assignment + + BOOST_MULTI_HD constexpr auto front() const& -> const_reference { return *begin(); } + BOOST_MULTI_HD constexpr auto back() const& -> const_reference { return *std::prev(end(), 1); } + + private: + template + static constexpr auto apply_impl_(Self&& self, Tuple const& tuple, std::index_sequence /*012*/) -> decltype(auto) { + using std::get; // for C++17 compatibility + return std::forward(self)(get(tuple)...); + } + + public: + template BOOST_MULTI_HD constexpr auto apply(Tuple const& tuple) const& -> decltype(auto) { return apply_impl_(*this, tuple, std::make_index_sequence>()); } + + // template::value == 0), int> = 0> BOOST_MULTI_HD constexpr auto operator[](Tuple const& /*empty*/) const& -> decltype(auto) { return *this; } // NOLINT(modernize-use-constraints) for C++20 + // template::value == 1), int> = 0> BOOST_MULTI_HD constexpr auto operator[](Tuple const& indices) const& -> decltype(auto) { // NOLINT(modernize-use-constraints) for C++20 + // using std::get; + // return operator[](get<0>(indices)); + // } + + // template::value > 1), int> = 0> // NOLINT(modernize-use-constraints) for C++20 + // BOOST_MULTI_HD constexpr auto operator[](Tuple const& indices) const& -> decltype(operator[](std::get<0>(indices))[detail::tuple_tail(indices)]) { + // using std::get; // for C++17 compatibility + // return operator[](get<0>(indices))[detail::tuple_tail(indices)]; + // } + +// Warning C4459 comes from boost::multi_array having a namespace indices which collides with the variable name? +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4459) +#endif + + [[deprecated("BMA compat, finish impl")]] constexpr auto operator[](std::tuple const& indices) const& { + using std::get; + return (*this)({get<0>(indices).front(), get<0>(indices).back() + 1}); + } + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + BOOST_MULTI_HD constexpr auto elements_at(size_type idx) const& -> decltype(auto) { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + return operator[](idx); + } + BOOST_MULTI_HD constexpr auto elements_at(size_type idx) && -> decltype(auto) { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + return operator[](idx); + } + BOOST_MULTI_HD constexpr auto elements_at(size_type idx) & -> decltype(auto) { + BOOST_MULTI_ASSERT(idx < this->num_elements()); + return operator[](idx); + } + + constexpr auto reindexed(index first) && { return reindexed(first); } + constexpr auto reindexed(index first) & { return const_subarray{this->layout().reindex(first), types::base_}; } + + private: + BOOST_MULTI_HD constexpr auto taked_aux_(difference_type count) const { + BOOST_MULTI_ASSERT(count <= this->size()); // calculating size is expensive that is why + typename types::layout_t const new_layout{ + this->layout().sub(), + this->layout().stride(), + this->layout().offset(), + this->stride() * count + }; + return const_subarray{new_layout, this->base_}; + } + + public: + constexpr auto taked(difference_type count) const& -> const_subarray { return taked_aux_(count); } + + private: + BOOST_MULTI_HD constexpr auto dropped_aux_(difference_type count) const { + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + return const_subarray( + this->layout().drop(count), this->base_ + (count * this->layout().stride() /*- this->layout().offset()*/) // TODO(correaa) fix need for offset // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ); + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + } + + public: + constexpr auto dropped(difference_type count) const& -> const_subarray { return dropped_aux_(count); } + + private: + BOOST_MULTI_HD constexpr auto sliced_aux_(index first, index last) const { + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return const_subarray{this->layout().slice(first, last), this->base_ + (first * this->layout().stride() /*- this->layout().offset()*/)}; // TODO(correaa) fix need for offset + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + } + + public: + BOOST_MULTI_HD constexpr auto sliced(index first, index last) const& -> basic_const_array { return basic_const_array{sliced_aux_(first, last)}; } + BOOST_MULTI_HD constexpr auto sliced(index first, index last) & -> const_subarray { return sliced_aux_(first, last); } + BOOST_MULTI_HD constexpr auto sliced(index first, index last) && -> const_subarray { return sliced_aux_(first, last); } + + using elements_iterator = elements_iterator_t; + using celements_iterator = elements_iterator_t; + + using elements_range = elements_range_t; + using const_elements_range = elements_range_t; + + private: + constexpr auto elements_aux_() const { return elements_range{this->base_, this->layout()}; } + + public: + constexpr auto elements() & -> elements_range { return elements_aux_(); } + constexpr auto elements() && -> elements_range { return elements_aux_(); } + constexpr auto elements() const& -> const_elements_range { return const_elements_range{this->base(), this->layout()}; } // TODO(correaa) simplify + + constexpr auto celements() const -> const_elements_range { return elements_aux_(); } + + constexpr auto hull() const -> std::pair { + return {(std::min)(this->base(), this->base() + this->hull_size()), std::abs(this->hull_size())}; // paren for MSVC macros + } + + /*[[gnu::pure]]*/ constexpr auto blocked(index first, index last) & -> const_subarray { + return sliced(first, last).reindexed(first); + } + /*[[gnu::pure]]*/ constexpr auto stenciled(typename const_subarray::index_extension ext) -> const_subarray { + return blocked(ext.first(), ext.last()); + } + + private: + constexpr auto strided_aux_(difference_type diff) const { + auto const new_layout = typename types::layout_t{this->layout().sub(), this->layout().stride() * diff, this->layout().offset(), this->layout().nelems()}; + return subarray(new_layout, types::base_); + } + + public: + constexpr auto strided(difference_type diff) const& -> const_subarray { return strided_aux_(diff); } + + BOOST_MULTI_HD constexpr auto sliced(index first, index last, difference_type stride) const& -> basic_const_array { return sliced(first, last).strided(stride); } + + BOOST_MULTI_HD constexpr auto range(index_range const& rng) const& { return sliced(rng.front(), rng.last()); } + + private: + BOOST_MULTI_HD constexpr auto paren_aux_() const& { return const_subarray(this->layout(), this->base_); } + + BOOST_MULTI_HD constexpr auto paren_aux_(index idx) const& -> decltype(auto) { return operator[](idx); } + + BOOST_MULTI_HD constexpr auto paren_aux_(index_range const& rng) const& { return range(rng); } + + public: + BOOST_MULTI_HD constexpr auto operator()() const& { return paren_aux_(); } +#if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + BOOST_MULTI_HD constexpr auto operator[]() const& -> const_subarray { return paren_aux_(); } +#endif + + BOOST_MULTI_HD constexpr auto operator()(index idx) const -> decltype(auto) { return operator[](idx); } + + BOOST_MULTI_HD constexpr auto operator()(index_range const& rng) const& { return range(rng); } + + private: + constexpr auto paren_aux_(intersecting_range const& rng) const& -> decltype(auto) { return paren_aux_(intersection(this->extension(), rng)); } + + public: + BOOST_MULTI_HD constexpr auto operator()(intersecting_range const& isrange) const& -> decltype(auto) { return paren_aux_(isrange); } + + template + BOOST_MULTI_HD constexpr auto operator()(Args&&... args) const& -> decltype(paren_(*this, std::forward(args)...)) { + return paren_(*this, std::forward(args)...); + } + + private: + BOOST_MULTI_HD constexpr auto halved_aux_() const { + auto new_layout = this->layout().halve(); + return subarray(new_layout, this->base_); + } + + public: + BOOST_MULTI_HD constexpr auto halved() const& -> const_subarray { return halved_aux_(); } + + private: + BOOST_MULTI_HD constexpr auto partitioned_aux_(size_type size) const { + BOOST_MULTI_ASSERT(size != 0); + BOOST_MULTI_ASSERT((this->layout().nelems() % size) == 0); // TODO(correaa) remove assert? truncate left over? (like mathematica) // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + multi::layout_t<2> new_layout{this->layout(), this->layout().nelems() / size, 0, this->layout().nelems()}; + new_layout.sub().nelems() /= size; // TODO(correaa) : don't use mutation + return subarray(new_layout, types::base_); + } + + public: + BOOST_MULTI_HD constexpr auto partitioned(size_type size) const& -> const_subarray { return partitioned_aux_(size); } + + private: + BOOST_MULTI_HD constexpr auto chunked_aux_(size_type size) const { + BOOST_MULTI_ASSERT(this->size() % size == 0); + return partitioned_aux_(this->size() / size); + } + + public: // in Mathematica this is called Partition https://reference.wolfram.com/language/ref/Partition.html in RangesV3 it is called chunk + BOOST_MULTI_HD constexpr auto chunked(size_type size) const& -> const_subarray { return chunked_aux_(size); } + // BOOST_MULTI_HD constexpr auto chunked(size_type size) & -> partitioned_type {return chunked_aux_(size);} + // BOOST_MULTI_HD constexpr auto chunked(size_type size) && -> partitioned_type {return chunked_aux_(size);} + + constexpr auto tiled(size_type count) const& { + BOOST_MULTI_ASSERT(count != 0); + struct divided_type { + const_subarray quotient; + const_subarray remainder; + }; + return divided_type{ + this->taked(this->size() - (this->size() % count)).chunked(count), + this->dropped(this->size() - (this->size() % count)) + }; + } + + private: + constexpr auto reversed_aux_() const -> const_subarray { + auto new_layout = this->layout(); + new_layout.reverse(); + return {new_layout, types::base_}; + } + + public: + constexpr auto reversed() const& -> basic_const_array { return reversed_aux_(); } + constexpr auto reversed() & -> const_subarray { return reversed_aux_(); } + constexpr auto reversed() && -> const_subarray { return reversed_aux_(); } + + friend constexpr auto reversed(const_subarray const& self) -> basic_const_array { return self.reversed(); } + friend constexpr auto reversed(const_subarray& self) -> const_subarray { return self.reversed(); } + friend constexpr auto reversed(const_subarray&& self) -> const_subarray { return std::move(self).reversed(); } + + // friend constexpr auto rotated(const_subarray const& self) -> decltype(auto) {return self. rotated();} + // friend constexpr auto unrotated(const_subarray const& self) -> decltype(auto) {return self.unrotated();} + + // constexpr auto rotated() & -> decltype(auto) {return operator()();} + // constexpr auto rotated() && -> decltype(auto) {return operator()();} + BOOST_MULTI_HD constexpr auto rotated() const& { return operator()(); } + BOOST_MULTI_HD constexpr auto unrotated() const& { return operator()(); } + + auto transposed() const& = delete; + auto flatted() const& = delete; + + using iterator = typename multi::array_iterator; + using const_iterator = typename multi::array_iterator; + using move_iterator = typename multi::array_iterator; + + using reverse_iterator [[deprecated]] = std::reverse_iterator; + using const_reverse_iterator [[deprecated]] = std::reverse_iterator; + + struct [[deprecated("BMA compatibility")]] index_gen { + auto operator[](irange const& rng) const { return std::make_tuple(rng); } + }; + using extent_gen [[deprecated("BMA compatibility")]] = std::array; + using extent_range [[deprecated("BMA compatibility")]] = irange; + + template< + class Range, + std::enable_if_t>::value, int> = 0, + std::enable_if_t>::value, int> = 0, + class = decltype((void)std::declval().begin(), std::declval().end()), + class = decltype(Range{std::declval(), std::declval()})> + constexpr explicit operator Range() const { + // vvv Range{...} needed by Windows GCC? + return Range{begin(), end()}; // NOLINT(fuchsia-default-arguments-calls) e.g. std::vector(it, it, alloc = {}) + } + + private: +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + BOOST_MULTI_HD constexpr auto begin_aux_() const { return iterator{this->base_, this->stride()}; } + BOOST_MULTI_HD constexpr auto end_aux_() const { return iterator{this->base_ + types::nelems(), this->stride()}; } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + public: + BOOST_MULTI_HD constexpr auto begin() const& -> const_iterator { return begin_aux_(); } + BOOST_MULTI_HD constexpr auto end() const& -> const_iterator { return end_aux_(); } + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + [[deprecated("implement as negative stride")]] constexpr auto rbegin() const& { return const_reverse_iterator(end()); } // TODO(correaa) implement as negative stride? + [[deprecated("implement as negative stride")]] constexpr auto rend() const& { return const_reverse_iterator(begin()); } // TODO(correaa) implement as negative stride? +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + BOOST_MULTI_HD constexpr auto cbegin() const& -> const_iterator { return begin(); } + BOOST_MULTI_HD constexpr auto cend() const& -> const_iterator { return end(); } + + BOOST_MULTI_FRIEND_CONSTEXPR auto cbegin(const_subarray const& self) { return self.cbegin(); } + BOOST_MULTI_FRIEND_CONSTEXPR auto cend(const_subarray const& self) { return self.cend(); } + + template constexpr auto assign(It first) && -> decltype(adl_copy_n(first, std::declval(), std::declval()), void()) { + return adl_copy_n(first, this->size(), std::move(*this).begin()), void(); + } + + friend constexpr auto operator==(const_subarray const& self, const_subarray const& other) -> bool { + return self.extension() == other.extension() && self.elements() == other.elements(); + } + + friend constexpr auto operator!=(const_subarray const& self, const_subarray const& other) -> bool { + return self.extension() != other.extension() || self.elements() != other.elements(); + } + + template + friend constexpr auto operator==(const_subarray const& self, const_subarray const& other) -> bool { + return self.extension() == other.extension() && self.elements() == other.elements(); + } + + template + friend constexpr auto operator!=(const_subarray const& self, const_subarray const& other) -> bool { + return self.extension() != other.extension() || self.elements() != other.elements(); + } + + friend constexpr auto operator<(const_subarray const& self, const_subarray const& other) -> bool { return lexicographical_compare_(self, other); } + friend constexpr auto operator>(const_subarray const& self, const_subarray const& other) -> bool { return lexicographical_compare_(other, self); } // NOLINT(readability-suspicious-call-argument) + + friend constexpr auto operator<=(const_subarray const& self, const_subarray const& other) -> bool { return lexicographical_compare_(self, other) || self == other; } + friend constexpr auto operator>=(const_subarray const& self, const_subarray const& other) -> bool { return lexicographical_compare_(other, self) || self == other; } // NOLINT(readability-suspicious-call-argument) + + private: + template + static constexpr auto lexicographical_compare_(A1 const& self, A2 const& other) -> bool { // NOLINT(readability-suspicious-call-argument) + if(self.extension().first() > other.extension().first()) { + return true; + } + if(self.extension().first() < other.extension().first()) { + return false; + } + return adl_lexicographical_compare(adl_begin(self), adl_end(self), adl_begin(other), adl_end(other)); + } + + public: + template::template rebind> + constexpr auto static_array_cast() const -> subarray { // name taken from std::static_pointer_cast + return {this->layout(), static_cast(this->base_)}; + } + template::template rebind, class... Args> + constexpr auto static_array_cast(Args&&... args) const -> subarray { // name taken from std::static_pointer_cast + return { + this->layout(), P2{this->base_, std::forward(args)...} + }; + } + + template + constexpr auto element_transformed(UF&& fun) const& { + return static_array_cast< + // std::remove_cv_t>>, + std::decay_t>, + transform_ptr< + // std::remove_cv_t>>, + std::decay_t>, + UF, element_const_ptr, std::invoke_result_t>>(std::forward(fun)); + } + template + constexpr auto element_transformed(UF&& fun) & { + return static_array_cast< + std::decay_t>, + transform_ptr< + std::decay_t>, + UF, element_ptr, std::invoke_result_t>>(std::forward(fun)); + } + template + constexpr auto element_transformed(UF&& fun) && { return element_transformed(std::forward(fun)); } + + template< + class T2, class P2 = typename std::pointer_traits::template rebind, + class Element = typename const_subarray::element, + class PM = T2 std::decay_t::*> + constexpr auto member_cast(PM member) const { + static_assert(sizeof(T) % sizeof(T2) == 0, "array_member_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. " + "Use custom alignas structures (to the interesting member(s) sizes) or custom pointers to allow reintrepreation of array elements"); + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret is what the function does. alternative for GCC/NVCC + auto&& r1 = (*(reinterpret_cast(const_subarray::base_))).*member; // ->*pm; + auto* p1 = &r1; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) TODO(correaa) find a better way + P2 p2 = reinterpret_cast(p1); // NOSONAR +#else + auto p2 = static_cast(&(this->base_->*member)); // this crashes nvcc 11.2-11.4 and some? gcc compiler +#endif + return subarray(this->layout().scale(sizeof(T), sizeof(T2)), p2); + } + + template::template rebind> + constexpr auto reinterpret_array_cast() const& { + BOOST_MULTI_ASSERT(this->layout().stride() * static_cast(sizeof(T)) % static_cast(sizeof(T2)) == 0); + + return const_subarray{ + layout_type{this->layout().sub(), this->layout().stride() * static_cast(sizeof(T)) / static_cast(sizeof(T2)), this->layout().offset() * static_cast(sizeof(T)) / static_cast(sizeof(T2)), this->layout().nelems() * static_cast(sizeof(T)) / static_cast(sizeof(T2))}, + reinterpret_pointer_cast(this->base_) + }; + } + + template::template rebind> + constexpr auto reinterpret_array_cast(size_type n) const& -> subarray, 2, P2> { // TODO(correaa) : use rebind for return type + static_assert(sizeof(T) % sizeof(T2) == 0, "error: reinterpret_array_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. Use custom pointers to allow reintrepreation of array elements in other cases"); + + return subarray, 2, P2>{ + layout_t<2>{this->layout().scale(sizeof(T), sizeof(T2)), 1, 0, n}, + reinterpret_pointer_cast(this->base()) + } + .rotated(); + } + + template + void serialize(Archive& arxiv, unsigned /*version*/) { + using AT = multi::archive_traits; + std::for_each(this->begin(), this->end(), [&](reference& item) { arxiv& AT ::make_nvp("item", item); }); + // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & cereal::make_nvp("item", item);}); + // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & item ;}); + } +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template +constexpr auto static_array_cast(Array&& self, Args&&... args) -> decltype(auto) { + return std::forward(self).template static_array_cast(std::forward(args)...); +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template< + typename T, dimensionality_type D, typename ElementPtr = T*, + class Layout = + std::conditional_t< + (D == 1), + // contiguous_layout<>, // 1, typename std::pointer_traits::difference_type>, + multi::layout_t::difference_type>, + multi::layout_t::difference_type>>> +class array_ref : public subarray { + using subarray_layout = Layout; + + using subarray_base = subarray; + + public: + ~array_ref() = default; // lints(cppcoreguidelines-special-member-functions) + + using layout_type = typename subarray_base::layout_t; + using iterator = typename subarray_base::iterator; + + constexpr array_ref() = delete; // because reference cannot be unbound + + // [[deprecated("references are not copyable, use auto&&")]] + array_ref(array_ref const&) = delete; // don't try to use `auto` for references, use `auto&&` or explicit value type + array_ref(array_ref&&) = delete; + + array_ref(iterator, iterator) = delete; + + // return type removed for MSVC + friend constexpr auto sizes(array_ref const& self) noexcept /*-> typename array_ref::sizes_type*/ { return self.sizes(); } // needed by nvcc + friend constexpr auto size(array_ref const& self) noexcept /*-> typename array_ref::size_type*/ { return self.size(); } // needed by nvcc + +#if defined(BOOST_MULTI_HAS_SPAN) && !defined(__NVCC__) + template && (D == 1), int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + constexpr explicit operator std::span() const& { return std::span(this->data_elements(), this->size()); } +#endif + + template>, decltype(multi::detail::explicit_cast(std::declval()))* = nullptr> + constexpr explicit array_ref(array_ref&& other) + : subarray_base(other.layout(), ElementPtr{std::move(other).base()}) {} // cppcheck-suppress internalAstError ; bug in cppcheck 2.13.0 + + template>, decltype(multi::detail::implicit_cast(std::declval()))* = nullptr> + // cppcheck-suppress noExplicitConstructor ; to allow terse syntax + constexpr /*mplct*/ array_ref(array_ref&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions,bugprone-use-after-move,hicpp-invalid-access-moved) + : subarray_base(other.layout(), ElementPtr{std::move(other).base()}) {} // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + + constexpr array_ref(ElementPtr dat, ::boost::multi::extensions_t const& xs) noexcept // TODO(correa) eliminate this ctor + : subarray_base(typename subarray_base::types::layout_t(xs), dat) {} + + constexpr array_ref(::boost::multi::extensions_t exts, ElementPtr dat) noexcept + : subarray_base{typename array_ref::types::layout_t(exts), dat} {} + + // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) // compatibility with legacy c-arrays + template< + class Array, + std::enable_if_t< // NOLINT(modernize-use-constraints) for C++20 + !std::is_array_v && !std::is_base_of_v> && std::is_convertible_v())), ElementPtr>, int> = 0> + // cppcheck-suppress noExplicitConstructor ; to allow terse syntax and because a reference to c-array can be represented as an array_ref + constexpr array_ref(Array& array) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax and because a reference to c-array can be represented as an array_ref + : array_ref(multi::data_elements(array), extensions(array)) {} + // NOLINTEND(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + // cppcheck-suppress noExplicitConstructor ; to allow terse syntax and because a reference to c-array can be represented as an array_ref + constexpr array_ref( // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax and because a reference to c-array can be represented as an array_ref + T& elem // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + ) + : array_ref(&elem, {}) {} + + template + // cppcheck-suppress noExplicitConstructor ; see below + constexpr array_ref(TT (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-explicit-constructor,hicpp-explicit-conversions) : for backward compatibility // NOSONAR + : array_ref( + ::boost::multi::extensions(arr), + ::boost::multi::data_elements(arr) + ) {} + + template + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(runtime/explicit) + constexpr array_ref(std::array& arr) : array_ref(::boost::multi::extensions(arr), ::boost::multi::data_elements(arr)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) array_ptr is more general than pointer c-array support legacy c-arrays // NOSONAR + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) bug in clang-tidy 19? + template, int> = 0> // NOLINT(modernize-use-constraints) for C++20 + // cppcheck-suppress noExplicitConstructor + array_ref(std::initializer_list il) + : array_ref( + (il.size() == 0) ? nullptr + : il.begin(), // TODO(correaa) simplify conditional by still using a il pointer in empty case? + typename array_ref::extensions_type{static_cast(il.size())} + ) {} + + using subarray_base::operator=; + + private: + template constexpr auto copy_elements_(It first) { + return adl_copy_n(first, this->num_elements(), this->data_elements()); + } + + public: + BOOST_MULTI_HD constexpr auto data_elements() const& { return static_cast(array_ref::base_); } + + template>, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + constexpr auto operator=(array_ref const& other) && -> array_ref& { // if MSVC complains here, it probably needs /EHsc /permissive- for C++17 mode + BOOST_MULTI_ASSERT(this->extensions() == other.extensions()); + array_ref::copy_elements_(other.data_elements()); + return *this; + } + + constexpr auto operator=(array_ref const& other) & -> array_ref& { + if(this == std::addressof(other)) { + return *this; + } // lints(cert-oop54-cpp) + // TODO(correaa) assert on extensions, not on num elements + BOOST_MULTI_ASSERT(this->num_elements() == other.num_elements()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + array_ref::copy_elements_(other.data_elements()); + return *this; + } + + constexpr auto operator=(array_ref const& other) && -> array_ref& { + if(this == std::addressof(other)) { + return *this; + } // lints(cert-oop54-cpp) + operator=(other); + return *this; + } + + constexpr auto operator=(array_ref&& other) & noexcept(std::is_nothrow_copy_assignable_v) // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations) //NOSONAR(cppS5018) + -> array_ref& { + if(this == std::addressof(other)) { + return *this; + } // lints(cert-oop54-cpp) + operator=(std::as_const(other)); + return *this; + } + + constexpr auto operator=(array_ref&& other) && noexcept(std::is_nothrow_copy_assignable_v) // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations) + -> array_ref& { + if(this == std::addressof(other)) { + return *this; + } // lints(cert-oop54-cpp) + operator=(std::as_const(other)); + return *this; + } + + template + auto operator=(array_ref const& other) & -> array_ref& { + BOOST_MULTI_ASSERT(this->extensions() == other.extensions()); + adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); + return *this; + } + + template + constexpr auto operator=(array_ref const& other) && -> array_ref& { + this->operator=(other); + return *this; // lints (cppcoreguidelines-c-copy-assignment-signature) + } + + using elements_type = array_ref; + using celements_type = array_ref; + + private: + constexpr auto elements_aux_() const { + return elements_type{ + this->base_, + typename elements_type::extensions_type{multi::iextension{this->num_elements()}} + }; + } + + public: + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + constexpr auto elements() const& -> celements_type { return elements_aux_(); } + constexpr auto elements() & -> elements_type { return elements_aux_(); } + constexpr auto elements() && -> elements_type { return elements_aux_(); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + friend constexpr auto elements(array_ref& self) -> elements_type { return self.elements(); } + friend constexpr auto elements(array_ref&& self) -> elements_type { return std::move(self).elements(); } + friend constexpr auto elements(array_ref const& self) -> celements_type { return self.elements(); } + + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto celements() const& { return celements_type{array_ref::data_elements(), array_ref::num_elements()}; } + + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + constexpr auto element_moved() & { return array_ref(this->extensions(), typename array_ref::element_move_ptr{this->base_}); } + constexpr auto element_moved() && { return element_moved(); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + template + friend constexpr auto operator==(array_ref const& self, array_ref const& other) -> bool { + if(self.extensions() != other.extensions()) { + return false; + } + return adl_equal( + other.data_elements(), other.data_elements() + other.num_elements(), // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) use span? + self.data_elements() + ); + } + + template + friend constexpr auto operator!=(array_ref const& self, array_ref const& other) -> bool { + if(self.extensions() != other.extensions()) { + return true; + } + return !adl_equal( + other.data_elements(), other.data_elements() + other.num_elements(), // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) use span? + self.data_elements() + ); + // return ! operator==(self, other); // commented due to bug in nvcc 22.11 + } + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + + BOOST_MULTI_HD constexpr auto data_elements() & -> typename array_ref::element_ptr { return array_ref::base_; } + BOOST_MULTI_HD constexpr auto data_elements() && -> typename array_ref::element_ptr { return array_ref::base_; } + + friend constexpr auto data_elements(array_ref&& self) -> typename array_ref::element_ptr { return std::move(self).data_elements(); } + + // data() is here for compatibility with std::vector + template = 0> constexpr auto data() const& { return data_elements(); } // NOLINT(modernize-use-constraints) TODO(correaa) + template = 0> constexpr auto data() && { return data_elements(); } // NOLINT(modernize-use-constraints) TODO(correaa) + template = 0> constexpr auto data() & { return data_elements(); } // NOLINT(modernize-use-constraints) TODO(correaa) + + // TODO(correaa) : find a way to use [[deprecated("use data_elements()")]] for friend functions + friend constexpr auto data(array_ref const& self) -> typename array_ref::element_ptr { return self.data_elements(); } + friend constexpr auto data(array_ref& self) -> typename array_ref::element_ptr { return self.data_elements(); } + friend constexpr auto data(array_ref&& self) -> typename array_ref::element_ptr { return std::move(self).data_elements(); } + + using decay_type = typename array_ref::decay_type; + + // cppcheck-suppress duplInheritedMember ; to override + constexpr auto decay() const& -> decay_type const& { return static_cast(*this); } + + private: + template + void check_sizes_() const { + using std::get; // for C++17 compatibility + if(size_type{get
(this->sizes())} != size_type{std::extent_v}) { + throw std::bad_cast{}; + } + if constexpr(DD + 1 != D) { + check_sizes_(); + } + } + + template static auto launder_(TT* pointer) -> TT* { +#if defined(__cpp_lib_launder) && (__cpp_lib_launder >= 201606L) + return std::launder(pointer); +#else + return pointer; +#endif + } + + template friend struct array; + + template + constexpr auto to_carray_() & -> TTN& { + check_sizes_(); + return *launder_(reinterpret_cast(array_ref::base_)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + constexpr auto to_carray_() const& -> TTN const& { + check_sizes_(); + return *launder_(reinterpret_cast(array_ref::base_)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + public: + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + template, int> = 0> // NOLINT(modernize-use-constraints) for C++20 + constexpr explicit operator TTN const&() const& { return to_carray_(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + + template, int> = 0> // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,modernize-use-constraints) for C++20 + constexpr explicit operator TTN&() && { return to_carray_(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + + template, int> = 0> // NOLINT(modernize-use-constraints) for C++20 + constexpr explicit operator TTN&() & { return to_carray_(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + private: + template + auto serialize_structured_(Ar& arxiv, unsigned int const version) { + subarray_base::serialize(arxiv, version); + } + + template + auto serialize_flat_(Archive& arxiv, unsigned int const /*version*/) { + using AT = multi::archive_traits; + arxiv& AT::make_nvp("elements", AT::make_array(this->data_elements(), static_cast(this->num_elements()))); + } + + // template> + // auto serialize_binary_if(std::true_type, Ar& ar) { + // ar & AT::make_nvp("binary_data", AT::make_binary_object(this->data_elements(), static_cast(this->num_elements())*sizeof(typename array_ref::element))); + // } + // template + // auto serialize_binary_if(std::false_type, Ar& ar) {return serialize_flat(ar);} + + public: + template + auto serialize(Archive& arxiv, unsigned int const version) { // cppcheck-suppress duplInheritedMember ; + serialize_flat_(arxiv, version); + // serialize_structured_(ar, version); + // switch(version) { + // case static_cast( 0): return serialize_flat_(arxiv); + // case static_cast(-1): return serialize_structured_(arxiv, version); + // // case 2: return serialize_binary_if(std::is_trivially_copy_assignable{}, arxiv); + // default: + // if( this->num_elements() <= version ){serialize_structured_(arxiv, version);} + // else {serialize_flat_ (arxiv );} + // } + } +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template::template rebind> +using array_cref = array_ref, D, Ptr>; + +template +using array_mref = array_ref< + std::decay_t, D, + std::move_iterator>; + +template +constexpr auto ref( + TT (&arr)[N] // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) interact with legacy // NOSONAR +) { + return array_ref, std::rank_v>(arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) interact with legacy +} + +template +struct array_ptr +: subarray_ptr::layout_t, false> { + using basic_ptr = subarray_ptr::layout_t, false>; + + constexpr array_ptr(Ptr data, multi::extensions_t extensions) + : basic_ptr{data, typename array_ref::layout_t(extensions)} {} + + constexpr explicit array_ptr(std::nullptr_t nil) : array_ptr{nil, multi::extensions_t{}} {} + + template + // cppcheck-suppress constParameterPointer ; workaround cppcheck 2.11 + constexpr explicit array_ptr(CArray* data) : array_ptr{data_elements(*data), extensions(*data)} {} + + template< + class TT, std::size_t N, + std::enable_if_t())), Ptr>, int> = 0 // NOLINT(modernize-use-constraints,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) support legacy c-arrays TODO(correaa) for C++20 + > + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(runtime/explicit) + constexpr array_ptr(TT (*array)[N]) : array_ptr{data_elements(*array), extensions(*array)} {} // NOLINT(modernize-use-constraints,google-explicit-constructor,hicpp-explicit-conversions,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) array_ptr is more general than pointer c-array support legacy c-arrays TODO(correaa) for C++20 // NOSONAR + + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto operator*() const -> array_ref { + return array_ref((*static_cast::layout_t, false> const&>(*this)).extensions(), this->base()); + } +}; + +template +class [[deprecated("no good uses found")]] array_ptr { // TODO(correaa) make it private mutable member + mutable multi::array_ref ref_; // TODO(correaa) implement array_ptr like other cases + + public: + ~array_ptr() = default; + + constexpr array_ptr(array_ptr const&) = default; + constexpr array_ptr(array_ptr&&) noexcept = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) TODO(correaa) change the implementation like the other cases + + constexpr explicit array_ptr(Ptr dat, typename multi::array_ref::extensions_type extensions) : ref_(dat, extensions) {} + constexpr explicit array_ptr(Ptr dat) : array_ptr(dat, typename multi::array_ref::extensions_type{}) {} + + constexpr explicit operator bool() const { return ref_.base(); } // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr explicit operator Ptr() const { return ref_.base(); } + + auto operator=(array_ptr const&) -> array_ptr& = default; + auto operator=(array_ptr&&) noexcept -> array_ptr& = default; + + friend constexpr auto operator==(array_ptr const& self, array_ptr const& other) -> bool { return self.ref_.base() == other.ref_.base(); } + friend constexpr auto operator!=(array_ptr const& self, array_ptr const& other) -> bool { return self.ref_.base() != other.ref_.base(); } + + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto operator*() const -> multi::array_ref& { return ref_; } // moLINT(cppcoreguidelines-pro-type-const-cast) : TODO(correaa) make ref base class a mutable member + + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto operator->() const -> multi::array_ref* { return &ref_; } // moLINT(cppcoreguidelines-pro-type-const-cast) : TODO(correaa) make ref base class a mutable member +}; + +template +constexpr auto addressof(TT (&array)[N]) { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + return array_ptr< + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + std::decay_t>, static_cast(std::rank{}), std::remove_all_extents_t*>{&array}; +} + +template +using array_cptr = array_ptr::template rebind>; + +template +constexpr auto make_array_ref(P data, multi::extensions_t extensions) { + return array_ref::value_type, D, P>(data, extensions); +} + +template auto make_array_ref(P data, extensions_t<0> exts) { return make_array_ref<0>(data, exts); } +template auto make_array_ref(P data, extensions_t<1> exts) { return make_array_ref<1>(data, exts); } +template auto make_array_ref(P data, extensions_t<2> exts) { return make_array_ref<2>(data, exts); } +template auto make_array_ref(P data, extensions_t<3> exts) { return make_array_ref<3>(data, exts); } +template auto make_array_ref(P data, extensions_t<4> exts) { return make_array_ref<4>(data, exts); } +template auto make_array_ref(P data, extensions_t<5> exts) { return make_array_ref<5>(data, exts); } + +#ifdef __cpp_deduction_guides + +template::value_type> // pointer_traits doesn't have ::value_type +array_ptr(It) -> array_ptr; +template::value_type> // pointer_traits doesn't have ::value_type +array_ptr(It, index_extensions<0>) -> array_ptr; +template::value_type> +array_ptr(It, index_extensions<1>) -> array_ptr; +template::value_type> +array_ptr(It, index_extensions<2>) -> array_ptr; +template::value_type> +array_ptr(It, index_extensions<3>) -> array_ptr; + +template< + class T, + std::size_t N, + typename V = std::remove_all_extents_t, std::size_t D = std::rank_v // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + > +array_ptr(T (*)[N]) -> array_ptr(D)>; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + +template array_ref(Ptr, index_extensions<0>) -> array_ref::value_type, 0, Ptr>; +template array_ref(Ptr, index_extensions<1>) -> array_ref::value_type, 1, Ptr>; +template array_ref(Ptr, index_extensions<2>) -> array_ref::value_type, 2, Ptr>; +template array_ref(Ptr, index_extensions<3>) -> array_ref::value_type, 3, Ptr>; +template array_ref(Ptr, index_extensions<4>) -> array_ref::value_type, 4, Ptr>; +template array_ref(Ptr, index_extensions<5>) -> array_ref::value_type, 5, Ptr>; + +template array_ref(It, Tuple) -> array_ref::value_type, std::tuple_size_v, It>; + +template const_subarray(It, It) -> const_subarray>; +#endif +// TODO(correaa) move to utility +template +constexpr auto rotated(T const (&array)[N]) noexcept { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + return multi::array_ref, std::rank{}, decltype(base(array))>( // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + base(array), extensions(array) + ) + .rotated(); +} + +template +constexpr auto rotated(T (&array)[N]) noexcept { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + return multi::array_ref, std::rank{}, decltype(base(array))>( // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility + base(array), extensions(array) + ) + .rotated(); +} + +template +constexpr auto operator/(RandomAccessIterator data, multi::extensions_t extensions) + -> multi::array_ptr::value_type, D, RandomAccessIterator> { + return {data, extensions}; +} + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + +template 1)>, class = decltype((void)adl_begin(*In{}), adl_end(*In{}))> +constexpr auto uninitialized_copy + // require N>1 (this is important because it forces calling placement new on the pointer + (In first, In last, multi::array_iterator dest) { // NOLINT(performance-unnecessary-value-param) TODO(correaa) inverstigate why I can't make this In const& last + while(first != last) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + adl_uninitialized_copy(adl_begin(*first), adl_end(*first), adl_begin(*dest)); + ++first; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ++dest; + } + return dest; +} + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + +// begin and end for forwarding reference are needed in this namespace +// to overwrite the behavior of std::begin and std::end +// which take rvalue-references as const-references. + +template auto begin(T&& rng) -> decltype(std::forward(rng).begin()) { return std::forward(rng).begin(); } +template auto end(T&& rng) -> decltype(std::forward(rng).end()) { return std::forward(rng).end(); } + +template +auto transposed(T (&array)[N][M]) -> decltype(auto) { return ~multi::array_ref(array); } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + +template +using array_const_view = array_ref const&; + +template +using array_view = array_ref&; + +} // end namespace boost::multi + +#ifndef BOOST_MULTI_SERIALIZATION_ARRAY_VERSION +#define BOOST_MULTI_SERIALIZATION_ARRAY_VERSION 0 // NOLINT(cppcoreguidelines-macro-usage) gives user opportunity to select serialization version //NOSONAR +// #define BOOST_MULTI_SERIALIZATION_ARRAY_VERSION 0 // save data as flat array +// #define BOOST_MULTI_SERIALIZATION_ARRAY_VERSION -1 // save data as structured nested labels array +// #define BOOST_MULTI_SERIALIZATION_ARRAY_VERSION 16 // any other value, structure for N <= 16, flat otherwise N > 16 + +namespace boost::multi { +constexpr inline int serialization_array_version = BOOST_MULTI_SERIALIZATION_ARRAY_VERSION; +} // end namespace boost::multi +#endif + +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) && !defined(_MSC_VER) +namespace std::ranges { // NOLINT(cert-dcl58-cpp) to enable borrowed, nvcc needs namespace +template +[[maybe_unused]] constexpr bool enable_borrowed_range<::boost::multi::subarray> = true; // NOLINT(misc-definitions-in-headers) + +template +[[maybe_unused]] constexpr bool enable_borrowed_range<::boost::multi::const_subarray> = true; // NOLINT(misc-definitions-in-headers) +} // end namespace std::ranges +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#undef BOOST_MULTI_HD + +#endif // BOOST_MULTI_ARRAY_REF_HPP_ diff --git a/include/boost/multi/detail/adl.hpp b/include/boost/multi/detail/adl.hpp new file mode 100644 index 000000000..61532a7ed --- /dev/null +++ b/include/boost/multi/detail/adl.hpp @@ -0,0 +1,721 @@ +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_ADL_HPP +#define BOOST_MULTI_DETAIL_ADL_HPP + +#include + +#if defined(__CUDA__) || defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + +#ifdef __NVCC__ +#pragma nv_diagnostic push +#pragma nv_diag_suppress = 20011 // deep inside Thrust: calling a __host__ function("std::vector > ::vector(const ::std::vector > &)") from a __host__ __device__ function("thrust::system::detail::generic::detail::uninitialized_copy_functor< ::std::vector > , ::std::vector > > ::operator ()< ::thrust::detail::tuple_of_iterator_references< ::std::vector > &, ::std::vector > & > > ") is not allowed +#pragma nv_diag_suppress = 20014 // deep inside Thrust: calling a __host__ function from a __host__ __device__ function is not allowed +#pragma nv_diag_suppress = 20015 // deep inside Thrust: calling a constexpr __host__ function from a __host__ __device__ function is not allowed +#endif + +#include +#include +#include +#include +#include + +#ifdef __NVCC__ +#pragma nv_diagnostic pop // nv_diagnostics pop +#endif + +#endif + +#include // for for_each, copy_n, fill, fill_n, lexicographical_compare, swap_ranges // IWYU pragma: keep // bug in iwyu 0.18 +#include // for size_t +#include // for equal_to +#include // for iterator_traits, distance, size +#include // for allocator_traits, allocator, pointer_traits +#include // for decay_t, enable_if_t, conditional_t, declval, is_pointer, true_type +#include // for forward, addressof + +#ifdef _MULTI_FORCE_TRIVIAL_STD_COMPLEX +#include +#endif + +#define BOOST_MULTI_DEFINE_ADL(FuN) /*NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) consider replacing for all ADL'd operations*/ \ +namespace boost { \ +namespace multi { \ +namespace adl { \ + namespace custom {template struct FuN##_t;} __attribute__((unused)) \ + static constexpr class FuN##_t { \ + template [[deprecated]] auto _(priority<0>, As&&... args) const = delete; \ + template auto _(priority<1>, As&&... args) const BOOST_MULTI_DECLRETURN(std::FuN(std::forward(args)...)) \ + template auto _(priority<2>, As&&... args) const BOOST_MULTI_DECLRETURN( FuN(std::forward(args)...)) \ + template auto _(priority<3>, T&& t, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(t).FuN(std::forward(args)...)) \ + template auto _(priority<4>, As&&... args) const BOOST_MULTI_DECLRETURN(custom::FuN##_t::_(std::forward(args)...)) \ + public: \ + template auto operator()(As&&... args) const-> decltype(_(priority<4>{}, std::forward(args)...)) {return _(priority<4>{}, std::forward(args)...);} \ + } (FuN); \ +} /* end namespace adl */ \ +} /* end namespace multi */ \ +} /* end namespace boost */ + +#define BOOST_MULTI_DECLRETURN(ExpR) -> decltype(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing +#define BOOST_MULTI_JUSTRETURN(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing + +namespace boost::multi { + +template struct priority : std::conditional_t> {}; + +class adl_copy_n_t { + template constexpr auto _(priority<0>/**/, As&&... args) const BOOST_MULTI_DECLRETURN(std:: copy_n( std::forward(args)...)) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN(::thrust:: copy_n( std::forward(args)...)) +#endif + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( copy_n( std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::decay_t:: copy_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).copy_n( std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_copy_n_t adl_copy_n; + +// there is no move_n (std::move_n), use copy_n(std::make_move_iterator(first), count) instead + +class adl_move_t { + template constexpr auto _(priority<0>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: move( std::forward(args)...)) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) // there is no thrust::move algorithm + template constexpr auto _(priority<1>/**/, It first, It last, As&&... args) const BOOST_MULTI_DECLRETURN( thrust::copy(std::make_move_iterator(first), std::make_move_iterator(last), std::forward(args)...)) +#endif + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( move( std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::decay_t:: move(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).move( std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_move_t adl_move; + +class adl_fill_n_t { + template< class... As> constexpr auto _(priority<0>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: fill_n (std::forward(args)...)) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + template< class... As> constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( thrust:: fill_n (std::forward(args)...)) +#endif + template< class... As> constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( fill_n (std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::decay_t:: fill_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).fill_n (std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_fill_n_t adl_fill_n; + +class adl_fill_t { + template< class... As> constexpr auto _(priority<0>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: fill (std::forward(args)...)) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + template< class... As> constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( thrust:: fill (std::forward(args)...)) +#endif + template< class... As> constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( fill (std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::decay_t:: fill(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).fill (std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_fill_t adl_fill; + +class adl_equal_t { + template< class...As> constexpr auto _(priority<1>/**/, As&&...args) const BOOST_MULTI_DECLRETURN( std:: equal( std::forward(args)...)) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + template< class...As> constexpr auto _(priority<2>/**/, As&&...args) const BOOST_MULTI_DECLRETURN( ::thrust:: equal( std::forward(args)...)) +#endif + template< class...As> constexpr auto _(priority<3>/**/, As&&...args) const BOOST_MULTI_DECLRETURN( equal( std::forward(args)...)) + template< class...As> constexpr auto _(priority<4>/**/, As&&...args) const BOOST_MULTI_DECLRETURN( equal( std::forward(args)..., std::equal_to<>{})) // WORKAROUND makes syntax compatible with boost::ranges::equal if, for some reason, it is included. + template constexpr auto _(priority<5>/**/, T&& arg, As&&...args) const BOOST_MULTI_DECLRETURN( std::decay_t:: equal(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<6>/**/, T&& arg, As&&...args) const BOOST_MULTI_DECLRETURN( std::forward(arg).equal( std::forward(args)...)) + + public: + template constexpr auto operator()(As&&...args) const BOOST_MULTI_DECLRETURN(_(priority<6>{}, std::forward(args)...)) +}; +inline constexpr adl_equal_t adl_equal; + +#ifndef _MSC_VER +template > void copy(As...) = delete; // NOLINT(modernize-use-constraints) TODO(correaa) +#endif + +class adl_copy_t { + template::reference, typename std::iterator_traits::reference>> // NOLINT(modernize-use-constraints) TODO(correaa) + > + constexpr auto _(priority<1>/**/, InputIt first, InputIt last, OutputIt d_first) const BOOST_MULTI_DECLRETURN(std::copy(first, last, d_first)) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( ::thrust::copy(std::forward(args)...)) +#endif + template< class... As> constexpr auto _(priority<3>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( copy(std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t::copy(std::forward(arg), std::forward(args)...)) +// template constexpr auto _(priority<5>/**/, As&&... args) const BOOST_MULTI_DECLRETURN(boost::multi::adl_custom_copy...>::copy(std::forward(as)...)) + template constexpr auto _(priority<6>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).copy(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN( _(priority<6>{}, std::forward(args)...) ) \ +}; +inline constexpr adl_copy_t adl_copy; + +namespace adl { + // namespace custom {template struct fill_t;} + class fill_t { + template auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: fill (std::forward(args)...)) + template auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( fill (std::forward(args)...)) + template auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).fill (std::forward(args)...)) + // template auto _(priority<4>/**/, As&&... args) const BOOST_MULTI_DECLRETURN(custom:: fill_t::_(std::forward(args)...)) + + public: + template auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<5>{}, std::forward(args)...)) + }; + inline constexpr fill_t fill; +} // end namespace adl + +namespace xtd { + +template // this one goes last!!! +constexpr auto to_address(T const& ptr) noexcept; + +template +constexpr auto me_to_address(priority<0> /**/, T const& ptr) noexcept + -> decltype(to_address(ptr.operator->())) { + return to_address(ptr.operator->()); +} + +template +constexpr auto me_to_address(priority<1> /**/, T const& ptr) noexcept + -> decltype(std::pointer_traits::to_address(ptr)) { + return std::pointer_traits::to_address(ptr); +} + +template{}, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) +constexpr auto me_to_address(priority<2>/**/, T const& ptr) noexcept -> T { + static_assert(! std::is_function_v); + return ptr; +} + +template // this one goes last!!! +constexpr auto to_address(T const& ptr) noexcept +->decltype(me_to_address(priority<2>{}/**/, ptr)) { + return me_to_address(priority<2>{} , ptr); } + +template::value_type, typename = decltype(std::addressof(*ForwardIt{})), + typename = decltype(Value()) +> +auto alloc_uninitialized_value_construct_n(Alloc& alloc, ForwardIt first, Size count) -> ForwardIt { +// ->std::decay_t::construct(alloc, std::addressof(*first), Value()), first)> + ForwardIt current = first; + try { + for (; count > 0 ; ++current, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + std::allocator_traits::construct(alloc, std::addressof(*current), Value()); // !!!!!!!!!!!!!! if you are using std::complex type consider making complex default constructible (e.g. by type traits) + } + // ::new (static_cast(std::addressof(*current))) Value(); + return current; + } catch(...) { + for(; current != first; ++first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + std::allocator_traits::destroy(alloc, std::addressof(*first)); + } + throw; + } +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + +template::value_type> +auto alloc_uninitialized_default_construct_n(Alloc& alloc, ForwardIt first, Size count) +-> std::decay_t::construct(alloc, std::addressof(*first)), first)> { + if(std::is_trivially_default_constructible_v) { + std::advance(first, count); + return first; + } + using alloc_traits = std::allocator_traits; + ForwardIt current = first; + + try { + // return std::for_each_n(first, count, [&](T& elem) { alloc_traits::construct(alloc, std::addressof(elem)); ++current; }); + // workadoung for gcc 8.3.1 in Lass + std::for_each(first, first + count, [&](T& elem) { alloc_traits::construct(alloc, std::addressof(elem)); ++current; }); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return first + count; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } catch(...) { + // LCOV_EXCL_START // TODO(correaa) add test + std::for_each(first, current, [&](T& elem) { alloc_traits::destroy(alloc, std::addressof(elem)); }); + throw; + // LCOV_EXCL_STOP + } + + + // return current; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // end namespace xtd + +template::value_type> +constexpr auto destroy_n(BidirIt first, Size count) +->std::decay_t { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + first += count; + for(; count != 0; --first, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + std::addressof(*(first-1))->~T(); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + return first; +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + +template::value_type> +constexpr auto alloc_destroy_n(Alloc& alloc, BidirIt first, Size count) +->std::decay_t { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + first += count; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + for (; count != 0; --first, --count) { // NOLINT(altera-unroll-loops,cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) consider using an algorithm + std::allocator_traits::destroy(alloc, std::addressof(*(first - 1))); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + return first; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +class adl_uninitialized_copy_t { + template // sfinae friendy std::uninitialized_copy + [[nodiscard]] constexpr auto _(priority<1> /**/, InIt first, InIt last, FwdIt d_first) const // N_O_L_I_N_T(performance-unnecessary-value-param) bug in clang-tidy + // BOOST_MULTI_DECLRETURN( std::uninitialized_copy(first, last, d_first)) + { +#if __cplusplus >= 202002L + using ValueType = typename std::iterator_traits::value_type; + if( + std::is_constant_evaluated() && (std::is_trivially_default_constructible_v || multi::force_element_trivial_default_construction) + ) { + return std::copy(std::move(first), std::move(last), std::move(d_first)); + } +#endif + return std::uninitialized_copy(std::move(first), std::move(last), std::move(d_first)); + } +#if defined(__CUDACC__) || defined(__HIPCC__) + template::value_type> + constexpr auto _(priority<2>/**/, InIt first, InIt last, FwdIt d_first) const -> decltype(::thrust::uninitialized_copy(first, last, d_first)) // doesn't work with culang 17, cuda 12 ? + { + if constexpr(std::is_trivially_default_constructible_v || multi::force_element_trivial_default_construction) { + return ::thrust::copy(first, last, d_first); + } else { + return ::thrust::uninitialized_copy(first, last, d_first); + } + } +#endif + template constexpr auto _(priority<3>/**/, TB&& first, As&&... args ) const BOOST_MULTI_DECLRETURN( uninitialized_copy( std::forward(first) , std::forward(args)...)) + template constexpr auto _(priority<4>/**/, TB&& first, TE&& last, DB&& d_first) const BOOST_MULTI_DECLRETURN(std::decay_t ::uninitialized_copy( std::forward(first) , std::forward(last), std::forward(d_first) )) + template constexpr auto _(priority<5>/**/, TB&& first, As&&... args ) const BOOST_MULTI_DECLRETURN(std::decay_t ::uninitialized_copy(std::forward(first), std::forward(args)...)) + template constexpr auto _(priority<6>/**/, TB&& first, As&&... args ) const BOOST_MULTI_DECLRETURN(std::forward(first).uninitialized_copy( std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<6>{}, std::forward(args)...)) +}; +inline constexpr adl_uninitialized_copy_t adl_uninitialized_copy; + +#ifdef __NVCC__ +#pragma nv_diagnostic push +#pragma nv_diag_suppress = implicit_return_from_non_void_function +#endif + +class adl_uninitialized_copy_n_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std::uninitialized_copy_n(std::forward(args)...)) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( uninitialized_copy_n(std::forward(args)...)) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + template< + class It, class Size, class ItFwd, + class ValueType = typename std::iterator_traits::value_type, + class = std::enable_if_t::reference> > + > + constexpr auto _(priority<3>/**/, It first, Size count, ItFwd d_first) const -> decltype(::thrust::uninitialized_copy_n(first, count, d_first)) { + if constexpr(std::is_trivially_default_constructible_v || multi::force_element_trivial_default_construction) { + return ::thrust::copy_n(first, count, d_first); + } else { + return ::thrust::uninitialized_copy_n(first, count, d_first); + } + } +#endif + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::decay_t:: uninitialized_copy_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).uninitialized_copy_n(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<5>{}, std::forward(args)...)) // TODO(correaa) this might trigger a compiler crash with g++ 7.5 because of operator&() && overloads +}; +inline constexpr adl_uninitialized_copy_n_t adl_uninitialized_copy_n; + +#ifdef __NVCC__ +#pragma nv_diagnostic pop +#endif + +class adl_uninitialized_move_n_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: uninitialized_move_n(std::forward(args)...)) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( uninitialized_move_n(std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::decay_t:: uninitialized_move_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).uninitialized_move_n(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const {return _(priority<4>{}, std::forward(args)...);} +}; +inline constexpr auto adl_uninitialized_move_n = adl_uninitialized_move_n_t{}; + +namespace xtd { + +template +constexpr auto alloc_uninitialized_copy_n(std::allocator& /*alloc*/, InputIt first, Size count, ForwardIt d_first) { + return adl_uninitialized_copy_n(first, count, d_first);} + +// template +// constexpr auto alloc_uninitialized_move_n(std::allocator& /*alloc*/, InputIt first, Size count, ForwardIt d_first) { +// return adl_uninitialized_move_n(first, count, d_first);} + +template +auto alloc_uninitialized_copy_n(Alloc& alloc, InputIt first, Size count, ForwardIt d_first) { + ForwardIt current = d_first; + try { + for(; count > 0; ++first, ++current, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + std::allocator_traits::construct(alloc, std::addressof(*current), *first); + } + return current; + } catch(...) { + for(; d_first != current; ++d_first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + std::allocator_traits::destroy(alloc, std::addressof(*d_first)); + } + throw; + } +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + +template +// [[deprecated("check")]] +auto alloc_uninitialized_move_n(Alloc& alloc, InputIt first, Size count, ForwardIt d_first) { + ForwardIt current = d_first; + try { + // NOLINTNEXTLINE(altera-unroll-loops,cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) consider using an algorithm + for(; count > 0; ++first, ++current, --count) { // mull-ignore: cxx_gt_to_ge + std::allocator_traits::construct(alloc, std::addressof(*current), std::move(*first)); + } + return current; + } catch(...) { + for(; d_first != current; ++d_first) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch,cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) consider using an algorithm + std::allocator_traits::destroy(alloc, std::addressof(*d_first)); + } + throw; + } +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template +constexpr auto alloc_uninitialized_copy(std::allocator&/*allocator*/, InputIt const& first, InputIt const& last, ForwardIt d_first) { + return adl_uninitialized_copy(first, last, d_first); +} + +template())), + class=std::enable_if_t::value_type, typename std::iterator_traits::reference>> // NOLINT(modernize-use-constraints) TODO(correaa) +> +#if __cplusplus >= 202002L +constexpr +#endif +auto alloc_uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, ForwardIt d_first) { +// ->std::decay_t // problematic in clang-11 + gcc-9 + ForwardIt current = d_first; + using alloc_traits = std::allocator_traits; + try { + std::for_each(first, last, [&](auto const& elem) { // TODO(correaa) replace by adl_for_each + alloc_traits::construct(alloc, std::addressof(*current), elem); + ++current; + }); + return current; + } catch(...) { + std::for_each(d_first, current, [&](auto const& elem) { + std::allocator_traits::destroy(alloc, std::addressof(elem)); + }); + throw; + } +} + +template +auto alloc_uninitialized_fill_n(Alloc& alloc, ForwardIt first, Size n, T const& value) +->std::decay_t::construct(alloc, std::addressof(*first), value), first)> { + ForwardIt current = first; // using std::to_address; + try { + for(; n > 0; ++current, --n) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + std::allocator_traits::construct(alloc, std::addressof(*current), value); + } + return current; + } catch(...) { + for(; first != current; ++first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + std::allocator_traits::destroy(alloc, std::addressof(*first)); + } + throw; + } +} +} // end namespace xtd + +class adl_distance_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: distance(std::forward(args)...)) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( distance(std::forward(args)...)) +// template constexpr auto _(priority<3>/**/, It1 it1, It2 it2 ) const BOOST_MULTI_DECLRETURN(it2 - it1) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t:: distance(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).distance(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<5>{}, std::forward(args)...)) +}; +inline constexpr adl_distance_t adl_distance; + +class adl_begin_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std::begin(std::forward(args)...)) +// template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( begin(std::forward(args)...)) // this is catching boost::range_iterator if Boost 1.53 is included +// #if defined(__NVCC__) // this is no thrust::begin +// template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN(::thrust:: begin( std::forward(args)...)) +// #endif + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t::begin(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).begin(std::forward(args)...)) + + public: + template [[nodiscard]] constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_begin_t adl_begin; + +class adl_end_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: end(std::forward(args)...)) + // template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( end(std::forward(args)...)) +// #if defined(__NVCC__) // there is no thrust::end +// template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN(::thrust:: end( std::forward(args)...)) +// #endif + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t:: end(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).end(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_end_t adl_end; + +class adl_size_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std::size(std::forward(args)...)) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( size(std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t::size(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).size(std::forward(args)...)) + + public: + template [[nodiscard]] constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_size_t adl_size; + +class adl_swap_ranges_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: swap_ranges(std::forward(args)...)) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( swap_ranges(std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t:: swap_ranges(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).swap_ranges(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_swap_ranges_t adl_swap_ranges; + +class adl_lexicographical_compare_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: lexicographical_compare(std::forward(args)...)) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( lexicographical_compare(std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t:: lexicographical_compare(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).lexicographical_compare(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_lexicographical_compare_t adl_lexicographical_compare; + +class adl_uninitialized_value_construct_n_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: uninitialized_value_construct_n(std::forward(args)...)) // TODO(correaa) use boost alloc_X functions? + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( uninitialized_value_construct_n(std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t::uninitialized_value_construct_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).uninitialized_value_construct_n(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const {return (_(priority<4>{}, std::forward(args)...));} +}; +inline constexpr adl_uninitialized_value_construct_n_t adl_uninitialized_value_construct_n; + +class adl_alloc_uninitialized_value_construct_n_t { + template constexpr auto _(priority<1>/**/, Alloc&& /*alloc*/, As&&... args) const BOOST_MULTI_DECLRETURN( adl_uninitialized_value_construct_n(std::forward(args)...)) // NOLINT(cppcoreguidelines-missing-std-forward) +// template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( xtd:: alloc_uninitialized_value_construct_n(std::forward(args)...)) // TODO(correaa) use boost alloc_X functions? + template constexpr auto _(priority<3>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( alloc_uninitialized_value_construct_n(std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t:: alloc_uninitialized_value_construct_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).alloc_uninitialized_value_construct_n(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const {return (_(priority<5>{}, std::forward(args)...));} +}; +inline constexpr adl_alloc_uninitialized_value_construct_n_t adl_alloc_uninitialized_value_construct_n; + +class adl_uninitialized_default_construct_n_t { + template constexpr auto _(priority<1>/**/, As&&... args) const {return std:: uninitialized_default_construct_n( std::forward(args)...);} + // #if defined(__NVCC__) + // template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( thrust::uninitialized_default_construct_n( std::forward(args)...)) + // #endif + template constexpr auto _(priority<3>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( uninitialized_default_construct_n( std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t:: uninitialized_default_construct_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).uninitialized_default_construct_n( std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const {return (_(priority<5>{}, std::forward(args)...));} +}; +inline constexpr adl_uninitialized_default_construct_n_t adl_uninitialized_default_construct_n; + +class adl_alloc_uninitialized_default_construct_n_t { + template constexpr auto _(priority<1>/**/, Alloc&&/*unused*/, As&&... args) const BOOST_MULTI_JUSTRETURN( adl_uninitialized_default_construct_n( std::forward(args)...)) // NOLINT(cppcoreguidelines-missing-std-forward) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( xtd::alloc_uninitialized_default_construct_n( std::forward(args)...)) // TODO(correaa) use boost alloc_X functions? +#if defined(__CUDACC__) || defined(__HIPCC__) +#if defined(THRUST_VERSION) && (THRUST_VERSION < 200700) // 200800) + // boost::multi::detail::what_value_t a; + template constexpr auto _(priority<3>/**/, Alloc&& alloc, It first, Size n) const BOOST_MULTI_DECLRETURN( (thrust::detail::default_construct_range(std::forward(alloc), first, n)) ) +#else + // boost::multi::detail::what_value_t b; + template constexpr auto _(priority<3>/**/, Alloc&& alloc, It first, Size n) const BOOST_MULTI_DECLRETURN( (thrust::detail::value_initialize_range(std::forward(alloc), first, n)) ) +#endif +#endif + template constexpr auto _(priority<4>/**/, As&&... args ) const BOOST_MULTI_DECLRETURN( alloc_uninitialized_default_construct_n( std::forward(args)...)) + template constexpr auto _(priority<5>/**/, T&& arg, As&&... args ) const BOOST_MULTI_DECLRETURN( std::decay_t:: alloc_uninitialized_default_construct_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<6>/**/, T&& arg, As&&... args ) const BOOST_MULTI_DECLRETURN(std::forward(arg).alloc_uninitialized_default_construct_n( std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const {return (_(priority<6>{}, std::forward(args)...));} +}; +inline constexpr adl_alloc_uninitialized_default_construct_n_t adl_alloc_uninitialized_default_construct_n; + +class adl_destroy_n_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( multi:: destroy_n (std::forward(args)...)) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( destroy_n (std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t:: destroy_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).destroy_n (std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(args)...)) +}; +inline constexpr adl_destroy_n_t adl_destroy_n; + +class adl_alloc_destroy_n_t { + template constexpr auto _(priority<1>/**/, Alloc&&/*unused*/, As&&... args) const BOOST_MULTI_DECLRETURN( adl_destroy_n (std::forward(args)...)) // NOLINT(cppcoreguidelines-missing-std-forward) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + template constexpr auto _(priority<2>/**/, Alloc& alloc, It first, Size n) const BOOST_MULTI_DECLRETURN( (thrust::detail::destroy_range(alloc, first, first + n))) +#endif + template< class... As> constexpr auto _(priority<3>/**/, As&&... args) const BOOST_MULTI_DECLRETURN(multi:: alloc_destroy_n (std::forward(args)...)) // TODO(correaa) use boost alloc_X functions? + template< class... As> constexpr auto _(priority<4>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( alloc_destroy_n (std::forward(args)...)) + template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::decay_t:: alloc_destroy_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<6>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).alloc_destroy_n (std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<6>{}, std::forward(args)...)) +}; +inline constexpr adl_alloc_destroy_n_t adl_alloc_destroy_n; + +class adl_alloc_uninitialized_copy_t { + template constexpr auto _(priority<1>/**/, Alloc&&/*ll*/, As&&... args) const BOOST_MULTI_DECLRETURN( adl_uninitialized_copy( std::forward(args)...)) // NOLINT(cppcoreguidelines-missing-std-forward) + template constexpr auto _(priority<2>/**/, Alloc&& alloc, As&&... args) const BOOST_MULTI_DECLRETURN( xtd::alloc_uninitialized_copy(std::forward(alloc), std::forward(args)...)) + template constexpr auto _(priority<3>/**/, Alloc&& alloc, As&&... args) const BOOST_MULTI_DECLRETURN( alloc_uninitialized_copy(std::forward(alloc), std::forward(args)...)) + template constexpr auto _(priority<4>/**/, Alloc&& alloc, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t::alloc_uninitialized_copy(std::forward(alloc), std::forward(args)...)) + template constexpr auto _(priority<5>/**/, Alloc&& alloc, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(alloc).alloc_uninitialized_copy( std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<5>{}, std::forward(args)...)) +}; +inline constexpr adl_alloc_uninitialized_copy_t adl_alloc_uninitialized_copy; + +class adl_alloc_uninitialized_copy_n_t { + template constexpr auto _(priority<1>/**/, Alloc&& /*alloc*/, As&&... args) const BOOST_MULTI_DECLRETURN( adl_uninitialized_copy_n(std::forward(args)...)) // NOLINT(cppcoreguidelines-missing-std-forward) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( alloc_uninitialized_copy_n(std::forward(args)...)) +// template constexpr auto _(priority<3>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( xtd::alloc_uninitialized_copy_n(std::forward(args)...)) +// #if defined(__NVCC__) +// there is no thrust alloc uninitialized copy +// #endif + template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::decay_t::alloc_uninitialized_copy_n(std::forward(arg), std::forward(args)...)) + template constexpr auto _(priority<6>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).alloc_uninitialized_copy_n(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const {return _(priority<6>{}, std::forward(args)...);} +}; +inline constexpr adl_alloc_uninitialized_copy_n_t adl_alloc_uninitialized_copy_n; + +class alloc_uninitialized_move_n_t { +// TODO(correaa) : fallback to no alloc version + template constexpr auto _(priority<1>/**/, As&&... args) const {return( xtd:: alloc_uninitialized_move_n(std::forward(args)...));} + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( alloc_uninitialized_move_n(std::forward(args)...)) + template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(std::forward(arg).alloc_uninitialized_move_n(std::forward(args)...)) + + public: + template constexpr auto operator()(As&&... args) const { return _(priority<3>{}, std::forward(args)...); } +}; +inline constexpr alloc_uninitialized_move_n_t adl_alloc_uninitialized_move_n; + +class uninitialized_fill_n_t { + template constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( std:: uninitialized_fill_n(std::forward(args)...)) + template constexpr auto _(priority<2>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( uninitialized_fill_n(std::forward(args)...)) +#if defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__) + template constexpr auto _(priority<3>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( ::thrust::uninitialized_fill_n(std::forward(args)...)) +#endif + template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const BOOST_MULTI_DECLRETURN( std::forward(arg).uninitialized_fill_n(std::forward(args)...)) + + public: + template constexpr auto operator()(T1&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(arg), std::forward(args)...)) +}; +inline constexpr uninitialized_fill_n_t adl_uninitialized_fill_n; + +class alloc_uninitialized_fill_n_t { + template< class... As> constexpr auto _(priority<1>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( xtd::alloc_uninitialized_fill_n(std::forward(args)...)) + template constexpr auto _(priority<2>/**/, Alloc&&/*alloc*/, As&&... args) const BOOST_MULTI_DECLRETURN( adl_uninitialized_fill_n(std::forward(args)...)) // NOLINT(cppcoreguidelines-missing-std-forward) + template< class... As> constexpr auto _(priority<3>/**/, As&&... args) const BOOST_MULTI_DECLRETURN( alloc_uninitialized_fill_n(std::forward(args)...)) + template constexpr auto _(priority<4>/**/, Alloc&& alloc , As&&... args) const BOOST_MULTI_DECLRETURN( std::forward(alloc).alloc_uninitialized_fill_n(std::forward(args)...)) + + public: + template constexpr auto operator()(T1&& arg, As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<4>{}, std::forward(arg), std::forward(args)...)) +}; +inline constexpr alloc_uninitialized_fill_n_t adl_alloc_uninitialized_fill_n; + +// template +// struct recursive { +// template +// static constexpr auto alloc_uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, ForwardIt dest){ +// using std::begin; using std::end; +// while(first!=last) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm +// recursive::alloc_uninitialized_copy(alloc, begin(*first), end(*first), begin(*dest)); +// ++first; +// ++dest; +// } +// return dest; +// } +// }; + +// template<> struct recursive<1> { +// template +// static auto alloc_uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, ForwardIt dest){ +// return adl_alloc_uninitialized_copy(alloc, first, last, dest); +// } +// }; + +} // end namespace boost::multi + +#undef BOOST_MULTI_DECLRETURN +#undef BOOST_MULTI_JUSTRETURN + +#endif diff --git a/include/boost/multi/detail/config/ASSERT.hpp b/include/boost/multi/detail/config/ASSERT.hpp new file mode 100644 index 000000000..8a19eb1da --- /dev/null +++ b/include/boost/multi/detail/config/ASSERT.hpp @@ -0,0 +1,28 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_CONFIG_ASSERT_HPP +#define BOOST_MULTI_DETAIL_CONFIG_ASSERT_HPP + +#include + +#if defined(BOOST_MULTI_ASSERT_DISABLE) // to activate bounds check compile in debug mode (default) with -DBOOST_MULTI_ACCESS_DEBUG + #define BOOST_MULTI_ASSERT(Expr) /*empty*/ // NOLINT(cppcoreguidelines-macro-usage +#else + #define BOOST_MULTI_ASSERT(Expr) assert(Expr) // NOLINT(cppcoreguidelines-macro-usage) +#endif + +// #if defined(BOOST_MULTI_NDEBUG) || defined(__CUDACC__) +// #define BOOST_MULTI_ASSERT(Expr) // NOLINT(cppcoreguidelines-macro-usage +// #else +// // #include +// // // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) this is for very inefficient asserts +// // #if defined(__cpp_lib_stacktrace) && (__cpp_lib_stacktrace >= 202011L) +// // #define BOOST_MULTI_ASSERT(Expr) assert((std::cerr<= 201907L) && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +# define BOOST_MULTI_NODISCARD(MsG) [[nodiscard]] // [[nodiscard(MsG)]] in c++20 empty message is not allowed with paren +# else +# define BOOST_MULTI_NODISCARD(MsG) [[nodiscard]] // NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) check if this is needed in C++17 +# endif +# elif __has_cpp_attribute(gnu::warn_unused_result) +# define BOOST_MULTI_NODISCARD(MsG) [[gnu::warn_unused_result]] +# endif + +// No discard class +# if(__has_cpp_attribute(nodiscard) && !defined(__NVCC__) && (!defined(__clang__) || (defined(__clang__) && (__cplusplus >= 202002L)))) +# if (__has_cpp_attribute(nodiscard) >= 201907L) && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +# define BOOST_MULTI_NODISCARD_CLASS(MsG) [[nodiscard_(MsG)]] +# else +# define BOOST_MULTI_NODISCARD_CLASS(MsG) [[nodiscard]] +# endif +# endif + +#endif + +#ifndef BOOST_MULTI_NODISCARD +# define BOOST_MULTI_NODISCARD(MsG) +#endif + +#ifndef BOOST_MULTI_NODISCARD_CLASS +# define BOOST_MULTI_NODISCARD_CLASS(MsG) +#endif + +// clang-format on + +#endif // BOOST_MULTI_DETAIL_CONFIG_NODISCARD_HPP diff --git a/include/boost/multi/detail/config/NO_UNIQUE_ADDRESS.hpp b/include/boost/multi/detail/config/NO_UNIQUE_ADDRESS.hpp new file mode 100644 index 000000000..778c818a6 --- /dev/null +++ b/include/boost/multi/detail/config/NO_UNIQUE_ADDRESS.hpp @@ -0,0 +1,24 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_CONFIG_NO_UNIQUE_ADDRESS_HPP +#define BOOST_MULTI_DETAIL_CONFIG_NO_UNIQUE_ADDRESS_HPP + +#ifdef __has_cpp_attribute + #if __has_cpp_attribute(no_unique_address) >= 201803L && !defined(__NVCC__) && !defined(__PGI) && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) this macro will be needed until C++20 + #define BOOST_MULTI_NO_UNIQUE_ADDRESS [[no_unique_address]] + #endif +#endif + +#ifndef BOOST_MULTI_NO_UNIQUE_ADDRESS + #ifdef _MSC_VER + #define BOOST_MULTI_NO_UNIQUE_ADDRESS // [[msvc::no_unique_address]] + #else + #define BOOST_MULTI_NO_UNIQUE_ADDRESS + #endif +#endif + +#endif // BOOST_MULTI_DETAIL_CONFIG_NO_UNIQUE_ADDRESS_HPP diff --git a/include/boost/multi/detail/config/VERSION.hpp b/include/boost/multi/detail/config/VERSION.hpp new file mode 100644 index 000000000..508fd5d36 --- /dev/null +++ b/include/boost/multi/detail/config/VERSION.hpp @@ -0,0 +1,17 @@ +// Copyright 2022-2023 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_CONFIG_VERSION_HPP_ +#define BOOST_MULTI_CONFIG_VERSION_HPP_ + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,modernize-macro-to-enum) indicate library version +#define BOOST_MULTI_VERSION_MAJOR 0 +#define BOOST_MULTI_VERSION_MINOR 81 +#define BOOST_MULTI_VERSION_MICRO 0 + +#define BOOST_MULTI_VERSION (BOOST_MULTI_VERSION_MAJOR * 10000 + BOOST_MULTI_VERSION_MINOR * 100 + BOOST_MULTI_VERSION_MICRO) + +// NOLINTEND(cppcoreguidelines-macro-usage,modernize-macro-to-enum) + +#endif // BOOST_MULTI_CONFIG_VERSION_HPP_ diff --git a/include/boost/multi/detail/extensions.hpp b/include/boost/multi/detail/extensions.hpp new file mode 100644 index 000000000..f11fcdea7 --- /dev/null +++ b/include/boost/multi/detail/extensions.hpp @@ -0,0 +1,118 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_EXTENSIONS_HPP +#define BOOST_MULTI_DETAIL_EXTENSIONS_HPP + +#include + +#if (__cplusplus >= 202002L) && !defined(_MSC_VER) && __has_include() +#include +#endif + +#include + +#ifdef __NVCC__ +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif + +namespace boost::multi::detail { + +template +class extensions; + +template<> +class extensions<> { + public: + extensions() = default; +}; + +template +class extensions : private Ex { + extensions rest_; + + public: + extensions() = default; + + BOOST_MULTI_HD explicit constexpr extensions(Ex ex, Exts... rest) : Ex{ex}, rest_{rest...} {} + + template + BOOST_MULTI_HD constexpr auto get() const { + if constexpr(I == 0) { + return static_cast(*this); + } else { + return rest_.template get(); + } + } + + using extension_type = Ex; + using sub_type = extensions; + + BOOST_MULTI_HD constexpr auto extension() const { return static_cast(*this); } + BOOST_MULTI_HD constexpr auto sub() const { return rest_; } +}; + +template extensions(Exts...) -> extensions; + +template +auto get(::boost::multi::detail::extensions const& exts) + -> decltype(exts.template get()) { + return exts.template get(); +} + +template +struct tyid { + using type = Self; +}; + +} // end namespace boost::multi::detail + +template +struct std::tuple_size<::boost::multi::detail::extensions> { // NOLINT(cert-dcl58-cpp) structured binding + static constexpr std::size_t value = sizeof...(Exts); +}; + +template +struct std::tuple_element> { // NOLINT(cert-dcl58-cpp) structured binding + using type = typename std::conditional_t< // NOLINT(modernize-type-traits) bug in clang-tidy + I == 0, + ::boost::multi::detail::tyid< + typename ::boost::multi::detail::extensions::extension_type + >, + ::std::tuple_element< + I - 1, typename ::boost::multi::detail::extensions::sub_type + > + >::type; +}; + +#if defined(__cpp_lib_format) && (__cpp_lib_format >= 202106L) && !defined(_MSC_VER) + +template +struct std::formatter<::boost::multi::detail::extensions > { // NOLINT(cert-dcl58-cpp) it's the way + constexpr auto parse(std::format_parse_context& /*ctx*/) { return /* */; } + + auto format(::boost::multi::detail::extensions const& obj, std::format_context& ctx) const { + using std::get; + if constexpr(sizeof...(Exts) == 1) { + return std::format_to(ctx.out(), "({})", get<0>(obj)); + } + if constexpr(sizeof...(Exts) == 2) { + return std::format_to(ctx.out(), "({} x {})", get<0>(obj), get<1>(obj)); + } + if constexpr(sizeof...(Exts) == 3) { + return std::format_to(ctx.out(), "({} x {})", get<0>(obj), get<1>(obj), get<2>(obj)); + } + if constexpr(sizeof...(Exts) == 4) { + return std::format_to(ctx.out(), "({} x {} x {} x {})", get<0>(obj), get<1>(obj), get<2>(obj), get<3>(obj)); + } + } +}; + +#endif + +#undef BOOST_MULTI_HD + +#endif // BOOST_MULTI_DETAIL_EXTENSIONS_HPP diff --git a/include/boost/multi/detail/implicit_cast.hpp b/include/boost/multi/detail/implicit_cast.hpp new file mode 100644 index 000000000..9e683e47c --- /dev/null +++ b/include/boost/multi/detail/implicit_cast.hpp @@ -0,0 +1,23 @@ +// Copyright 2023-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_IMPLICIT_CAST_HPP +#define BOOST_MULTI_DETAIL_IMPLICIT_CAST_HPP + +#include +#include + +namespace boost::multi::detail { // this library requires C++17 and above !!! + +template constexpr bool is_implicitly_convertible_v = std::is_convertible_v; // this library needs C++17 or higher (e.g. -std=c++17) +template constexpr bool is_explicitly_convertible_v = std::is_constructible_v; + +template, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) +constexpr auto implicit_cast(From&& ref) -> To {return static_cast(std::forward(ref));} + +template && ! std::is_convertible_v, int> =0> // NOLINT(modernize-use-constraints) TODO(correaa) +constexpr auto explicit_cast(From&& ref) -> To {return static_cast(std::forward(ref));} + +} // end namespace boost::multi::detail +#endif diff --git a/include/boost/multi/detail/index_range.hpp b/include/boost/multi/detail/index_range.hpp new file mode 100644 index 000000000..c805cfb79 --- /dev/null +++ b/include/boost/multi/detail/index_range.hpp @@ -0,0 +1,443 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_INDEX_RANGE_HPP +#define BOOST_MULTI_DETAIL_INDEX_RANGE_HPP + +#include +#include +#include +#include + +#include + +#include // for min, max +#include +#include // for ptrdiff_t +#include // for minus, plus +#include // for reverse_iterator, random_access_iterator_tag +#include // for numeric_limits +#include // for pointer_traits +#include // for declval, true_type, decay_t, enable_if_t +#include // for forward + +#ifdef __NVCC__ +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif + +namespace boost::multi { + +using boost::multi::detail::tuple; + +template< + class Self, + class ValueType, class AccessCategory, + class Reference = ValueType&, class DifferenceType = typename std::pointer_traits::difference_type, class Pointer = ValueType*> +class iterator_facade { + protected: + iterator_facade() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + private: + using self_type = Self; + [[nodiscard]] constexpr auto self_() & { return static_cast(*this); } + [[nodiscard]] constexpr auto self_() const& { return static_cast(*this); } + + public: + using value_type = ValueType; + using reference = Reference; + using pointer = Pointer; // NOSONAR(cpp:S5008) false positive + using difference_type = DifferenceType; + using iterator_category = AccessCategory; + + // friend constexpr auto operator!=(self_type const& self, self_type const& other) { return !(self == other); } + + friend constexpr auto operator<=(self_type const& self, self_type const& other) { return (self < other) || (self == other); } + friend constexpr auto operator>(self_type const& self, self_type const& other) { return !(self <= other); } + friend constexpr auto operator>=(self_type const& self, self_type const& other) { return !(self < other); } + + constexpr auto operator-(difference_type n) const { return self_type{self_()} -= n; } + constexpr auto operator+(difference_type n) const { return self_type{self_()} += n; } + + template // nvcc workaround + friend constexpr auto operator+(difference_type n, self_type const& self) { return self + n; } + + friend constexpr auto operator++(self_type& self, int) -> self_type { + self_type ret = self; + ++self; + return ret; + } + friend constexpr auto operator--(self_type& self, int) -> self_type { + self_type ret = self; + --self; + return ret; + } + + constexpr auto operator[](difference_type n) const { return *(self_() + n); } +}; + +template, class Minus = std::minus<>> +class range { + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4820) // 'boost::multi::range,std::minus>': '3' bytes padding added after data member 'boost::multi::range,std::minus>::first_' + #endif + + BOOST_MULTI_NO_UNIQUE_ADDRESS + IndexType first_; // = {}; + + #ifdef _MSC_VER + #pragma warning(pop) + #endif + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wpadded" + #endif + + IndexTypeLast last_; // = first_; // TODO(correaa) check how to do partially initialzed + + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + + public: + template // , class ArT = multi::archive_traits> + void serialize(Archive& arxiv, unsigned /*version*/) { + arxiv & multi::archive_traits::make_nvp("first", first_); + // arxiv & BOOST_SERIALIZATION_NVP( first_); + // arxiv & cereal:: make_nvp("first", first_); + // arxiv & CEREAL_NVP( first_); + // arxiv & first_ ; + + arxiv & multi::archive_traits::make_nvp("last", last_); + // arxiv & BOOST_SERIALIZATION_NVP( last_ ); + // arxiv & cereal:: make_nvp("last" , last_ ); + // arxiv & CEREAL_NVP( last_ ); + // arxiv & last_ ; + } + + using value_type = decltype(IndexTypeLast{} + IndexType{}); + using difference_type = decltype(IndexTypeLast{} - IndexType{}); // std::make_signed_t; + using size_type = difference_type; + using const_reference = value_type; + using reference = const_reference; + using const_pointer = value_type; + using pointer = value_type; + + range() = default; // cppcheck-suppress uninitMemberVar ; + + // range(range const&) = default; + + template>, int> = 0, // NOLINT(modernize-type-traits) for C++20 + decltype(detail::implicit_cast(std::declval().first()), + detail::implicit_cast(std::declval().last()) + )* = nullptr> + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(runtime/explicit) + constexpr /*implicit*/ range(Range&& other) // NOLINT(bugprone-forwarding-reference-overload,google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR(cpp:S1709) ranges are implicitly convertible if elements are implicitly convertible + : first_{std::forward(other).first()}, last_{std::forward(other).last()} {} // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + + template< + class Range, + std::enable_if_t>, unsigned> = 0, + decltype(detail::explicit_cast(std::declval().first()), + detail::explicit_cast(std::declval().last()) + )* = nullptr> + constexpr explicit range(Range&& other) // NOLINT(bugprone-forwarding-reference-overload) + : first_{std::forward(other).first()}, last_{std::forward(other).last()} {} + + BOOST_MULTI_HD constexpr range(IndexType first, IndexTypeLast last) : first_{first}, last_{last} {} + + // TODO(correaa) make this iterator SCARY + class const_iterator : public boost::multi::iterator_facade { + typename const_iterator::value_type curr_; + constexpr explicit const_iterator(value_type current) : curr_{current} {} + friend class range; + + public: + template using rebind = typename range>::const_iterator; + using pointer = const_iterator; + using element_type = IndexTypeLast; + + const_iterator() = default; + + template() = *OtherConstIterator{})> + // cppcheck-suppress noExplicitConstructor ; see below + const_iterator(OtherConstIterator const& other) : curr_{*other} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + + BOOST_MULTI_HD constexpr auto operator==(const_iterator const& other) const -> bool { return curr_ == other.curr_; } + BOOST_MULTI_HD constexpr auto operator!=(const_iterator const& other) const -> bool { return curr_ != other.curr_; } + + BOOST_MULTI_HD constexpr auto operator<(const_iterator const& other) const -> bool { return curr_ < other.curr_; } // mull-ignore: cxx_lt_to_le + + constexpr auto operator++() -> const_iterator& { + ++curr_; + return *this; + } + constexpr auto operator--() noexcept(noexcept(--curr_)) -> const_iterator& { + --curr_; + return *this; + } + + constexpr auto operator-=(typename const_iterator::difference_type n) -> const_iterator& { + curr_ -= n; + return *this; + } + constexpr auto operator+=(typename const_iterator::difference_type n) -> const_iterator& { + curr_ += n; + return *this; + } + + constexpr auto operator-(typename const_iterator::difference_type n) const -> const_iterator { + return const_iterator{*this} -= n; + } + + constexpr auto operator+(typename const_iterator::difference_type n) const -> const_iterator { + return const_iterator{*this} += n; + } + + constexpr auto operator-(const_iterator const& other) const { return curr_ - other.curr_; } + constexpr auto operator*() const noexcept -> typename const_iterator::reference { return curr_; } + }; + + using iterator = const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + [[nodiscard]] BOOST_MULTI_HD constexpr auto first() const { return first_; } + [[nodiscard]] BOOST_MULTI_HD constexpr auto last() const { return last_; } + + constexpr auto operator[](difference_type n) const -> const_reference { return first() + n; } + + [[nodiscard]] BOOST_MULTI_HD constexpr auto front() const -> value_type { return first(); } + [[nodiscard]] BOOST_MULTI_HD constexpr auto back() const -> value_type { return last() - 1; } + + [[nodiscard]] constexpr auto cbegin() const { return const_iterator{first_}; } + [[nodiscard]] constexpr auto cend() const { return const_iterator{last_}; } + + [[nodiscard]] constexpr auto rbegin() const { return reverse_iterator{end()}; } + [[nodiscard]] constexpr auto rend() const { return reverse_iterator{begin()}; } + + [[nodiscard]] constexpr auto begin() const -> const_iterator { return cbegin(); } + [[nodiscard]] constexpr auto end() const -> const_iterator { return cend(); } + + BOOST_MULTI_HD constexpr auto is_empty() const& noexcept { return first_ == last_; } + + [[nodiscard]] BOOST_MULTI_HD constexpr auto empty() const& noexcept { return is_empty(); } + + #ifdef __NVCC__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = 20013 // calling a constexpr __host__ function("operator std::streamoff") from a __host__ __device__ function("size") is not allowed. // TODO(correaa) implement HD integral_constant + #endif + + BOOST_MULTI_HD constexpr auto size() const& noexcept -> size_type { return last_ - first_; } + + #ifdef __NVCC__ + #pragma nv_diagnostic pop + #endif + + friend BOOST_MULTI_HD constexpr auto operator==(range const& self, range const& other) { + return (self.empty() && other.empty()) || (self.first_ == other.first_ && self.last_ == other.last_); + } + friend BOOST_MULTI_HD constexpr auto operator!=(range const& self, range const& other) { return !(self == other); } + + [[nodiscard]] // ("find returns an iterator to the sequence, that is the only effect")]] for C++20 + constexpr auto find(value_type const& value) const -> const_iterator { + if(value >= last_ || value < first_) { + return end(); + } + return begin() + (value - front()); + } + template [[nodiscard]] BOOST_MULTI_HD constexpr auto contains(Value const& value) const -> bool { return (first_ <= value) && (value < last_); } + template [[nodiscard]] BOOST_MULTI_HD constexpr auto count(Value const& value) const -> size_type { return contains(value); } + + friend constexpr auto intersection(range const& self, range const& other) { + using std::max; + using std::min; + auto new_first = max(self.first(), other.first()); + auto new_last = min(self.last(), other.last()); + new_first = min(new_first, new_last); + return range(new_first, new_last); + } +}; + +#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703) +template // , class Plus = std::plus<>, class Minus = std::minus<> > +range(IndexType, IndexTypeLast) -> range; // #3 +#endif + +template +constexpr auto make_range(IndexType first, IndexTypeLast last) -> range { + return {first, last}; +} + +template +class intersecting_range { + range impl_; + + constexpr intersecting_range() noexcept : // MSVC 19.07 needs constexpr to initialize ALL later + impl_{ + (std::numeric_limits::min)(), // parent needed for MSVC min/max macros + (std::numeric_limits::max)() + } + {} + + static constexpr auto make_(IndexType first, IndexType last) -> intersecting_range { + intersecting_range ret; + ret.impl_ = range{first, last}; + return ret; + } + friend constexpr auto intersection(intersecting_range const& self, range const& other) { + return intersection(self.impl_, other); + } + friend constexpr auto intersection(range const& other, intersecting_range const& self) { + return intersection(other, self.impl_); + } + friend constexpr auto operator<(intersecting_range const& self, IndexType end) { + return intersecting_range::make_(self.impl_.first(), end); + } + friend constexpr auto operator<=(IndexType first, intersecting_range const& self) { + return intersecting_range::make_(first, self.impl_.last()); + } + + public: + constexpr auto operator*() const& -> intersecting_range const& { return *this; } + static constexpr auto all() noexcept { return intersecting_range{}; } +}; + +[[maybe_unused]] constexpr intersecting_range<> ALL = intersecting_range<>::all(); +[[maybe_unused]] constexpr intersecting_range<> _ = ALL; // NOLINT(readability-identifier-length) +[[maybe_unused]] constexpr intersecting_range<> U = ALL; // NOLINT(readability-identifier-length) +[[maybe_unused]] constexpr intersecting_range<> ooo = ALL; + +[[maybe_unused]] constexpr intersecting_range<> V = U; // NOLINT(readability-identifier-length) +[[maybe_unused]] constexpr intersecting_range<> A = V; // NOLINT(readability-identifier-length) + +#if !defined(__clang__) && !defined(CPPCHECK) +// cppcheck-suppress preprocessorErrorDirective ; // unicode +// [[maybe_unused]] constexpr intersecting_range<> ∀ = V; // not valid in clang or g++-15 +// [[maybe_unused]] constexpr intersecting_range<> https://www.compart.com/en/unicode/U+2200 = V; +// [[maybe_unused]] constexpr intersecting_range<> ┄ = ALL; // not valid in g++-15 +#endif + +template() + IndexType{1})> +struct extension_t : public range { + using range::range; + + BOOST_MULTI_HD constexpr extension_t(IndexType first, IndexTypeLast last) noexcept + : range{first, last} {} + +// BOOST_MULTI_HD constexpr extension_t(extension_t::size_type size) : extensions_t(IndexType{}, IndexType{} + size) {} + + // cppcheck-suppress noExplicitConstructor ; because syntax convenience // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extension_t(IndexTypeLast last) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR(cpp:S1709) allow terse syntax + : range(IndexType{}, IndexType{} + last) {} + + template< + class OtherExtension, + decltype( + detail::implicit_cast(std::declval().first()), + detail::implicit_cast(std::declval().last()) + )* = nullptr + > + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extension_t(OtherExtension const& other) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : extension_t{other.first(), other.last()} {} + + // template< + // class OtherExtension, + // decltype( + // detail::explicit_cast(std::declval().first()), + // detail::explicit_cast(std::declval().last()) + // )* = nullptr + // > + // BOOST_MULTI_HD constexpr explicit extension_t(OtherExtension const& other) noexcept + // : extension_t{other.first(), other.last()} {} + + template + BOOST_MULTI_HD constexpr auto operator=(OtherExtension const& other) -> extension_t& { + (*this) = extension_t{other}; + return *this; + } + + // BOOST_MULTI_HD constexpr extension_t() noexcept : range() {} + constexpr extension_t() = default; + + // friend constexpr auto size(extension_t const& self) -> typename extension_t::size_type { return self.size(); } + + friend constexpr auto intersection(extension_t const& ex1, extension_t const& ex2) -> extension_t { + using std::max; + using std::min; + + auto first = max(ex1.first(), ex2.first()); + auto const last = min(ex1.last(), ex2.last()); + + first = min(first, last); + + return extension_t{first, last}; + } +}; + +#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703) +template +extension_t(IndexType, IndexTypeLast) -> extension_t; + +template +extension_t(IndexType) -> extension_t, IndexType>; +#endif + +template() + 1)> +constexpr auto make_extension_t(IndexType first, IndexTypeLast last) { + return extension_t{first, last}; +} + +template +constexpr auto make_extension_t(IndexType last) { return make_extension_t(std::integral_constant{}, last); } + +using index_range = range; +using index_extension = extension_t; +using iextension = index_extension; +using irange = index_range; + +namespace detail { + +template +struct append_to_type_seq {}; + +template class TT> +struct append_to_type_seq> { + using type = TT; +}; + +template class TT> +struct repeat { + using type = typename append_to_type_seq< + T, + typename repeat::type>::type; +}; + +template class TT> +struct repeat { + using type = TT<>; +}; + +} // end namespace detail + +template using index_extensions = typename detail::repeat::type; + +template +constexpr auto contains(index_extensions const& iex, Tuple const& tup) { + // using detail::head; + // using detail::tail; + return contains(head(iex), head(tup)) && contains(tail(iex), tail(tup)); +} + +} // end namespace boost::multi + +#undef BOOST_MULTI_HD + +#endif // BOOST_MULTI_DETAIL_INDEX_RANGE_HPP diff --git a/include/boost/multi/detail/is_trivial.hpp b/include/boost/multi/detail/is_trivial.hpp new file mode 100644 index 000000000..041e95270 --- /dev/null +++ b/include/boost/multi/detail/is_trivial.hpp @@ -0,0 +1,20 @@ +// Copyright 2022-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_IS_TRIVIAL_HPP +#define BOOST_MULTI_DETAIL_IS_TRIVIAL_HPP + +#include + +namespace boost { // NOLINT(modernize-concat-nested-namespaces) +namespace multi { + +// template struct is_trivially_default_constructible : std::is_trivially_default_constructible {}; +// template struct is_trivial : std::is_trivial {}; + + +} // end namespace multi +} // end namespace boost + +#endif // BOOST_MULTI_DETAIL_IS_TRIVIAL_HPP diff --git a/include/boost/multi/detail/layout.hpp b/include/boost/multi/detail/layout.hpp new file mode 100644 index 000000000..66a7354a6 --- /dev/null +++ b/include/boost/multi/detail/layout.hpp @@ -0,0 +1,2108 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_LAYOUT_HPP +#define BOOST_MULTI_DETAIL_LAYOUT_HPP + +#include + +#include +#include + +#include // IWYU pragma: export // for index_extension, extension_t, tuple, intersection, range, operator!=, operator== +#include // IWYU pragma: export // for equality_comparable +#include // IWYU pragma: export // for archive_traits +#include // IWYU pragma: export // for get, tuple, tuple_prepend, tail, tuple_prepend_t, ht_tuple +#include // IWYU pragma: export // for dimensionality_type, index, size_type, difference_type, size_t + +#include // for max +#include // for array +#include // for assert +#include // for size_t, ptrdiff_t, __GLIBCXX__ +#include // for abs +#include // for initializer_list +#include +#include +#include // for swap +#include // for tuple_element, tuple, tuple_size, tie, make_index_sequence, index_sequence +#include // for enable_if_t, integral_constant, decay_t, declval, make_signed_t, common_type_t +#include // for forward + +#if defined(__cplusplus) && (__cplusplus >= 202002L) && __has_include() +#if !defined(__clang_major__) || !(__clang_major__ == 16) +#include // IWYU pragma: keep +#endif +#endif + +// clang-format off +namespace boost::multi { template struct layout_t; } +namespace boost::multi::detail { template class tuple; } +// clang-format on + +#ifdef __NVCC__ +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4514) // inline function removed, in MSVC C++17 mode +#pragma warning(disable : 5045) // Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified +#endif + +namespace boost::multi { + +template +struct stride_traits; + +template<> +struct stride_traits { + using category = std::random_access_iterator_tag; +}; + +template +struct stride_traits { + using category = typename Stride::category; +}; + +template +struct stride_traits> { +#if defined(__cplusplus) && (__cplusplus >= 202002L) && (!defined(__clang__) || __clang_major__ != 10) + using category = std::contiguous_iterator_tag; +#else + using category = std::random_access_iterator_tag; +#endif +}; + +namespace detail { + +template +constexpr auto tuple_tail_impl(Tuple&& tup, std::index_sequence /*012*/) { + (void)tup; // workaround bug warning in nvcc + using boost::multi::detail::get; + return boost::multi::detail::tuple{std::forward(std::forward(tup)))>(get(std::forward(tup)))...}; +} + +template +constexpr auto tuple_tail(Tuple&& t) // NOLINT(readability-identifier-length) std naming + -> decltype(tuple_tail_impl(std::forward(t), std::make_index_sequence> - 1U>())) { + return tuple_tail_impl(std::forward(t), std::make_index_sequence> - 1U>()); +} + +} // end namespace detail + +// template struct layout_t; + +template +struct extensions_t; + +template > struct array; + +template +class f_extensions_t { + extensions_t xs_; + Proj proj_; + + template + static BOOST_MULTI_HD constexpr auto std_apply_(Fun&& fun, Tup&& tup) -> decltype(auto) { + using std::apply; + return apply(std::forward(fun), std::forward(tup)); + } + + public: + static constexpr dimensionality_type dimensionality = D; + constexpr static dimensionality_type rank_v = D; + + using difference_type = typename extensions_t::difference_type; + + BOOST_MULTI_HD constexpr f_extensions_t(extensions_t xs, Proj proj) : xs_{xs}, proj_{std::move(proj)} {} + + using element = decltype(std_apply_(std::declval(), std::declval::element>())); + + using value_type = std::conditional_t< + (D == 1), + element, + array + >; + + struct bind_front_t { + multi::index idx_; + Proj proj_; + template + constexpr auto operator()(Args&&... rest) const noexcept { return proj_(idx_, std::forward(rest)...); } + }; + + constexpr auto operator[](index idx) const { + // assert( extension().contains(idx) ); + if constexpr(D != 1) { + // auto ll = [idx, proj = proj_](auto... rest) { return proj(idx, rest...); }; + // return f_extensions_t(extensions_t(xs_.base().tail()), ll); + return [idx, proj = proj_](auto... rest) noexcept { return proj(idx, rest...); } ^ extensions_t(xs_.base().tail()); + } else { + return proj_(idx); + } + } + + #if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + template + constexpr auto operator[](index idx, Indices... rest) const { + return operator[](idx)[rest...]; + } + #endif + + constexpr auto operator+() const { return multi::array{*this}; } + + struct bind_transposed_t { + Proj proj_; + template + constexpr auto operator()(T1 ii, T2 jj, Ts... rest) const noexcept -> element { return proj_(jj, ii, rest...); } + }; + + auto transposed() const -> f_extensions_t { + return bind_transposed_t{proj_} ^ layout_t(extensions()).transpose().extensions(); + // return [proj = proj_](auto i, auto j, auto... rest) { return proj(j, i, rest...); } ^ layout_t(extensions()).transpose().extensions(); + } + + struct bind_partitioned_t { + Proj proj_; + size_type nn_; + template + constexpr auto operator()(T1 ii, T2 jj, Ts... rest) const noexcept -> element { return proj_((ii * nn_) + jj, rest...); } + }; + + constexpr auto partitioned(size_type nn) const noexcept -> f_extensions_t { + return bind_partitioned_t{proj_, size()/nn} ^ layout_t(extensions()).partition(nn).extensions(); + } + + struct bind_reversed_t { + Proj proj_; + size_type size_m1; + template + constexpr auto operator()(T1 ii, Ts... rest) const noexcept -> element { return proj_(size_m1 - ii, rest...); } + }; + + constexpr auto reversed() const { return bind_reversed_t{proj_, size() - 1} ^ extensions(); } + + struct bind_rotated_t { + Proj proj_; + size_type size_; + template + constexpr auto operator()(T1 ii, Ts... rest) const noexcept { return proj_(rest..., ii); } + }; + + constexpr auto rotated() const { return bind_rotated_t{proj_, size()} ^ extensions(); } + + template + struct bind_element_transformed_t { + Proj proj_; + Proj2 proj2_; + template + constexpr auto operator()(Ts... rest) const noexcept -> element { return proj2_(proj_(rest...)); } + }; + + template + auto element_transformed(Proj2 proj2) const -> f_extensions_t > { + return bind_element_transformed_t{proj_, proj2} ^ extensions(); + } + + class iterator { + typename extensions_t::iterator it_; + Proj proj_; + + iterator(typename extensions_t::iterator it, Proj proj) : it_{it}, proj_{std::move(proj)} {} + + friend f_extensions_t; + + template + static constexpr auto apply_(Fun&& fun, Args&&... args) { + using std::apply; + return apply(std::forward(fun), std::forward(args)...); + } + + struct bind_front_t { + multi::index idx_; + Proj proj_; + template + constexpr auto operator()(Args&&... rest) const noexcept { return proj_(idx_, std::forward(rest)...); } + }; + + public: + constexpr iterator() {} // = default; // NOLINT(hicpp-use-equals-default,modernize-use-equals-default) TODO(correaa) investigate workaround + + // iterator(iterator const& other) = default; + constexpr iterator(iterator const& other) noexcept : it_{other.it_}, proj_{other.proj_} {} // NOLINT(hicpp-use-equals-default,modernize-use-equals-default) TODO(correaa) investigate workaround + iterator(iterator&&) = default; + + auto operator=(iterator&&) -> iterator& = default; + auto operator=(iterator const& other) -> iterator& { + if(this == &other) { return *this; } + // assert(proj_ == other.proj_); + it_ = other.it_; + return *this; + } + + ~iterator() = default; + + using value_type = std::conditional_t<(D != 1), + f_extensions_t, + decltype(apply_(std::declval(), std::declval::element>())) // (std::declval())) + >; + + using iterator_category = std::random_access_iterator_tag; + + constexpr auto operator++() -> auto& { ++it_; return *this; } + constexpr auto operator--() -> auto& { --it_; return *this; } + + constexpr auto operator+=(difference_type dd) -> auto& { it_+=dd; return *this; } + constexpr auto operator-=(difference_type dd) -> auto& { it_-=dd; return *this; } + + constexpr auto operator++(int) -> iterator { iterator ret{*this}; ++(*this); return ret; } + constexpr auto operator--(int) -> iterator { iterator ret{*this}; --(*this); return ret; } + + friend constexpr auto operator-(iterator const& self, iterator const& other) { return self.it_ - other.it_; } + friend constexpr auto operator+(iterator const& self, difference_type n) { iterator ret{self}; return ret += n; } + friend constexpr auto operator-(iterator const& self, difference_type n) { iterator ret{self}; return ret -= n; } + + friend constexpr auto operator+(difference_type n, iterator const& self) { return self + n; } + + friend constexpr auto operator==(iterator const& self, iterator const& other) noexcept -> bool { return self.it_ == other.it_; } + friend constexpr auto operator!=(iterator const& self, iterator const& other) noexcept -> bool { return self.it_ != other.it_; } + + friend auto operator<=(iterator const& self, iterator const& other) noexcept -> bool { return self.it_ <= other.it_; } + friend auto operator< (iterator const& self, iterator const& other) noexcept -> bool { return self.it_ < other.it_; } + friend auto operator> (iterator const& self, iterator const& other) noexcept -> bool { return self.it_ > other.it_; } + friend auto operator>=(iterator const& self, iterator const& other) noexcept -> bool { return self.it_ > other.it_; } + + constexpr auto operator*() const -> decltype(auto) { + if constexpr(D != 1) { + using std::get; + // auto ll = [idx = get<0>(*it_), proj = proj_](auto... rest) { return proj(idx, rest...); }; + return f_extensions_t(extensions_t((*it_).tail()), bind_front_t{get<0>(*it_), proj_}); + } else { + using std::get; + return proj_(get<0>(*it_)); + } + } + + auto operator[](difference_type dd) const { return *((*this) + dd); } // TODO(correaa) use ra_iterator_facade + }; + + constexpr auto begin() const { return iterator{xs_.begin(), proj_}; } + constexpr auto end() const { return iterator{xs_.end(), proj_}; } + + constexpr auto size() const { return xs_.size(); } + constexpr auto sizes() const { return xs_.sizes(); } + + constexpr auto extension() const { return xs_.extension(); } + constexpr auto extensions() const { return xs_; } + + constexpr auto front() const { return *begin(); } + constexpr auto back() const { return *(begin() + (size() - 1)); } + + class elements_t { + typename extensions_t::elements_t elems_; + Proj proj_; + + elements_t(typename extensions_t::elements_t elems, Proj proj) : elems_{elems}, proj_{std::move(proj)} {} + friend class f_extensions_t; + + public: + auto operator[](index idx) const -> decltype(auto) { return std::apply(proj_, elems_[idx]); } + + using difference_type = f_extensions_t::difference_type; + + class iterator : ra_iterable { + typename extensions_t::elements_t::iterator it_; + BOOST_MULTI_NO_UNIQUE_ADDRESS Proj proj_; + + public: + iterator(typename extensions_t::elements_t::iterator it, Proj proj) : it_{it}, proj_{std::move(proj)} {} + + auto operator++() -> auto& { ++it_; return *this; } + auto operator--() -> auto& { --it_; return *this; } + + constexpr auto operator+=(difference_type dd) -> auto& { it_+=dd; return *this; } + constexpr auto operator-=(difference_type dd) -> auto& { it_-=dd; return *this; } + + friend constexpr auto operator-(iterator const& self, iterator const& other) { return self.it_ - other.it_; } + + constexpr auto operator*() const -> decltype(auto) { using std::apply; return apply(proj_, *it_); } + + using difference_type = elements_t::difference_type; + using value_type = difference_type; + using pointer = void; + using reference = value_type; + using iterator_category = std::random_access_iterator_tag; + + friend auto operator==(iterator const& self, iterator const& other) -> bool { return self.it_ == other.it_; } + friend auto operator!=(iterator const& self, iterator const& other) -> bool { return self.it_ != other.it_; } + + friend auto operator<=(iterator const& self, iterator const& other) -> bool { return self.it_ <= other.it_; } + friend auto operator< (iterator const& self, iterator const& other) -> bool { return self.it_ < other.it_; } + + + constexpr auto operator[](difference_type dd) const { return *((*this) + dd); } // TODO(correaa) use ra_iterator_facade + }; + + auto begin() const { return iterator{elems_.begin(), proj_}; } + auto end() const { return iterator{elems_.end() , proj_}; } + + auto size() const { return elems_.size(); } + }; + + constexpr auto elements() const { return elements_t{xs_.elements(), proj_}; } + constexpr auto num_elements() const { return xs_.num_elements(); } +}; + +template +struct extensions_t : boost::multi::detail::tuple_prepend_t::base_> { + using base_ = boost::multi::detail::tuple_prepend_t::base_>; + + public: + static constexpr dimensionality_type dimensionality = D; + constexpr static dimensionality_type rank_v = D; + + using difference_type = index_extension::difference_type; + using nelems_type = multi::index; + + using element = boost::multi::detail::tuple_prepend_t::element>; + + extensions_t() = default; + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(multi::size_t size) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : allow terse syntax + : extensions_t{index_extension{size}} {} + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(index_extension ext1) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{ext1} {} + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + BOOST_MULTI_HD constexpr extensions_t(index_extension ext1, index_extension ext2) + : base_{ext1, ext2} {} + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + BOOST_MULTI_HD constexpr extensions_t(index_extension ext1, index_extension ext2, index_extension ext3) + : base_{ext1, ext2, ext3} {} + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + BOOST_MULTI_HD constexpr extensions_t(index_extension ext1, index_extension ext2, index_extension ext3, index_extension ext4) noexcept + : base_{ext1, ext2, ext3, ext4} {} + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + BOOST_MULTI_HD constexpr extensions_t(index_extension ext1, index_extension ext2, index_extension ext3, index_extension ext4, index_extension ext5) + : base_{ext1, ext2, ext3, ext4, ext5} {} + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + BOOST_MULTI_HD constexpr extensions_t(index_extension ext1, index_extension ext2, index_extension ext3, index_extension ext4, index_extension ext5, index_extension ext6) + : base_{ext1, ext2, ext3, ext4, ext5, ext6} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(detail::tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : base_{std::move(extensions)} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(::std::tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{std::move(extensions)} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(detail::tuple const& extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{extensions} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(::std::tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{std::move(extensions)} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{std::move(extensions)} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(::std::tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{std::move(extensions)} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{std::move(extensions)} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(::std::tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{std::move(extensions)} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax + : base_{std::move(extensions)} {} + + template{}}), std::enable_if_t = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(::std::tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : base_{std::move(extensions)} {} + + template + BOOST_MULTI_HD constexpr explicit extensions_t(tuple const& tup) + : extensions_t(tup, std::make_index_sequence(D)>()) {} + + // template static constexpr auto get_(TU const& tu) { using std::get; return get(tu); } + + template(OtherExtensions{}.extension()) )* = nullptr, + decltype( multi::detail::implicit_cast::extensions_type>(OtherExtensions{}.sub()) )* = nullptr + > + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(OtherExtensions const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : extensions_t(other.extension(), other.sub()) {} + + BOOST_MULTI_HD constexpr extensions_t(index_extension const& extension, typename layout_t::extensions_type const& other) + : extensions_t(multi::detail::ht_tuple(extension, other.base())) {} + + BOOST_MULTI_HD constexpr auto base() const& -> base_ const& { return *this; } + BOOST_MULTI_HD constexpr auto base() & -> base_& { return *this; } + + friend constexpr auto operator*(index_extension const& extension, extensions_t const& self) -> extensions_t { + // return extensions_t(tuple(extension, self.base())); + return extensions_t(extension, self); + } + + friend BOOST_MULTI_HD auto operator==(extensions_t const& self, extensions_t const& other) { return self.base() == other.base(); } + friend BOOST_MULTI_HD auto operator!=(extensions_t const& self, extensions_t const& other) { return self.base() != other.base(); } + + using index = multi::index; + using indices_type = multi::detail::tuple_prepend_t::indices_type>; + + template + friend BOOST_MULTI_HD constexpr auto operator^(Func fun, extensions_t const& xs) { + return f_extensions_t(xs, std::move(fun)); + } + template + friend constexpr auto operator->*(extensions_t const& xs, Func fun) { + return f_extensions_t(xs, std::move(fun)); + } + + BOOST_MULTI_HD constexpr auto sub() const { + return extensions_t{static_cast(*this).tail()}; + } + + [[nodiscard]] + BOOST_MULTI_HD constexpr auto from_linear(nelems_type const& n) const -> indices_type { + auto const sub_num_elements = sub().num_elements(); + #if !(defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)) + assert(sub_num_elements != 0); // clang hip doesn't allow assert in host device functions + #endif + return multi::detail::ht_tuple(n / sub_num_elements, sub().from_linear(n % sub_num_elements)); + } + + friend constexpr auto operator%(nelems_type idx, extensions_t const& extensions) { return extensions.from_linear(idx); } + + constexpr explicit operator bool() const { return !layout_t{*this}.empty(); } + + template + BOOST_MULTI_HD constexpr auto to_linear(index const& idx, Indices const&... rest) const { + auto const sub_extensions = extensions_t{this->base().tail()}; + return (idx * sub_extensions.num_elements()) + sub_extensions.to_linear(rest...); + } + + template + BOOST_MULTI_HD constexpr auto operator()(index idx, Indices... rest) const { return to_linear(idx, rest...); } + + class iterator { // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init) constructor does not initialize these fields: idx_ + index idx_; + extensions_t rest_; + friend extensions_t; + + constexpr iterator(index idx, extensions_t rest) : idx_{idx}, rest_{rest} {} + + public: + iterator() = default; + + using difference_type = index; + using value_type = decltype(ht_tuple(std::declval(), std::declval>().base())); + using pointer = void; + using reference = value_type; + using iterator_category = std::random_access_iterator_tag; + + constexpr auto operator+=(difference_type d) -> iterator& { idx_ += d; return *this; } + constexpr auto operator-=(difference_type d) -> iterator& { idx_ -= d; return *this; } + + constexpr auto operator+(difference_type d) const { return iterator{idx_ + d, rest_}; } + constexpr auto operator-(difference_type d) const { return iterator{idx_ - d, rest_}; } + + friend constexpr auto operator-(iterator const& self, iterator const& other) -> difference_type { assert( self.rest_ == other.rest_ ); return self.idx_ - other.idx_; } + + friend constexpr auto operator+(difference_type n, iterator const& self) { return self + n; } + + constexpr auto operator++() -> auto& { ++idx_; return *this; } + constexpr auto operator--() -> auto& { --idx_; return *this; } + + constexpr auto operator++(int) -> iterator { iterator ret{*this}; ++idx_; return ret; } + constexpr auto operator--(int) -> iterator { iterator ret{*this}; --idx_; return ret; } + + constexpr auto operator*() const { + // multi::detail::what(rest_); + return ht_tuple(idx_, rest_.base()); + } + + constexpr auto operator[](difference_type const& n) const -> reference { return *((*this) + n); } + + friend constexpr auto operator==(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ == other.idx_; } + friend constexpr auto operator!=(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ != other.idx_; } + + friend constexpr auto operator<(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ < other.idx_; } + friend constexpr auto operator>(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ > other.idx_; } + + friend constexpr auto operator<=(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ <= other.idx_; } + friend constexpr auto operator>=(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ >= other.idx_; } + }; + + constexpr auto begin() const { return iterator{this->base().head().first(), this->base().tail()}; } + constexpr auto end() const { return iterator{this->base().head().last() , this->base().tail()}; } + + constexpr auto operator[](index idx) const { + return static_cast(*this)[idx]; + } + + // template + // constexpr auto operator[]() + + template + BOOST_MULTI_HD constexpr auto next_canonical(index& idx, Indices&... rest) const -> bool { // NOLINT(google-runtime-references) idx is mutated + if(extensions_t{this->base().tail()}.next_canonical(rest...)) { + ++idx; + } + if(idx == this->base().head().last()) { + idx = this->base().head().first(); + return true; + } + return false; + } + template + constexpr auto prev_canonical(index& idx, Indices&... rest) const -> bool { // NOLINT(google-runtime-references) idx is mutated + if(extensions_t{this->base().tail()}.prev_canonical(rest...)) { + --idx; + } + if(idx < static_cast(this->base().head().first())) { + idx = static_cast(this->base().head().back()); + return true; + } + return false; + } + + class elements_t { + extensions_t xs_; + explicit constexpr elements_t(extensions_t const& xs) : xs_{xs} {} + + friend struct extensions_t; + + public: + using difference_type = extensions_t::difference_type; + + class iterator { + index_extension::iterator curr_; + + typename extensions_t::elements_t::iterator rest_it_; + typename extensions_t::elements_t::iterator rest_begin_; + typename extensions_t::elements_t::iterator rest_end_; + + BOOST_MULTI_HD constexpr iterator( + index_extension::iterator curr, + typename extensions_t::elements_t::iterator rest_it, + typename extensions_t::elements_t::iterator rest_begin, + typename extensions_t::elements_t::iterator rest_end + ) + : curr_{curr}, rest_it_{rest_it}, rest_begin_{rest_begin}, rest_end_{rest_end} {} + + friend class elements_t; + + public: + using difference_type = elements_t::difference_type; + using value_type = indices_type; + using pointer = void; + using reference = value_type; + using iterator_category = std::random_access_iterator_tag; + + template + class mk_tup { + CUT cu_; + + public: + constexpr explicit mk_tup(CUT cu) : cu_{cu} {} + template + constexpr auto operator()(Ts... es) const { return detail::mk_tuple(cu_, es...); } + }; + + BOOST_MULTI_HD constexpr auto operator*() const { + // printf("op* %ld ...\n", *curr_); + using std::apply; + return apply(mk_tup{*curr_}, *rest_it_); + // return apply([cu = *curr_] BOOST_MULTI_HD (auto... es) {return detail::mk_tuple(cu, es...);}, *rest_it_); + } + + BOOST_MULTI_HD constexpr auto operator+=(difference_type n) -> iterator& { + if(n > 0) { // mull-ignore: cxx_gt_to_ge + curr_ += (rest_it_ - rest_begin_ + n) / (rest_end_ - rest_begin_); + rest_it_ = rest_begin_ + ((rest_it_ - rest_begin_ + n) % (rest_end_ - rest_begin_)); + } else if(n < 0) { // mull-ignore + curr_ -= (rest_end_ - rest_it_ - n) / (rest_end_ - rest_begin_); + rest_it_ = rest_end_ - ((rest_end_ - rest_it_ - n) % (rest_end_ - rest_begin_)); + if(rest_it_ == rest_end_) { + rest_it_ = rest_begin_; + ++curr_; + } + } + return *this; + } + + BOOST_MULTI_HD constexpr auto operator-=(difference_type n) -> iterator& { + if(n > 0) { // mull-ignore: cxx_gt_to_ge + curr_ -= (rest_end_ - rest_it_ + n) / (rest_end_ - rest_begin_); + rest_it_ = rest_end_ - ((rest_end_ - rest_it_ + n) % (rest_end_ - rest_begin_)); + if(rest_it_ == rest_end_) { + rest_it_ = rest_begin_; + ++curr_; + } + } else if(n < 0) { // mull-ignore + curr_ += (rest_it_ - rest_begin_ - n) / (rest_end_ - rest_begin_); + rest_it_ = rest_begin_ + ((rest_it_ - rest_begin_ - n) % (rest_end_ - rest_begin_)); + } + return *this; + } + + friend BOOST_MULTI_HD constexpr auto operator-(iterator const& self, iterator const& other) -> difference_type { + return ((self.curr_ - other.curr_) * (self.rest_end_ - self.rest_begin_)) + (self.rest_it_ - self.rest_begin_) - (other.rest_it_ - other.rest_begin_); + } + + BOOST_MULTI_HD constexpr auto operator-(difference_type n) const { + return iterator{*this} -= n; + } + + BOOST_MULTI_HD constexpr auto operator+(difference_type n) const { + return iterator{*this} += n; + } + + BOOST_MULTI_HD constexpr auto operator++() -> auto& { + // printf("++\n"); + ++rest_it_; + if( rest_it_ == rest_end_ ) { + rest_it_ = rest_begin_; + ++curr_; + } + return *this; + } + + BOOST_MULTI_HD constexpr auto operator--() -> auto& { + // assert(0); + // printf("--\n"); + if( rest_it_ == rest_begin_ ) { + rest_it_ = rest_end_; + --curr_; + } + --rest_it_; + return *this; + } + + BOOST_MULTI_HD constexpr auto operator[](difference_type dd) const { return *((*this) + dd); } + + friend BOOST_MULTI_HD constexpr auto operator==(iterator const& self, iterator const& other) { return (self.curr_ == other.curr_) && (self.rest_it_ == other.rest_it_); } + friend BOOST_MULTI_HD constexpr auto operator!=(iterator const& self, iterator const& other) { return (self.curr_ != other.curr_) || (self.rest_it_ != other.rest_it_); } + + friend BOOST_MULTI_HD constexpr auto operator< (iterator const& self, iterator const& other) { return (self.curr_ < other.curr_) || ((self.curr_ == other.curr_) && (self.rest_it_ < other.rest_it_)); } + friend BOOST_MULTI_HD constexpr auto operator<=(iterator const& self, iterator const& other) { return (self < other) || (self == other); } + }; + + constexpr auto begin() const { + return iterator{ + xs_.head().begin(), + extensions_t{xs_.tail()}.elements().begin(), + extensions_t{xs_.tail()}.elements().begin(), + extensions_t{xs_.tail()}.elements().end(), + }; + } + + constexpr auto end() const { + return iterator{ + xs_.head().end(), + extensions_t{xs_.tail()}.elements().begin(), + extensions_t{xs_.tail()}.elements().begin(), + extensions_t{xs_.tail()}.elements().end(), + }; + } + + auto operator[](index idx) const { return begin()[idx]; } + + auto size() const { return xs_.num_elements(); } + }; + + constexpr auto elements() const { return elements_t{*this}; } + + template + constexpr auto element_transformed(Func fun) const { return [fun](auto const&... xs){ return fun(detail::mk_tuple(xs...)); } ^(*this); } + + BOOST_MULTI_HD constexpr auto extension() const { return this->get<0>(); } + BOOST_MULTI_HD constexpr auto size() const { return this->get<0>().size(); } + BOOST_MULTI_HD constexpr auto sizes() const { + return this->apply([](auto const&... xs) { return multi::detail::mk_tuple(xs.size()...); }); + } + + private: + template + void serialize_impl_(Archive& arxiv, std::index_sequence /*unused012*/) { + using boost::multi::detail::get; + (void)std::initializer_list{(arxiv & multi::archive_traits::make_nvp("extension", get(this->base())), 0U)...}; + } + + public: + template + void serialize(Archive& arxiv, unsigned int const /*version*/) { + serialize_impl_(arxiv, std::make_index_sequence(D)>()); + } + + private: + template(std::declval())...})> + BOOST_MULTI_HD constexpr extensions_t(Array const& tup, std::index_sequence /*unused012*/) + : base_{boost::multi::detail::get(tup)...} {} + + static BOOST_MULTI_HD constexpr auto multiply_fold_() -> size_type { return static_cast(1U); } + static BOOST_MULTI_HD constexpr auto multiply_fold_(size_type const& size) -> size_type { return size; } + template + static BOOST_MULTI_HD constexpr auto multiply_fold_(size_type const& size, As const&... rest) -> size_type { return size * static_cast(multiply_fold_(rest...)); } + + template + BOOST_MULTI_HD constexpr auto num_elements_impl_(std::index_sequence /*unused012*/) const -> size_type { + using boost::multi::detail::get; + return static_cast(multiply_fold_(static_cast(get(this->base()).size())...)); + } + + public: + BOOST_MULTI_HD constexpr auto num_elements() const -> size_type { + return static_cast(num_elements_impl_(std::make_index_sequence(D)>())); + } + friend constexpr auto intersection(extensions_t const& self, extensions_t const& other) -> extensions_t { + using boost::multi::detail::get; + return extensions_t{ + multi::detail::ht_tuple( + index_extension{intersection(get<0>(self.base()), get<0>(other.base()))}, + intersection(extensions_t{self.base().tail()}, extensions_t{other.base().tail()}).base() + ) + }; + } + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + friend constexpr auto get(extensions_t const& self) -> typename std::tuple_element_t { + using boost::multi::detail::get; + return get(self.base()); + } + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + constexpr auto get() const -> std::tuple_element_t { + using boost::multi::detail::get; + return get(this->base()); + } + + template + constexpr auto apply(Fn&& fn) const -> decltype(auto) { + return std::apply(std::forward(fn), this->base()); + } +}; + +template<> struct extensions_t<0> : tuple<> { + using base_ = tuple<>; + + private: + // base_ impl_; + + public: + static constexpr dimensionality_type dimensionality = 0; // TODO(correaa): consider deprecation + + using rank = std::integral_constant; + using element = tuple<>; + + using nelems_type = index; + + explicit BOOST_MULTI_HD constexpr extensions_t(tuple<> const& tup) + : base_{tup} {} + + extensions_t() = default; + + BOOST_MULTI_HD constexpr auto base() const& -> base_ const& { return *this; } + BOOST_MULTI_HD constexpr auto base() & -> base_& { return *this; } + + template static void serialize(Archive& /*ar*/, unsigned /*version*/) { /*noop*/ } + + static BOOST_MULTI_HD constexpr auto num_elements() /*const*/ -> size_type { return 1; } + + using indices_type = tuple<>; + + [[nodiscard]] static constexpr auto from_linear(nelems_type const& n) /*const*/ -> indices_type { + assert(n == 0); + (void)n; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : constexpr function + return indices_type{}; + } + friend constexpr auto operator%(nelems_type const& n, extensions_t const& /*s*/) -> tuple<> { return /*s.*/ from_linear(n); } + + static BOOST_MULTI_HD constexpr auto to_linear() /*const*/ -> difference_type { return 0; } + BOOST_MULTI_HD constexpr auto operator()() const { return to_linear(); } + + constexpr void operator[](index) const = delete; + + static BOOST_MULTI_HD constexpr auto next_canonical() /*const*/ -> bool { return true; } + static BOOST_MULTI_HD constexpr auto prev_canonical() /*const*/ -> bool { return true; } + + friend constexpr auto intersection(extensions_t const& /*x1*/, extensions_t const& /*x2*/) -> extensions_t { return {}; } + + constexpr BOOST_MULTI_HD auto operator==(extensions_t const& /*other*/) const { return true; } + constexpr BOOST_MULTI_HD auto operator!=(extensions_t const& /*other*/) const { return false; } + + template // TODO(correaa) = detele ? + friend constexpr auto get(extensions_t const& self) -> typename std::tuple_element_t { + using boost::multi::detail::get; + return get(self.base()); + } + + template // TODO(correaa) = detele ? + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto get() const -> typename std::tuple_element_t { + using boost::multi::detail::get; + return get(this->base()); + } +}; + +template<> struct extensions_t<1> : tuple { + using base_ = tuple; + + static constexpr auto dimensionality = 1; // TODO(correaa): consider deprecation + + constexpr static dimensionality_type rank_v = 1; + + using size_type = multi::index_extension::size_type; + using difference_type = multi::index_extension::difference_type; + using element = tuple; + + constexpr auto extension() const { using std::get; return get<0>(static_cast(*this)); } + + class iterator { // : public weakly_incrementable { + index idx_; + extensions_t<0> rest_; + friend extensions_t; + + constexpr iterator(index idx, extensions_t<0> rest) : idx_{idx}, rest_{rest} {} + + public: + iterator() = default; + + using difference_type = index; + using value_type = decltype(ht_tuple(std::declval(), std::declval>().base())); + using pointer = void; + using reference = value_type; + using iterator_category = std::random_access_iterator_tag; + + constexpr auto operator+(difference_type d) const { return iterator{idx_ + d, rest_}; } + constexpr auto operator-(difference_type d) const { return iterator{idx_ - d, rest_}; } + + friend constexpr auto operator-(iterator const& self, iterator const& other) -> difference_type { return self.idx_ - other.idx_; } + friend constexpr auto operator+(difference_type n, iterator const& self) { return self + n; } + + constexpr auto operator+=(difference_type d) -> iterator& { idx_ += d; return *this; } + constexpr auto operator-=(difference_type d) -> iterator& { idx_ -= d; return *this; } + + constexpr auto operator++() -> iterator& { ++idx_; return *this; } + constexpr auto operator--() -> iterator& { --idx_; return *this; } + + constexpr auto operator++(int) -> iterator { iterator ret{*this}; operator++(); return ret; } // NOLINT(cert-dcl21-cpp) + constexpr auto operator--(int) -> iterator { iterator ret{*this}; operator--(); return ret; } // NOLINT(cert-dcl21-cpp) + + constexpr auto operator*() const { + // multi::detail::what(rest_); + return ht_tuple(idx_, rest_.base()); + } + + constexpr auto operator[](difference_type n) const -> reference { return *((*this) + n); } + + friend constexpr auto operator==(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ == other.idx_; } + friend constexpr auto operator!=(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ != other.idx_; } + + friend constexpr auto operator<(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ < other.idx_; } + friend constexpr auto operator>(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ > other.idx_; } + + friend constexpr auto operator<=(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ <= other.idx_; } + friend constexpr auto operator>=(iterator const& self, iterator const& other) { assert( self.rest_ == other.rest_ ); return self.idx_ >= other.idx_; } + }; + + constexpr auto begin() const { return iterator{this->base().head().first(), extensions_t<0>{this->base().tail()}}; } + constexpr auto end() const { return iterator{this->base().head().last() , extensions_t<0>{this->base().tail()}}; } + + class elements_t { + multi::index_range rng_; + + public: + class iterator : multi::index_range::iterator { + friend class elements_t; // enclosing class is friend automatically? + BOOST_MULTI_HD constexpr explicit iterator(multi::index_range::iterator it) + : multi::index_range::iterator{it} {} + + BOOST_MULTI_HD constexpr auto base_() const -> multi::index_range::iterator const& { return *this; } + BOOST_MULTI_HD constexpr auto base_() -> multi::index_range::iterator& { return *this; } + + public: + using value_type = std::tuple; + using difference_type = multi::index_range::iterator::difference_type; + using reference = value_type; + // using pointer = void; + // using reference = value_type; + + BOOST_MULTI_HD constexpr auto operator*() const -> reference { return *base_(); } + + BOOST_MULTI_HD constexpr auto operator++() -> iterator& { + ++base_(); + return *this; + } + BOOST_MULTI_HD constexpr auto operator--() -> iterator& { + --base_(); + return *this; + } + + BOOST_MULTI_HD constexpr auto operator++(int) { iterator ret{*this}; ++(*this); return ret; } + BOOST_MULTI_HD constexpr auto operator--(int) { iterator ret{*this}; --(*this); return ret; } + + BOOST_MULTI_HD constexpr auto operator+=(difference_type n) -> iterator& { + base_() += n; + return *this; + } + BOOST_MULTI_HD constexpr auto operator-=(difference_type n) -> iterator& { + base_() -= n; + return *this; + } + + BOOST_MULTI_HD constexpr auto operator+(difference_type n) const -> iterator { return iterator{*this} += n; } + BOOST_MULTI_HD constexpr auto operator-(difference_type n) const -> iterator { return iterator{*this} -= n; } + + friend BOOST_MULTI_HD constexpr auto operator-(iterator const& self, iterator const& other) -> difference_type { + return self.base_() - other.base_(); + } + + BOOST_MULTI_HD constexpr auto operator==(iterator const& other) const { return base_() == other.base_(); } + BOOST_MULTI_HD constexpr auto operator!=(iterator const& other) const { return base_() != other.base_(); } + + BOOST_MULTI_HD constexpr auto operator<(iterator const& other) const { return base_() < other.base_(); } + BOOST_MULTI_HD constexpr auto operator<=(iterator const& other) const { return base_() <= other.base_(); } + + auto operator[](difference_type n) const { return *((*this) + n); } + }; + // using const_iterator = iterator; + + BOOST_MULTI_HD constexpr auto begin() const -> iterator { return iterator{rng_.begin()}; } + BOOST_MULTI_HD constexpr auto end() const -> iterator { return iterator{rng_.end()}; } + + using size_type = multi::index_extension::size_type; + using difference_type = multi::index_extension::difference_type; + using value_type = iterator::value_type; + using reference = iterator::reference; + + BOOST_MULTI_HD constexpr auto operator[](difference_type n) const noexcept(noexcept(*(std::declval()+n))) -> reference { return *(begin()+n); } + + BOOST_MULTI_HD constexpr auto size() const -> size_type { return end() - begin(); } + + BOOST_MULTI_HD constexpr explicit elements_t(multi::index_range rng) + : rng_{rng} {} + }; + + auto elements() const { + using std::get; + // auto rng = get<0>(static_cast const&>(*this)); + return elements_t{get<0>(static_cast const&>(*this))}; + } + + template + friend constexpr auto operator^(Func fun, extensions_t const& xs) { + return f_extensions_t<1, Func>(xs, std::move(fun)); + } + + using nelems_type = index; + + // cppcheck-suppress noExplicitConstructor ; to allow terse syntax (compatible with std::vector(int) constructor + BOOST_MULTI_HD constexpr extensions_t(multi::size_t size) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : base_(multi::index_extension{0, size}) {} + + template + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(tuple extensions) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : base_{static_cast(extensions.head())} {} + + // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(multi::index_extension const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : base_{other} {} + + BOOST_MULTI_HD constexpr explicit extensions_t(base_ tup) + : base_{tup} {} + + template(OtherExtensions{}.extension()) )* = nullptr + > + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr extensions_t(OtherExtensions const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : base_{other.extension()} {} + + extensions_t() = default; + + BOOST_MULTI_HD constexpr auto base() const& -> base_ const& { return *this; } + BOOST_MULTI_HD constexpr auto base() & -> base_& { return *this; } + + BOOST_MULTI_HD constexpr auto operator==(extensions_t const& other) const { return base() == other.base(); } + BOOST_MULTI_HD constexpr auto operator!=(extensions_t const& other) const { return base() != other.base(); } + + BOOST_MULTI_HD constexpr auto size() const -> size_type { return this->base().head().size(); } + + BOOST_MULTI_HD constexpr auto num_elements() const { return size(); } + + using indices_type = multi::detail::tuple; + + [[nodiscard]] BOOST_MULTI_HD constexpr auto from_linear(nelems_type const& n) const -> indices_type { // NOLINT(readability-convert-member-functions-to-static) TODO(correaa) + return indices_type{n}; + } + + friend constexpr auto operator%(nelems_type idx, extensions_t const& extensions) + -> multi::detail::tuple { + return extensions.from_linear(idx); + } + + static BOOST_MULTI_HD constexpr auto to_linear(index const& idx) -> difference_type { return idx; } + + constexpr auto operator[](index idx) const { + using std::get; + return multi::detail::tuple{get<0>(this->base())[idx]}; + } + BOOST_MULTI_HD constexpr auto operator()(index idx) const { return idx; } + + template + BOOST_MULTI_HD constexpr auto next_canonical(index& idx) const -> bool { // NOLINT(google-runtime-references) idx is mutated + using boost::multi::detail::get; + // if(idx == ::boost::multi::detail::get<0>(this->base()).back()) { + // idx = ::boost::multi::detail::get<0>(this->base()).first(); + // return true; + // } + ++idx; + if(idx == get<0>(this->base()).last()) { + idx = get<0>(this->base()).first(); + return true; + } + return false; + } + constexpr auto prev_canonical(index& idx) const -> bool { // NOLINT(google-runtime-references) idx is mutated + using boost::multi::detail::get; + if(idx == get<0>(this->base()).first()) { + // idx = 42; // TODO(correaa) implement and test + idx = get<0>(this->base()).back(); + return true; + } + --idx; + return false; + } + + friend auto intersection(extensions_t const& self, extensions_t const& other) { + return extensions_t{ + intersection( + boost::multi::detail::get<0>(self.base()), + boost::multi::detail::get<0>(other.base()) + ) + }; + } + template + void serialize(Archive& arxiv, unsigned /*version*/) { + using boost::multi::detail::get; + auto& extension_ = get<0>(this->base()); + arxiv& multi::archive_traits::make_nvp("extension", extension_); + } + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + // cppcheck-suppress duplInheritedMember ; to overwrite + constexpr auto get() const -> decltype(auto) { // -> typename std::tuple_element::type { + using boost::multi::detail::get; + return get(this->base()); + } + + template = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + friend constexpr auto get(extensions_t const& self) -> decltype(auto) { // -> typename std::tuple_element::type { + using boost::multi::detail::get; + return get(self.base()); + } +}; + +template using iextensions = extensions_t; + +template +constexpr auto array_size_impl(boost::multi::extensions_t const&) + -> std::integral_constant(D)>; + +extensions_t(multi::size_t) -> extensions_t<1>; +extensions_t(multi::size_t, multi::size_t) -> extensions_t<2>; +extensions_t(multi::size_t, multi::size_t, multi::size_t) -> extensions_t<3>; +extensions_t(multi::size_t, multi::size_t, multi::size_t, multi::size_t) -> extensions_t<4>; +extensions_t(multi::size_t, multi::size_t, multi::size_t, multi::size_t, multi::size_t) -> extensions_t<5>; +extensions_t(multi::size_t, multi::size_t, multi::size_t, multi::size_t, multi::size_t, multi::size_t) -> extensions_t<6>; +extensions_t(multi::size_t, multi::size_t, multi::size_t, multi::size_t, multi::size_t, multi::size_t, multi::size_t) -> extensions_t<7>; + +} // end namespace boost::multi + +// Some versions of Clang throw warnings that stl uses class std::tuple_size instead +// of struct std::tuple_size like it should be +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-tags" +#endif + +template +struct std::tuple_size> // NOLINT(cert-dcl58-cpp) to implement structured binding +: std::integral_constant(D)> {}; + +template<> +struct std::tuple_element<0, boost::multi::extensions_t<0>> { // NOLINT(cert-dcl58-cpp) to implement structured binding + using type = void; +}; + +template +struct std::tuple_element> { // NOLINT(cert-dcl58-cpp) to implement structured binding + using type = typename std::tuple_element_t::base_>; +}; + +namespace std { // NOLINT(cert-dcl58-cpp) + +// clang wants tuple_size to be a class, not a struct with -Wmismatched-tags +#if !defined(__GLIBCXX__) || (__GLIBCXX__ <= 20190406) +template<> struct tuple_size> : std::integral_constant {}; +template<> struct tuple_size> : std::integral_constant {}; +template<> struct tuple_size> : std::integral_constant {}; +template<> struct tuple_size> : std::integral_constant {}; +template<> struct tuple_size> : std::integral_constant {}; +template<> struct tuple_size> : std::integral_constant {}; +#else +template<> class tuple_size> : public std::integral_constant {}; +template<> class tuple_size> : public std::integral_constant {}; +template<> class tuple_size> : public std::integral_constant {}; +template<> class tuple_size> : public std::integral_constant {}; +template<> class tuple_size> : public std::integral_constant {}; +template<> class tuple_size> : public std::integral_constant {}; +#endif + +#if !defined(_MSC_VER) && (!defined(__GLIBCXX__) || (__GLIBCXX__ <= 20240707)) +template +constexpr auto get(::boost::multi::extensions_t const& tp) // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get, gcc workaround + -> decltype(tp.template get()) { + return tp.template get(); +} + +// template // , boost::multi::dimensionality_type D> +// constexpr auto get(boost::multi::extensions_t<2> const& tp) // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get, gcc workaround +// // ->decltype(tp.template get()) { +// -> decltype(auto) { +// return tp.template get(); } + +template +constexpr auto get(::boost::multi::extensions_t& tp) // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get, gcc workaround + -> decltype(tp.template get()) { + return tp.template get(); +} + +template +constexpr auto get(::boost::multi::extensions_t&& tp) // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get, gcc workaround + -> decltype(std::move(tp).template get()) { + return std::move(tp).template get(); +} +#endif + +template +constexpr auto +apply(Fn&& fn, boost::multi::extensions_t const& xs) noexcept -> decltype(auto) { // NOLINT(cert-dcl58-cpp) I have to specialize std::apply as a workaround + return xs.apply(std::forward(fn)); +} + +} // end namespace std + +namespace boost::multi { + +struct monostate : equality_comparable { + friend BOOST_MULTI_HD constexpr auto operator==(monostate const& /*self*/, monostate const& /*other*/) { return true; } +}; + +template +class stride_t { + difference_type stride_; + + public: + BOOST_MULTI_HD constexpr auto operator()() const -> difference_type { return stride_; } + + template + BOOST_MULTI_HD constexpr auto operator()(Ptr ptr) const -> Ptr { return ptr + stride_; } + + using category = std::random_access_iterator_tag; +}; + +template +class contiguous_stride_t { + public: + // using difference_type = SSize; + + BOOST_MULTI_HD constexpr auto operator()() const -> SSize { return 1; } + + template + BOOST_MULTI_HD constexpr auto operator()(Ptr const& ptr) const -> Ptr { return ptr + 1; } + +#if (__cplusplus >= 202002L) + using category = std::random_access_iterator_tag; // std::contiguous_iterator_tag; +#else + using category = std::random_access_iterator_tag; +#endif +}; + +using multi::detail::tuple; + +template +class contiguous_layout { + + public: + using dimensionality_type = multi::dimensionality_t; + using rank = std::integral_constant; + static constexpr auto rank_v = rank::value; + static constexpr dimensionality_type dimensionality = rank_v; + + using size_type = SSize; + using sizes_type = typename boost::multi::detail::tuple; + + using difference_type = SSize; + + using index = size_type; + using index_range = multi::range; + using index_extension = multi::extension_t; + + using indexes = tuple; + + using extension_type = multi::extension_t; + using extensions_type = multi::extensions_t<1>; + + using stride_type = std::integral_constant; + using strides_type = typename boost::multi::detail::tuple; + + using offset_type = std::integral_constant; + + using nelems_type = SSize; + + using sub_type = layout_t<0, SSize>; + + private: + // BOOST_MULTI_NO_UNIQUE_ADDRESS sub_type sub_; + // BOOST_MULTI_NO_UNIQUE_ADDRESS stride_type stride_; + size_type nelems_; + + template + static constexpr auto get_(Tup&& tup) { + using std::get; + return get(std::forward(tup)); + } + + public: + constexpr explicit contiguous_layout(multi::extensions_t<1> xs) + : nelems_{get_<0>(xs).size()} {} + + BOOST_MULTI_HD constexpr contiguous_layout( + sub_type /*sub*/, + stride_type /*stride*/, + offset_type /*offset*/, + nelems_type nelems + ) + : /*sub_{sub}, stride_{} offset_{},*/ nelems_{nelems} {} + + private: + constexpr auto at_aux_(index /*idx*/) const { + return sub_type{}; // sub_.sub_, sub_.stride_, sub_.offset_ + offset_ + (idx*stride_), sub_.nelems_}(); + } + + public: + constexpr auto operator[](index idx) const { return at_aux_(idx); } + + template + BOOST_MULTI_HD constexpr auto operator()(index idx, Indices... rest) const { return operator[](idx)(rest...); } + BOOST_MULTI_HD constexpr auto operator()(index idx) const { return at_aux_(idx); } + BOOST_MULTI_HD constexpr auto operator()() const { return *this; } + + BOOST_MULTI_HD constexpr auto stride() const { return std::integral_constant{}; } + BOOST_MULTI_HD constexpr auto offset() const { return std::integral_constant{}; } + BOOST_MULTI_HD constexpr auto extension() const { return extension_type{0, nelems_}; } + + BOOST_MULTI_HD constexpr auto num_elements() const { return nelems_; } + + BOOST_MULTI_HD constexpr auto size() const { return nelems_; } + BOOST_MULTI_HD constexpr auto sizes() const { return sizes_type{size()}; } + + BOOST_MULTI_HD constexpr auto nelems() const { return nelems_; } + + BOOST_MULTI_HD constexpr auto extensions() const { return multi::extensions_t<1>{extension()}; } + + BOOST_MULTI_HD constexpr auto is_empty() const -> bool { return nelems_ == 0; } + + BOOST_MULTI_NODISCARD("empty checks for emptyness, it performs no action. Use `is_empty()` instead") + BOOST_MULTI_HD constexpr auto empty() const { return is_empty(); } + + constexpr auto sub() const { return layout_t<0, SSize>{}; } + + constexpr auto is_compact() const { return std::true_type{}; } + + BOOST_MULTI_HD constexpr auto drop(difference_type count) const { + assert(count <= this->size()); + + return contiguous_layout{ + this->sub(), + this->stride(), + this->offset(), + this->stride() * (this->size() - count) + }; + } + + BOOST_MULTI_HD constexpr auto slice(index first, index last) const { + return contiguous_layout{ + this->sub(), + this->stride(), + this->offset(), + (this->is_empty()) ? 0 : this->nelems() / this->size() * (last - first) + }; + } +}; + +template +struct bilayout { + using size_type = multi::size_t; // SSize; + using difference_type = std::make_signed_t; + using index = difference_type; + + using stride1_type = difference_type; + using stride2_type = difference_type; + // using bistride_type = std::pair; + using sub_type = layout_t; + + using dimensionality_type = typename sub_type::dimensionality_type; + using rank = std::integral_constant; + constexpr static auto rank_v = rank::value; + + constexpr static auto dimensionality() { return rank_v; } + + private: + stride1_type stride1_; + size_type nelems1_; + stride2_type stride2_; + size_type nelems2_; + sub_type sub_; + + public: + bilayout( + stride1_type stride1, // NOLINT(bugprone-easily-swappable-parameters) + size_type nelems1, + stride2_type stride2, // NOLINT(bugprone-easily-swappable-parameters) + size_type nelems2, + sub_type sub + ) + : stride1_{stride1}, nelems1_{nelems1}, stride2_{stride2}, nelems2_{nelems2}, sub_{std::move(sub)} {} + + using offset_type = std::ptrdiff_t; + using stride_type = std::pair; + using index_range = void; + using strides_type = void; + using extension_type = void; + using extensions_type = void; + using sizes_type = void; + using indexes = void; + + // auto stride() const = delete; + BOOST_MULTI_HD constexpr auto stride() const { + class stride_t { + stride1_type stride1_; + stride2_type stride2_; + size_type nelems2_; + + public: + BOOST_MULTI_HD constexpr explicit stride_t(stride1_type stride1, stride2_type stride2, size_type size) // NOLINT(bugprone-easily-swappable-parameters) + : stride1_{stride1}, stride2_{stride2}, nelems2_{size} {} + BOOST_MULTI_HD constexpr auto operator*(std::ptrdiff_t nn) const { return stride_t{stride1_, nn * stride2_, nelems2_}; } + BOOST_MULTI_HD constexpr auto operator-(offset_type /*unused*/) const { return *this; } +#if (defined(__clang__) && (__clang_major__ >= 16)) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + BOOST_MULTI_HD constexpr auto operator+(double* ptr) { return ptr + (stride2_ % nelems2_) + ((stride2_ / nelems2_) * stride1_); } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic,clang-diagnostic-unsafe-buffer-usage) +#if (defined(__clang__) && (__clang_major__ >= 16)) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif + }; + return stride_t{stride1_, stride2_, nelems2_}; + } + auto num_elements() const = delete; + + BOOST_MULTI_HD constexpr auto offset() const { return offset_type{}; } + BOOST_MULTI_HD constexpr auto size() const { return (nelems2_ / stride2_) * (nelems1_ / stride1_); } + + auto nelems() const = delete; + void extension() const = delete; + auto extensions() const = delete; + auto is_empty() const = delete; + auto empty() const = delete; + auto sub() const = delete; + auto sizes() const = delete; + + auto is_compact() const = delete; + + using index_extension = multi::index_extension; +}; + +template +struct layout_t + : multi::equality_comparable> { + auto flatten() const { + return bilayout{ + stride(), + nelems(), + sub().stride(), + sub().nelems(), + sub().sub() + }; + } + + using dimensionality_type = multi::dimensionality_type; + using rank = std::integral_constant; + + using sub_type = layout_t; + using size_type = SSize; + using difference_type = std::make_signed_t; + using index = difference_type; + + using index_extension = multi::index_extension; + using index_range = multi::range; + + using stride_type = index; + using offset_type = index; + using nelems_type = index; + + using strides_type = typename boost::multi::detail::tuple_prepend::type; + using offsets_type = typename boost::multi::detail::tuple_prepend::type; + using nelemss_type = typename boost::multi::detail::tuple_prepend::type; + + using extension_type = index_extension; // not index_range! + + using extensions_type = extensions_t; + using sizes_type = typename boost::multi::detail::tuple_prepend::type; + + using indexes = typename boost::multi::detail::tuple_prepend::type; + + static constexpr dimensionality_type rank_v = rank::value; + static constexpr dimensionality_type dimensionality = rank_v; // TODO(correaa): consider deprecation + + [[deprecated("for compatibility with Boost.MultiArray, use static `dimensionality` instead")]] + static constexpr auto num_dimensions() { return dimensionality; } // NOSONAR(cpp:S1133) + + friend constexpr auto dimensionality(layout_t const& /*self*/) { return rank_v; } + + private: + sub_type sub_; + stride_type stride_; // = 1; // or std::numeric_limits::max()? + offset_type offset_; + nelems_type nelems_; + + template friend struct layout_t; + + public: + layout_t() = default; + + template< + class OtherLayout, + class = decltype(sub_type{std::declval().sub()}), + class = decltype(stride_type{std::declval().stride()}), + class = decltype(offset_type{std::declval().offset()}), + class = decltype(nelems_type{std::declval().nelems()})> + BOOST_MULTI_HD constexpr explicit layout_t(OtherLayout const& other) + : sub_{other.sub()}, stride_{other.stride()}, offset_{other.offset()}, nelems_{other.nelems()} {} + + private: + template + static BOOST_MULTI_HD constexpr auto apply_(Fun&& fun, Tup&& tup) -> decltype(auto) { // this is workaround for icc 2021 + using std::apply; + return apply(std::forward(fun), std::forward(tup)); + } + + public: + #ifdef __NVCC__ + #pragma nv_diagnostic push + #pragma nv_diag_suppress = 20013 // TODO(correa) use multi::apply // calling a constexpr __host__ function("apply") from a __host__ __device__ function("layout_t") is not allowed. + #endif + private: + template + static BOOST_MULTI_HD constexpr auto std_apply_(Args&&... args) ->decltype(auto) { using std::apply; return apply(std::forward(args)...); } + + public: + BOOST_MULTI_HD constexpr explicit layout_t(extensions_type const& extensions) + : sub_{apply_ ([](auto const&... subexts) { return multi::extensions_t{subexts...}; }, detail::tail(extensions.base()))} + // : sub_{/*std::*/apply([](auto const&... subexts) { return multi::extensions_t{subexts...}; }, detail::tail(extensions.base()))} + , stride_{sub_.num_elements() ? sub_.num_elements() : 1} + , offset_{boost::multi::detail::get<0>(extensions.base()).first() * stride_} + , nelems_{boost::multi::detail::get<0>(extensions.base()).size() * sub().num_elements()} {} + + BOOST_MULTI_HD constexpr explicit layout_t(extensions_type const& extensions, strides_type const& strides) + : sub_{std::apply([](auto const&... subexts) { return multi::extensions_t{subexts...}; }, detail::tail(extensions.base())), detail::tail(strides)}, stride_{boost::multi::detail::get<0>(strides)}, offset_{boost::multi::detail::get<0>(extensions.base()).first() * stride_}, nelems_{boost::multi::detail::get<0>(extensions.base()).size() * sub().num_elements()} {} + #ifdef __NVCC__ + #pragma nv_diagnostic pop + #endif + + BOOST_MULTI_HD constexpr explicit layout_t(sub_type const& sub, stride_type stride, offset_type offset, nelems_type nelems) // NOLINT(bugprone-easily-swappable-parameters) + : sub_{sub}, stride_{stride}, offset_{offset}, nelems_{nelems} {} + + BOOST_MULTI_HD constexpr explicit layout_t(sub_type const& sub, stride_type stride, offset_type offset /*, nelems_type nelems*/) // NOLINT(bugprone-easily-swappable-parameters) + : sub_{sub}, stride_{stride}, offset_{offset} /*, nelems_{nelems}*/ {} // this leaves nelems_ uninitialized + + constexpr auto origin() const { return sub_.origin() - offset_; } + + private: + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wlarge-by-value-copy" + #endif + + BOOST_MULTI_HD constexpr auto at_aux_(index idx) const { + return sub_type{sub_.sub_, sub_.stride_, sub_.offset_ + offset_ + (idx * stride_), sub_.nelems_}(); + } + + public: + BOOST_MULTI_HD constexpr auto operator[](index idx) const { return at_aux_(idx); } + + template + BOOST_MULTI_HD constexpr auto operator()(index idx, Indices... rest) const { return operator[](idx)(rest...); } + BOOST_MULTI_HD constexpr auto operator()(index idx) const { return at_aux_(idx); } + + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunknown-warning-option" + #pragma clang diagnostic ignored "-Wlarge-by-value-copy" // TODO(correaa) can it be returned by reference? + #endif + + BOOST_MULTI_HD constexpr auto operator()() const { return *this; } + + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + + BOOST_MULTI_HD constexpr auto sub() & -> sub_type& { return sub_; } + BOOST_MULTI_HD constexpr auto sub() const& -> sub_type const& { return sub_; } + friend BOOST_MULTI_HD constexpr auto sub(layout_t const& self) -> sub_type const& { return self.sub(); } + + BOOST_MULTI_HD constexpr auto nelems() & -> nelems_type& { return nelems_; } + BOOST_MULTI_HD constexpr auto nelems() const& -> nelems_type const& { return nelems_; } + friend BOOST_MULTI_HD constexpr auto nelems(layout_t const& self) -> nelems_type const& { return self.nelems(); } + + constexpr BOOST_MULTI_HD auto nelems(dimensionality_type dim) const { return (dim != 0) ? sub_.nelems(dim - 1) : nelems_; } + + friend BOOST_MULTI_HD constexpr auto operator==(layout_t const& self, layout_t const& other) -> bool { + return self.sub_ == other.sub_ && self.stride_ == other.stride_ && self.offset_ == other.offset_ && self.nelems_ == other.nelems_; + // return std::tie(self.sub_, self.stride_, self.offset_, self.nelems_) == std::tie(other.sub_, other.stride_, other.offset_, other.nelems_); + } + + friend BOOST_MULTI_HD constexpr auto operator!=(layout_t const& self, layout_t const& other) -> bool { + return !(self == other); + // return std::tie(self.sub_, self.stride_, self.offset_, self.nelems_) != std::tie(other.sub_, other.stride_, other.offset_, other.nelems_); + } + + constexpr BOOST_MULTI_HD auto operator<(layout_t const& other) const -> bool { + return std::tie(sub_, stride_, offset_, nelems_) < std::tie(other.sub_, other.stride_, other.offset_, other.nelems_); + } + + constexpr auto reindex() const { return *this; } + constexpr auto reindex(index idx) const { + return layout_t{ + sub(), + stride(), + idx * stride(), + nelems() + }; + } + template + constexpr auto reindexed(index first, Indexes... idxs) const { + return ((reindexed(first).rotate()).reindexed(idxs...)).unrotate(); + } + + BOOST_MULTI_HD constexpr auto num_elements() const noexcept -> size_type { return size() * sub_.num_elements(); } // TODO(correaa) investigate mutation * -> / + friend BOOST_MULTI_HD constexpr auto num_elements(layout_t const& self) noexcept -> size_type { return self.num_elements(); } + + BOOST_MULTI_HD constexpr auto is_empty() const noexcept { return nelems_ == 0; } // mull-ignore: cxx_eq_to_ne + friend BOOST_MULTI_HD constexpr auto is_empty(layout_t const& self) noexcept { return self.is_empty(); } + + BOOST_MULTI_HD constexpr auto empty() const noexcept { return is_empty(); } + + friend BOOST_MULTI_HD constexpr auto size(layout_t const& self) noexcept -> size_type { return self.size(); } + BOOST_MULTI_HD constexpr auto size() const noexcept -> size_type { + if(nelems_ == 0) { + return 0; + } + // BOOST_MULTI_ACCESS_ASSERT(stride_); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + // if(nelems_ != 0) {MULTI_ACCESS_ASSERT(stride_ != 0);} + // return nelems_ == 0?0:nelems_/stride_; + // assert(stride_ != 0); + return nelems_ / stride_; + } + + BOOST_MULTI_HD constexpr auto stride() -> stride_type& { return stride_; } + BOOST_MULTI_HD constexpr auto stride() const -> stride_type const& { return stride_; } + + friend BOOST_MULTI_HD constexpr auto stride(layout_t const& self) -> index { return self.stride(); } + + BOOST_MULTI_HD constexpr auto strides() const -> strides_type { return strides_type{stride(), sub_.strides()}; } + friend BOOST_MULTI_HD constexpr auto strides(layout_t const& self) -> strides_type { return self.strides(); } + + constexpr BOOST_MULTI_HD auto offset(dimensionality_type dim) const -> index { return (dim != 0) ? sub_.offset(dim - 1) : offset_; } + BOOST_MULTI_HD constexpr auto offset() const -> index { return offset_; } + friend BOOST_MULTI_HD constexpr auto offset(layout_t const& self) -> index { return self.offset(); } + constexpr BOOST_MULTI_HD auto offsets() const { return boost::multi::detail::tuple{offset(), sub_.offsets()}; } + constexpr BOOST_MULTI_HD auto nelemss() const { return boost::multi::detail::tuple{nelems(), sub_.nelemss()}; } + + constexpr auto base_size() const { + using std::max; + return max(nelems_, sub_.base_size()); + } + + constexpr auto is_compact() const& { return base_size() == num_elements(); } + friend constexpr auto is_compact(layout_t const& self) { return self.is_compact(); } + + constexpr auto shape() const& -> decltype(auto) { return sizes(); } + friend constexpr auto shape(layout_t const& self) -> decltype(auto) { return self.shape(); } + + BOOST_MULTI_HD constexpr auto sizes() const noexcept { return multi::detail::ht_tuple(size(), sub_.sizes()); } + + friend BOOST_MULTI_HD constexpr auto extension(layout_t const& self) { return self.extension(); } + [[nodiscard]] BOOST_MULTI_HD constexpr auto extension() const -> extension_type { + if(nelems_ == 0) { + return index_extension{}; + } + // assert(stride_ != 0); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + assert(offset_ % stride_ == 0); + assert(nelems_ % stride_ == 0); + return index_extension{offset_ / stride_, (offset_ + nelems_) / stride_}; + } + + BOOST_MULTI_HD constexpr auto extensions() const { + // auto fa = extension(); + // auto sa = sub_.extensions().base(); + // auto ht_tuple = multi::detail::ht_tuple(fa, sa); + // auto ret = extensions_type{ht_tuple}; + // return ret; + return extensions_type{multi::detail::ht_tuple(extension(), sub_.extensions().base())}; + } + + friend BOOST_MULTI_HD constexpr auto extensions(layout_t const& self) -> extensions_type { return self.extensions(); } + + [[deprecated("use get(m.extensions()")]] // TODO(correaa) redeprecate, this is commented to give a smaller CI output + constexpr auto + extension(dimensionality_type dim) const { + return std::apply([](auto... extensions) { return std::array(D)>{extensions...}; }, extensions().base()).at(static_cast(dim)); + } // cppcheck-suppress syntaxError ; bug in cppcheck 2.14 + // [[deprecated("use get(m.strides()) ")]] // TODO(correaa) redeprecate, this is commented to give a smaller CI output + constexpr auto stride(dimensionality_type dim) const { + return std::apply([](auto... strides) { return std::array(D)>{strides...}; }, strides()).at(static_cast(dim)); + } + // [[deprecated("use get(m.sizes()) ")]] // TODO(correaa) redeprecate, this is commented to give a smaller CI output + // constexpr auto size (dimensionality_type dim) const {return std::apply([](auto... sizes ) {return std::array(D)>{sizes ...};}, sizes () ).at(static_cast(dim));} + + BOOST_MULTI_HD constexpr auto drop(difference_type count) const { + assert(count <= this->size()); + + return layout_t{ + this->sub(), + this->stride(), + this->offset(), + this->stride() * (this->size() - count) + }; + } + + BOOST_MULTI_HD constexpr auto slice(index first, index last) const { + return layout_t{ + this->sub(), + this->stride(), + this->offset(), + (this->is_empty()) ? 0 : this->nelems() / this->size() * (last - first) + }; + } + + // template + // constexpr auto partition(Size const& count) -> layout_t& { + // stride_ *= count; + // nelems_ *= count; + // sub_.partition(count); + // return *this; + // } + + constexpr auto partition(size_type n) const { + assert(n != 0); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function + // vvv TODO(correaa) should be size() here? + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) normal in a constexpr function + assert((this->nelems() % n) == 0); // if you get an assertion here it means that you are partitioning an array with an incommunsurate partition + return multi::layout_t{ + multi::layout_t{ + this->sub(), + this->stride(), + this->offset(), + this->nelems() / n // mull-ignore: cxx_div_to_mul + }, + this->nelems() / n, // mull-ignore: cxx_div_to_mul + 0, + this->nelems() + }; + // new_layout.sub().nelems() /= n; + } + + template + constexpr static void ce_swap(TT& t1, TT& t2) { + TT tmp = std::move(t1); + t1 = std::move(t2); + t2 = tmp; + } + + BOOST_MULTI_HD constexpr auto transpose() const { + return layout_t( + sub_type( + sub().sub(), + stride(), + offset(), + nelems() + ), + sub().stride(), + sub().offset(), + sub().nelems() + ); + } + + constexpr auto reverse() const { + auto ret = unrotate(); + return layout_t( + ret.sub().reverse(), + ret.stride(), + ret.offset(), + ret.nelems() + ); + } + + BOOST_MULTI_HD constexpr auto rotate() const { + if constexpr(D > 1) { + auto const ret = transpose(); + return layout_t( + ret.sub().rotate(), + ret.stride(), + ret.offset(), + ret.nelems() + ); + } else { + return *this; + } + } + + BOOST_MULTI_HD constexpr auto unrotate() const { + if constexpr(D > 1) { + auto const ret = layout_t( + sub().unrotate(), + stride(), + offset(), + nelems() + ); + return ret.transpose(); + } else { + return *this; + } + } + + constexpr auto hull_size() const -> size_type { + if(is_empty()) { + return 0; + } + return std::abs(size() * stride()) > std::abs(sub_.hull_size()) ? size() * stride() : sub_.hull_size(); + } + + [[deprecated("use two arg version")]] constexpr auto scale(size_type factor) const { + return layout_t{sub_.scale(factor), stride_ * factor, offset_ * factor, nelems_ * factor}; + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wlarge-by-value-copy" // TODO(correaa) use checked span +#endif + + BOOST_MULTI_HD constexpr auto take(size_type n) const { + return layout_t( + this->sub(), + this->stride(), + this->offset(), + this->stride() * n + ); + } + + BOOST_MULTI_HD constexpr auto halve() const { + assert(this->size() % 2 == 0); + return layout_t( + this->take(this->size() / 2), + this->nelems() / 2, + 0, + this->nelems() + ); + } + + constexpr auto scale(size_type num, size_type den) const { + assert((stride_ * num) % den == 0); + assert(offset_ == 0); // TODO(correaa) implement ----------------vvv + return layout_t{sub_.scale(num, den), stride_ * num / den, offset_ /* *num/den */, nelems_ * num / den}; + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +}; + +template +struct layout_t<0, SSize> +: multi::equality_comparable> { + using dimensionality_type = multi::dimensionality_type; + using rank = std::integral_constant; + + using size_type = SSize; + using difference_type = std::make_signed_t; + using index = difference_type; + using index_extension = multi::index_extension; + using index_range = multi::range; + + using sub_type = monostate; + using stride_type = monostate; + using offset_type = index; + using nelems_type = index; + + using strides_type = tuple<>; + using offsets_type = tuple<>; + using nelemss_type = tuple<>; + + using extension_type = void; + + using extensions_type = extensions_t; + using sizes_type = tuple<>; + using indexes = tuple<>; + + static constexpr dimensionality_type rank_v = rank::value; + static constexpr dimensionality_type dimensionality = rank_v; // TODO(correaa) : consider deprecation + + friend constexpr auto dimensionality(layout_t const& /*self*/) { return rank_v; } + + private: +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // '6' bytes padding added after data member +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + BOOST_MULTI_NO_UNIQUE_ADDRESS sub_type sub_; + BOOST_MULTI_NO_UNIQUE_ADDRESS stride_type stride_; // TODO(correaa) padding struct 'boost::multi::layout_t<0>' with 1 byte to align 'stride_' [-Werror,-Wpadded] + + offset_type offset_; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + nelems_type nelems_; + + template friend struct layout_t; + + public: + layout_t() = default; + + BOOST_MULTI_HD constexpr explicit layout_t(extensions_type const& /*nil*/) + : offset_{0}, nelems_{1} {} + + // BOOST_MULTI_HD constexpr explicit layout_t(extensions_type const& /*nil*/, strides_type const& /*nil*/) {} + + BOOST_MULTI_HD constexpr layout_t(sub_type sub, stride_type stride, offset_type offset, nelems_type nelems) // NOLINT(bugprone-easily-swappable-parameters) + : sub_{sub}, stride_{stride}, offset_{offset}, nelems_{nelems} {} + + [[nodiscard]] BOOST_MULTI_HD constexpr auto extensions() const { return extensions_type{}; } + friend BOOST_MULTI_HD constexpr auto extensions(layout_t const& self) { return self.extensions(); } + + [[nodiscard]] BOOST_MULTI_HD constexpr auto num_elements() const { return nelems_; } + friend BOOST_MULTI_HD constexpr auto num_elements(layout_t const& self) { return self.num_elements(); } + + [[nodiscard]] BOOST_MULTI_HD constexpr auto sizes() const { return tuple<>{}; } + friend BOOST_MULTI_HD constexpr auto sizes(layout_t const& self) { return self.sizes(); } + + [[nodiscard]] BOOST_MULTI_HD constexpr auto strides() const { return strides_type{}; } + [[nodiscard]] BOOST_MULTI_HD constexpr auto offsets() const { return offsets_type{}; } + [[nodiscard]] BOOST_MULTI_HD constexpr auto nelemss() const { return nelemss_type{}; } + + BOOST_MULTI_HD constexpr auto operator()() const { return offset_; } + // constexpr explicit operator offset_type() const {return offset_;} + + constexpr auto stride() const -> stride_type = delete; + constexpr auto offset() const -> offset_type { return offset_; } + constexpr auto nelems() const -> nelems_type { return nelems_; } + constexpr auto sub() const -> sub_type = delete; + + constexpr auto size() const -> size_type = delete; + constexpr auto extension() const -> extension_type = delete; + + BOOST_MULTI_HD constexpr auto is_empty() const noexcept { return nelems_ == 0; } + + BOOST_MULTI_NODISCARD("empty checks for emptyness, it performs no action. Use `is_empty()` instead") + constexpr auto empty() const noexcept { return nelems_ == 0; } + + friend constexpr auto empty(layout_t const& self) noexcept { return self.empty(); } + + [[deprecated("is going to be removed")]] + constexpr auto is_compact() const -> bool = delete; + + constexpr auto base_size() const -> size_type { return 0; } + constexpr auto origin() const -> offset_type { return 0; } + + constexpr auto reverse() const { return *this; } + // constexpr auto reverse() -> layout_t& {return *this;} + + BOOST_MULTI_HD constexpr auto take(size_type /*n*/) const { + return layout_t<0, SSize>{}; + } + + BOOST_MULTI_HD constexpr auto halve() const { + return layout_t<1, SSize>(*this, 0, 0, 0); + } + + // [[deprecated("use two arg version")]] constexpr auto scale(size_type /*size*/) const {return *this;} + constexpr auto scale(size_type /*num*/, size_type /*den*/) const { return *this; } + + // friend constexpr auto operator!=(layout_t const& self, layout_t const& other) {return not(self == other);} + friend BOOST_MULTI_HD constexpr auto operator==(layout_t const& self, layout_t const& other) { + return + self.sub_ == other.sub_ && + self.stride_ == other.stride_ && + self.nelems_ == other.nelems_ + ; + // return std::tie(self.sub_, self.stride_, self.offset_, self.nelems_) == std::tie(other.sub_, other.stride_, other.offset_, other.nelems_); + } + + friend BOOST_MULTI_HD constexpr auto operator!=(layout_t const& self, layout_t const& other) { + return !(self==other); + // return std::tie(self.sub_, self.stride_, self.offset_, self.nelems_) != std::tie(other.sub_, other.stride_, other.offset_, other.nelems_); + } + + constexpr auto operator<(layout_t const& other) const -> bool { + return std::tie(offset_, nelems_) < std::tie(other.offset_, other.nelems_); + } + + BOOST_MULTI_HD constexpr auto rotate() const { return *this; } + BOOST_MULTI_HD constexpr auto unrotate() const { return *this; } + + constexpr auto hull_size() const -> size_type { return num_elements(); } // not in bytes +}; + +BOOST_MULTI_HD constexpr auto +operator*(layout_t<0>::index_extension const& extensions_0d, layout_t<0>::extensions_type const& /*zero*/) + -> typename layout_t<1>::extensions_type { + return typename layout_t<1>::extensions_type{tuple::index_extension>{extensions_0d}}; +} + +BOOST_MULTI_HD constexpr auto operator*(extensions_t<1> const& extensions_1d, extensions_t<1> const& self) { + using boost::multi::detail::get; + return extensions_t<2>({get<0>(extensions_1d.base()), get<0>(self.base())}); +} + +} // end namespace boost::multi + +namespace boost::multi::detail { + +template +struct convertible_tuple : Tuple { + using Tuple::Tuple; + BOOST_MULTI_HD explicit convertible_tuple(Tuple const& other) + : Tuple(other) {} + + public: + using array_type = std::array>; + auto to_array() const noexcept { + return std::apply([](auto... es) noexcept { + return std::array, sizeof...(es)>{{static_cast(es)...}}; + }, + static_cast(*this)); + } + + /*explicit*/ operator array_type() const& noexcept { return to_array(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + /*explicit*/ operator array_type() && noexcept { return to_array(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-stack-address" +#endif + [[deprecated("This is here for nominal compatiblity with Boost.MultiArray, this would be a dangling conversion")]] + operator std::ptrdiff_t const*() const&&; // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + /*{ return to_array().data(); }*/ +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + template), int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + friend BOOST_MULTI_HD constexpr auto get(convertible_tuple const& self) -> std::tuple_element_t { + using std::get; + return get(static_cast(self)); + } +}; + +template +struct decaying_array : Array { + using Array::Array; + explicit decaying_array(Array const& other) + : Array(other) {} + + [[deprecated("possible dangling conversion, use `std::array p` instead of `auto* p`")]] + constexpr operator std::ptrdiff_t const*() const { return Array::data(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + + template), int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) + friend constexpr auto get(decaying_array const& self) -> std::tuple_element_t { + using std::get; + return get(static_cast(self)); + } +}; +} // end namespace boost::multi::detail + +template struct std::tuple_size> : std::integral_constant> {}; // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple size +template struct std::tuple_size> : std::integral_constant> {}; // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple size + +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) && !defined(_MSC_VER) +namespace std::ranges { // NOLINT(cert-dcl58-cpp) to enable borrowed, nvcc needs namespace +template<> +[[maybe_unused]] constexpr bool enable_borrowed_range<::boost::multi::extensions_t<1>::elements_t> = true; // NOLINT(misc-definitions-in-headers) + +template +[[maybe_unused]] constexpr bool enable_borrowed_range<::boost::multi::f_extensions_t > = true; // NOLINT(misc-definitions-in-headers) +} // end namespace std::ranges +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#undef BOOST_MULTI_HD + +#endif // BOOST_MULTI_DETAIL_LAYOUT_HPP diff --git a/include/multi/detail/memory.hpp b/include/boost/multi/detail/memory.hpp similarity index 52% rename from include/multi/detail/memory.hpp rename to include/boost/multi/detail/memory.hpp index 0923206f6..d05e23d97 100644 --- a/include/multi/detail/memory.hpp +++ b/include/boost/multi/detail/memory.hpp @@ -1,42 +1,39 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa +// Copyright 2019-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_DETAIL_MEMORY_HPP -#define MULTI_DETAIL_MEMORY_HPP +#ifndef BOOST_MULTI_DETAIL_MEMORY_HPP +#define BOOST_MULTI_DETAIL_MEMORY_HPP -#include // for std::allocator_traits -#include // for std::void_t +#include // for has_rank, to_address + +#include // for copy, iterator_traits +#include // for allocator_traits +#include // for declval, enable_if_t, false_type, is_trivially_default_constructible, true_type, void_t +#include // for addressof, forward namespace boost::multi { template -struct allocator_traits : std::allocator_traits { -#if 0 - template - static auto construct(Alloc& alloc, Ptr p, Args&&... args) // NOLINT(readability-identifier-length) std naming - ->decltype(alloc.construct(p, std::forward(args)...)) { - return alloc.construct(p, std::forward(args)...); } - - template - static auto destroy(Alloc& alloc, Ptr p) // NOLINT(readability-identifier-length) std naming - ->decltype(alloc.destroy(p)) { - return alloc.destroy(p); } -#endif -}; +struct allocator_traits : std::allocator_traits {}; // https://en.cppreference.com/w/cpp/memory/destroy -template::value, int> = 0> +template< + class Alloc, class ForwardIt, + std::enable_if_t::value, int> =0 // NOLINT(modernize-use-constraints) TODO(correaa) +> void destroy(Alloc& alloc, ForwardIt first, ForwardIt last) { for(; first != last; ++first) {allocator_traits::destroy(alloc, std::addressof(*first));} // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm } -template::value and ForwardIt::rank_v == 1, int> = 0> +template::value && ForwardIt::rank_v == 1, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) void destroy(Alloc& alloc, ForwardIt first, ForwardIt last) { // using multi::to_address; - for(; first != last; ++first) {alloc.destroy(to_address(first));} // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + std::for_each(first, last, [&](auto& elem) {alloc.destroy(addressof(elem));}); + // for(; first != last; ++first) {alloc.destroy(to_address(first));} // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm } -template::value and ForwardIt::rank_v != 1, int> = 0> +template::value && ForwardIt::rank_v != 1, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) void destroy(Alloc& alloc, ForwardIt first, ForwardIt last) { for(; first != last; ++first) {destroy(alloc, begin(*first), end(*first));} // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm } @@ -68,7 +65,7 @@ auto uninitialized_default_construct_n(Alloc& alloc, ForwardIt first, Size n) -> template< class Alloc, class ForwardIt, class Size, typename T = typename std::iterator_traits::value_type, - typename = std::enable_if_t{}> + typename = std::enable_if_t{}> // NOLINT(modernize-use-constraints) TODO(correaa) > auto uninitialized_value_construct_n(Alloc& alloc, ForwardIt first, Size n) -> ForwardIt { ForwardIt current = first; // using std::addressof; @@ -76,7 +73,7 @@ auto uninitialized_value_construct_n(Alloc& alloc, ForwardIt first, Size n) -> F for(; n > 0; ++current, --n) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm allocator_traits::construct(alloc, to_address(current), T{}); } - // a.construct(to_address(current), T()); // a.construct(std::pointer_traits::pointer_to(*current), T()); // AT::construct(a, to_address(current), T()); // AT::construct(a, addressof(*current), T()); // a.construct(addressof(*current), T()); + // a.construct(to_address(current), T()); // a.construct(std::pointer_traits::pointer_to(*current), T()); // AT::construct(a, to_address(current), T()); // AT::construct(a, addressof(*current), T()); // a.construct(addressof(*current), T()); return current; } catch(...) {destroy(alloc, first, current); throw;} } @@ -88,7 +85,7 @@ template auto std_copy(Args&&... args) { namespace xtd { -template{}> > +template{}> > // NOLINT(modernize-use-constraints) TODO(correaa) auto alloc_uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, MIt dest) -> MIt { MIt current = dest; // using multi::to_address; @@ -100,20 +97,6 @@ auto alloc_uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, MIt des } // end namespace xtd -// // https://en.cppreference.com/w/cpp/memory/destroy_at -// template > -// void destroy_at(Alloc& a, T* p) {AT::destroy(a, p);} - -// // https://en.cppreference.com/w/cpp/memory/destroy_n -// template // , typename AT = typename std::allocator_traits > -// auto destroy_n(Alloc& a, ForwardIt first, Size n) -> ForwardIt { -// // using std::addressof; -// for(; n > 0; ++first, --n) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm -// allocator_traits::destroy(a, to_address(first)); -// } -// return first; -// } - template struct is_allocator : std::false_type {}; @@ -123,35 +106,17 @@ struct is_allocator().deallocate(typename Alloc::pointer{std::declval().allocate(std::declval())}, std::declval()) )>> : std::true_type {}; -//template class is_allocator { -// template< -// class A, -// class P = typename A::pointer, class S = typename A::size_type, -// typename = decltype( -// std::declval() == A{std::declval()}, -// std::declval().deallocate(P{std::declval().allocate(std::declval())}, std::declval()) -// ) -// > -// static auto aux(A const&) -> std::true_type; -// static auto aux(... ) -> std::false_type; - -// public: -// constexpr static bool const value = decltype(aux(std::declval()))::value; -// constexpr explicit operator bool() const {return value;} -//}; - template constexpr bool is_allocator_v = is_allocator::value; -template -auto uninitialized_copy(InputIt first, InputIt last, ForwardIt dest) { - while(first!=last) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - uninitialized_copy(begin(*first), end(*first), begin(*dest)); - ++first; - ++dest; - } - return dest; -} +// template +// auto uninitialized_copy(InputIt first, InputIt last, ForwardIt dest) { +// while(first!=last) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm +// uninitialized_copy(begin(*first), end(*first), begin(*dest)); +// ++first; +// ++dest; +// } +// return dest; +// } } // end namespace boost::multi - -#endif +#endif // BOOST_MULTI_DETAIL_MEMORY_HPP diff --git a/include/boost/multi/detail/operators.hpp b/include/boost/multi/detail/operators.hpp new file mode 100644 index 000000000..6d16a942a --- /dev/null +++ b/include/boost/multi/detail/operators.hpp @@ -0,0 +1,353 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_OPERATORS_HPP +#define BOOST_MULTI_DETAIL_OPERATORS_HPP + +#include // for ptrdiff_t +#include // for random_access_iterator_tag +#include // for enable_if_t, is_base_of +#include // for forward + +#ifdef __NVCC__ + #define BOOST_MULTI_HD __host__ __device__ +#else + #define BOOST_MULTI_HD +#endif + +namespace boost::multi { + +struct empty_base {}; + +template struct selfable { + protected: + selfable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + public: + using self_type = Self; + constexpr auto self() const -> self_type const& { + static_assert(std::is_base_of_v, Self>); + return static_cast(*this); + } + constexpr auto self() -> self_type& { + static_assert(std::is_base_of_v, Self>); + return static_cast(*this); + } + friend constexpr auto self(selfable const& self) -> self_type const& { + static_assert(std::is_base_of_v, Self>); + return self.self(); + } +}; + +template +class ra_iterable : selfable { + ra_iterable() = default; + friend Self; + + template + using difference_type_t = decltype(std::declval() - std::declval()); + + public: + using iterator_category = std::random_access_iterator_tag; + + template, int> =0> // NOLINT(modernize-use-constraints) for C++20 + friend BOOST_MULTI_HD constexpr auto operator+(Self2 self, difference_type_t const& n) { return self += n; } + // template + // friend auto operator+(difference_type const& n, Self2 const& self) { return self + n; } + BOOST_MULTI_HD constexpr auto operator++(int) { Self tmp{*this}; ++(this->self()); return tmp; } // NOLINT(cert-dcl21-cpp) + BOOST_MULTI_HD constexpr auto operator--(int) { Self tmp{*this}; --(this->self()); return tmp; } // NOLINT(cert-dcl21-cpp) + + template, int> =0> // NOLINT(modernize-use-constraints) for C++20 + BOOST_MULTI_HD constexpr auto friend operator!=(Self2 const& self, Self2 const& other) { return !(self == other); } +}; + +template struct equality_comparable2; + +template +struct equality_comparable2 : selfable { + // friend constexpr auto operator==(equality_comparable2 const& self, equality_comparable2 const& other) {return self.self() == other.self() ;} + friend constexpr auto operator!=(equality_comparable2 const& self, equality_comparable2 const& other) { return !(self.self() == other.self()); } +}; + +template struct equality_comparable : equality_comparable2 { + protected: + equality_comparable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; +}; + +template struct totally_ordered2; + +template +struct totally_ordered2 : equality_comparable2, totally_ordered2> { + using self_type = Self; + BOOST_MULTI_HD constexpr auto self() const -> self_type const& { return static_cast(*this); } + + // friend auto operator< (totally_ordered2 const& self, totally_ordered2 const& other) -> bool {return self.self() < other.self() ;} + friend BOOST_MULTI_HD constexpr auto operator==(totally_ordered2 const& self, totally_ordered2 const& other) -> bool { return !(self.self() < other.self()) && !(other.self() < self.self()); } + // friend auto operator!=(totally_ordered2 const& self, totally_ordered2 const& other) {return (s.self() < o.self()) or (o.self() < s.self());} + + friend BOOST_MULTI_HD constexpr auto operator<=(totally_ordered2 const& self, totally_ordered2 const& other) -> bool { return !(other.self() < self.self()); } + + friend BOOST_MULTI_HD constexpr auto operator>(totally_ordered2 const& self, totally_ordered2 const& other) -> bool { return !(self.self() < other.self()) && !(self.self() == other.self()); } + friend BOOST_MULTI_HD constexpr auto operator>=(totally_ordered2 const& self, totally_ordered2 const& other) -> bool { return !(self.self() < other.self()); } +}; + +template using totally_ordered = totally_ordered2; + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4820 ) // '3' bytes padding added after data member +#endif +template +struct totally_ordered2 { + // template + // friend constexpr auto operator<=(T const& self, U const& other) { return (self < other) || (self == other); } + // template + // friend constexpr auto operator>=(T const& self, U const& other) { return (other < self) || (self == other); } + // template + // friend constexpr auto operator>(T const& self, U const& other) { return other < self; } +}; +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +template +struct copy_constructible {}; + +template +struct weakly_incrementable : selfable { + protected: + weakly_incrementable() = default; + + public: + constexpr auto operator++(int) -> T { + auto ret{this->self()}; ++(this->self()); return ret; + } +}; + +template +struct weakly_decrementable { + protected: + weakly_decrementable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend T; + // friend T& operator--(weakly_decrementable& t){return --static_cast(t);} +}; + +template +struct incrementable : totally_ordered { + protected: + incrementable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + public: + friend BOOST_MULTI_HD constexpr auto operator++(incrementable& self, int) -> Self { + static_assert(std::is_base_of_v, Self>); + Self tmp{self.self()}; + ++self.self(); + assert(self.self() > tmp); + return tmp; + } +}; + +template +struct decrementable : weakly_decrementable { + protected: + decrementable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend T; + + public: + template>> // NOLINT(modernize-use-constraints) TODO(correaa) + friend constexpr auto operator--(U& self, int) -> T { + T tmp{self}; + --self; + return tmp; + } +}; + +template +struct steppable : totally_ordered { + protected: + steppable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + public: + using self_type = Self; + BOOST_MULTI_HD constexpr auto self() const -> self_type const& { return static_cast(*this); } + BOOST_MULTI_HD constexpr auto self() -> self_type& { return static_cast(*this); } + + friend BOOST_MULTI_HD constexpr auto operator++(steppable& self, int) -> Self { + Self tmp{self.self()}; + ++self.self(); + return tmp; + } + friend BOOST_MULTI_HD constexpr auto operator--(steppable& self, int) -> Self { + Self tmp{self.self()}; + --self.self(); + return tmp; + } +}; + +template +struct affine_with_unit : steppable { + protected: + affine_with_unit() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + public: + using self_type = Self; + BOOST_MULTI_HD constexpr auto cself() const -> self_type const& { return static_cast(*this); } + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto self() const -> self_type const& { return static_cast(*this); } + BOOST_MULTI_HD constexpr auto self() -> self_type& { return static_cast(*this); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + using difference_type = Difference; + friend BOOST_MULTI_HD constexpr auto operator++(affine_with_unit& self) -> Self& { return self.self() += difference_type{1}; } + friend BOOST_MULTI_HD constexpr auto operator--(affine_with_unit& self) -> Self& { return self.self() -= difference_type{1}; } + + BOOST_MULTI_HD constexpr auto operator+(difference_type const& diff) const -> Self { + auto ret{cself()}; + ret += diff; + return ret; + } + friend BOOST_MULTI_HD constexpr auto operator+(difference_type const& diff, affine_with_unit const& self) -> Self { + auto ret{self.self()}; + ret += diff; + return ret; + } + friend constexpr auto operator<(affine_with_unit const& self, affine_with_unit const& other) -> bool { + return difference_type{0} < other.self() - self.self(); + } +}; + +template +struct dereferenceable { + protected: + dereferenceable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + public: + using self_type = Self; + constexpr auto self() const -> self_type const& { return static_cast(*this); } + constexpr auto self() -> self_type& { return static_cast(*this); } + + using reference = Reference; + + BOOST_MULTI_HD constexpr auto operator*() const -> reference { return *(self().operator->()); } +}; + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4820 ) // '7' bytes padding added after base class +#endif + +template +struct random_accessable // NOLINT(fuchsia-multiple-inheritance) +: affine_with_unit +, dereferenceable { + protected: + random_accessable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + public: + using difference_type = Difference; + using reference = Reference; + using iterator_category = std::random_access_iterator_tag; + + using self_type = Self; + // cppcheck-suppress-begin duplInheritedMember ; to overwrite + BOOST_MULTI_HD constexpr auto self() const -> self_type const& { return static_cast(*this); } + BOOST_MULTI_HD constexpr auto self() -> self_type& { return static_cast(*this); } + // cppcheck-suppress-end duplInheritedMember ; to overwrite + + BOOST_MULTI_HD constexpr auto operator[](difference_type idx) const -> reference { return *(self() + idx); } +}; + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +template +class addable2 { + protected: + addable2() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + public: + using difference_type = D; + + template{}>> // NOLINT(modernize-use-constraints) TODO(correaa) + friend BOOST_MULTI_HD constexpr auto operator+(TT&& self, difference_type const& diff) -> Self { return Self{std::forward(self)} += diff; } + + template{}>> // NOLINT(modernize-use-constraints) TODO(correaa) + friend BOOST_MULTI_HD constexpr auto operator+(difference_type const& diff, TT&& self) -> Self { return std::forward(self) + diff; } +}; + +template +class subtractable2 { + protected: + subtractable2() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend T; + + public: + using difference_type = D; + // TODO(correaa) clang 16 picks up this and converts the difference_type to TT !! + // template + // friend auto operator-(TT&& self, difference_type const& diff) -> T {T tmp{std::forward(self)}; tmp -= diff; return tmp;} +}; + +template +struct affine : addable2 +, subtractable2 { + protected: + affine() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend T; + + public: + using difference_type = Difference; +}; + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4820 ) // '3' bytes padding added after data member +#endif +template +class random_iterable { + protected: + random_iterable() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend T; + + public: + constexpr auto cfront() const& -> decltype(auto) { return static_cast(*this).front(); } + constexpr auto cback() const& -> decltype(auto) { return static_cast(*this).back(); } + friend constexpr auto cfront(T const& self) -> decltype(auto) { return self.cfront(); } + friend constexpr auto cback(T const& self) -> decltype(auto) { return self.cback(); } +}; +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +namespace detail { +template +struct random_access_iterator : equality_comparable2 { + protected: + random_access_iterator() = default; // NOLINT(bugprone-crtp-constructor-accessibility) + friend Self; + + public: + using difference_type = Difference; + using value_type = Value; + using pointer = Pointer; + using reference = Reference; + using iterator_category = std::random_access_iterator_tag; + BOOST_MULTI_HD constexpr auto operator*() const -> Reference { return *static_cast(*this); } +}; +} // end namespace detail + +} // end namespace boost::multi + +#undef BOOST_MULTI_HD + +#endif // BOOST_MULTI_DETAIL_OPERATORS_HPP diff --git a/include/multi/memory/pointer_traits.hpp b/include/boost/multi/detail/pointer_traits.hpp similarity index 77% rename from include/multi/memory/pointer_traits.hpp rename to include/boost/multi/detail/pointer_traits.hpp index 57a6e94b4..20978353d 100644 --- a/include/multi/memory/pointer_traits.hpp +++ b/include/boost/multi/detail/pointer_traits.hpp @@ -1,9 +1,9 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa +// Copyright 2020-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#ifndef MULTI_MEMORY_POINTER_TRAITS_HPP_ -#define MULTI_MEMORY_POINTER_TRAITS_HPP_ -#pragma once +#ifndef BOOST_MULTI_DETAIL_POINTER_TRAITS_HPP +#define BOOST_MULTI_DETAIL_POINTER_TRAITS_HPP #include // for size_t #include // for iterator_traits @@ -24,4 +24,4 @@ struct pointer_traits/*, typename Pointer::default_allocator_type>*/ : std::poin }; } // end namespace boost::multi -#endif // MULTI_MEMORY_POINTER_TRAITS_HPP_ +#endif // BOOST_MULTI_DETAIL_POINTER_TRAITS_HPP diff --git a/include/boost/multi/detail/serialization.hpp b/include/boost/multi/detail/serialization.hpp new file mode 100644 index 000000000..59311ca8e --- /dev/null +++ b/include/boost/multi/detail/serialization.hpp @@ -0,0 +1,138 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_SERIALIZATION_HPP_ +#define BOOST_MULTI_DETAIL_SERIALIZATION_HPP_ + +#include // for std::for_each // IWYU pragma: keep // bug in iwyu 0.18 +#include // for size_t, byte +#include // for uint32_t +#include // for next +#include // for enable_if_t, decay_t +#include // for forward + +#if defined(__cpp_lib_byte) && (__cpp_lib_byte >= 201603L ) +using BOOST_MULTI_BYTE = std::byte; +#else +using BOOST_MULTI_BYTE = unsigned char; +#endif + +namespace boost::archive::detail { template class common_iarchive; } // lines 24-24 +namespace boost::archive::detail { template class common_oarchive; } // lines 25-25 + +namespace boost::serialization { struct binary_object; } +namespace boost::serialization { template class array_wrapper; } +namespace boost::serialization { template class nvp; } + +namespace cereal { template struct InputArchive; } +namespace cereal { template struct OutputArchive; } +namespace cereal { template class NameValuePair; } // if you get an error here you many need to #include at some point // IWYU pragma: keep // bug in iwyu 0.18 + +namespace boost { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat +namespace multi { + +template +struct archive_traits { + template + /*inline*/ static auto make_nvp(char const* /*n*/, T&& value) noexcept { return std::forward(value); } // match original boost declaration +}; + +template> && (MA::dimensionality > -1), int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) +auto operator>>(Archive& arxiv, MA&& self) // this is for compatibility with Archive type + -> decltype(arxiv >> static_cast(std::forward(self))) { + return arxiv >> static_cast(std::forward(self)); +} + +template> && (MA::dimensionality > -1), int> = 0> // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,modernize-use-constraints) for C++20 +auto operator<<(Archive& arxiv, MA&& self) // this is for compatibility with Archive type +->decltype(arxiv << static_cast(std::forward(self))) { + return arxiv << static_cast(std::forward(self)); } + +template> && (MA::dimensionality > -1), int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) +auto operator&(Archive& arxiv, MA&& self) // this is for compatibility with Archive type + -> decltype(arxiv & static_cast(std::forward(self))) { + return arxiv & static_cast(std::forward(self)); +} + +template +struct archive_traits, Ar> || std::is_base_of_v, Ar>>> { + template using nvp = boost::serialization::nvp; + template using array_wrapper = boost::serialization::array_wrapper; + template struct binary_object_t { + using type = boost::serialization::binary_object; + }; + + template /*inline*/ static auto make_nvp(char const* name, T& value) noexcept -> nvp const { return nvp{name, value}; } // NOLINT(readability-const-return-type) match original boost declaration + template /*inline*/ static auto make_nvp(char const* name, T&& value) noexcept -> nvp const { return nvp{name, value /*static_cast(std::forward(value))*/}; } // NOLINT(readability-const-return-type,cppcoreguidelines-missing-std-forward) match original boost declaration + + template /*inline*/ static auto make_array(T* first, std::size_t size) noexcept -> array_wrapper const { return array_wrapper{first, size}; } // NOLINT(readability-const-return-type) original boost declaration + template /*inline*/ static auto make_binary_object(BOOST_MULTI_BYTE const* first, std::size_t size) noexcept -> const typename binary_object_t::type { return typename binary_object_t::type(first, size); } // if you get an error here you need to eventually `#include`// NOLINT(readability-const-return-type,clang-diagnostic-ignored-qualifiers) original boost declaration +}; + +template +struct archive_traits< + Ar, + typename std::enable_if_t< + std::is_base_of_v, Ar> || std::is_base_of_v, Ar> || std::is_base_of_v, Ar> || std::is_base_of_v, Ar>>> { + using self_t = archive_traits, Ar> || std::is_base_of_v, Ar> || std::is_base_of_v, Ar> || std::is_base_of_v, Ar>>>; + + // template + // inline static auto make_nvp (char const* name, T const& value) noexcept {return cereal::NameValuePair{name, value};} // if you get an error here you many need to #include at some point // TODO(correaa) replace by cereal::make_nvp from cereal/cereal.hpp + // template + // inline static auto make_nvp (std::string const& name, T&& value) noexcept {return cereal::NameValuePair{name.c_str(), std::forward(value)};} // if you get an error here you many need to #include at some point + template + /*inline*/ static auto make_nvp(char const* name, T&& value) noexcept { return cereal::NameValuePair{name, std::forward(value)}; } // if you get an error here you many need to #include at some point + // template + // inline static auto make_nvp (char const* name, T& value) noexcept {return cereal::NameValuePair{name, value};} // if you get an error here you many need to #include at some point + + template + struct array_wrapper { + T* p_; + std::size_t c_; + + template + void serialize(Archive& arxiv, unsigned int const /*version*/) { + std::for_each( // std::for_each_n is absent in GCC 7 + p_, std::next(p_, c_), + [&arxiv](auto& item) { arxiv& make_nvp("item", item); } + ); + // for(std::size_t i = 0; i != c_; ++i) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm + // auto& item = p_[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + // arxiv & make_nvp("item" , item ); // "item" is the name used by Boost.Serialization XML make_array + // // arxiv & boost::multi::archive_traits::make_nvp("element", element); + // // arxiv & cereal::make_nvp("element", element); + // // arxiv & CEREAL_NVP( element); + // // arxiv & element ; + // } + } + }; + + template + /*inline*/ static auto make_array(T* ptr, std::size_t count) -> array_wrapper { return array_wrapper{ptr, count}; } + + template + /*inline*/ static auto make_nvp(char const* name, array_wrapper&& value) noexcept { return make_nvp(name, /*static_cast&>(std::move(*/ value /*))*/); } // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved) +}; + +} // end namespace multi +} // end namespace boost + +namespace boost { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat + +namespace serialization { + +// workaround for rvalue subarrays +template > > // NOLINT(modernize-use-constraints) for C++20 +inline auto make_nvp(char const* name, T&& value) noexcept -> ::boost::serialization::nvp { // NOLINT(cppcoreguidelines-missing-std-forward) workaround legacy interface + return ::boost::serialization::nvp(name, value); +} + +} // end namespace serialization + +using ::boost::serialization::make_nvp; + +} // end namespace boost + +#endif // BOOST_MULTI_DETAIL_SERIALIZATION_HPP_ diff --git a/include/boost/multi/detail/static_allocator.hpp b/include/boost/multi/detail/static_allocator.hpp new file mode 100644 index 000000000..6babedb6e --- /dev/null +++ b/include/boost/multi/detail/static_allocator.hpp @@ -0,0 +1,144 @@ +// Copyright 2023-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_STATIC_ALLOCATOR_HPP +#define BOOST_MULTI_DETAIL_STATIC_ALLOCATOR_HPP + +#include +#include + +#include +#include +#include +#include + +namespace boost::multi::detail { + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template +class static_allocator { // NOSONAR(cpp:S4963) this allocator has special semantics +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4324) // Warning that the structure is padded due to the below +#endif + +// #if defined(__clang__) +// #pragma clang diagnostic push +// #pragma clang diagnostic ignored "-Wpadded" +// #endif + + BOOST_MULTI_NO_UNIQUE_ADDRESS alignas(T) std::array buffer_; + +// #if defined(__clang__) +// #pragma clang diagnostic pop +// #endif + +#ifdef _MSC_VER + #pragma warning(pop) +#endif + +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4820) // warning C4820: 'boost::multi::detail::static_allocator': '3' bytes padding added after data member 'boost::multi::detail::static_allocator::dirty_' [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\test\allocator.cpp.x.vcxproj] +#endif + bool dirty_ = false; +#ifdef _MSC_VER + #pragma warning(pop) +#endif + + public: + using value_type = T; + using pointer = T*; + + template struct rebind { + using other = static_allocator; + }; + + static constexpr auto max_size() noexcept -> std::size_t { return N; } + + static_allocator() = default; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init) buffer_ is not initialized + + template + explicit static_allocator(static_allocator const& /*other*/) { // NOLINT(hicpp-explicit-conversions,google-explicit-constructor) follow std::allocator // NOSONAR + // static_assert(sizeof(T) == sizeof(TT)); + static_assert(NN == N); + } + + static_allocator(static_allocator const& /*other*/) // std::vector makes a copy right away + // = default; // this copies the internal buffer + {} + + // [[deprecated("don't move dynamic container with static_allocator")]] + static_allocator(static_allocator&& /*other*/) // this is called *by the elements* during move construction of a vector + // = delete; + // {throw std::runtime_error("don't move dynamic container with static_allocator");} // this is called *by the elements* during move construction of a vector + noexcept {} + // noexcept {std::memmove(buffer_.data(), other.buffer_.data(), sizeof(T)*N);} + // noexcept : buffer_{std::move(other.buffer_)} {} + // noexcept = default; + + [[deprecated("don't move dynamic container with static_allocator")]] + auto operator=(static_allocator const& /*other*/) -> static_allocator& = delete; + + [[deprecated("don't move dynamic container with static_allocator")]] auto operator=(static_allocator&& other) -> static_allocator& = delete; + + ~static_allocator() = default; + + auto select_on_container_copy_construction() noexcept -> static_allocator = delete; + // {return static_allocator{};} + + using propagate_on_container_move_assignment = std::false_type; // this forces to call move assignment of the allocator by std::vector + using propagate_on_container_copy_assignment = std::false_type; + using propagate_on_container_swap = std::false_type; + + static constexpr auto capacity() { return N; } + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4068) // bug in MSVC 14.2/14.3 +#endif + BOOST_MULTI_NODISCARD("because otherwise it will generate a memory leak") + auto allocate([[maybe_unused]] std::size_t n) -> pointer { + assert(n <= N); + assert(!dirty_); // do not attempt to resize a vector with static_allocator + // dirty_ = true; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-align" // buffer_ is aligned as T + return reinterpret_cast(&buffer_); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) +#pragma GCC diagnostic pop + } +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + + void deallocate(pointer /*ptr*/, [[maybe_unused]] std::size_t n) { + assert(n <= N); + } + + using is_always_equal = std::true_type; +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template +constexpr auto operator==(static_allocator const& /*a1*/, static_allocator const& /*a2*/) noexcept { return true; } // &a1 == &a2; } +// = delete; + +template +auto operator!=(static_allocator const& /*a1*/, static_allocator const& /*a2*/) noexcept // this is used *by the elements* when resizing a vector +{ return false; } // &a1 != &a2;} +// = delete + +template +[[deprecated("don't swap dynamic container with static_allocator")]] +void swap(static_allocator& a1, static_allocator& a2) noexcept = delete; + +} // end namespace boost::multi::detail +#endif // BOOST_MULTI_DETAIL_STATIC_ALLOCATOR_HPP diff --git a/include/boost/multi/detail/tuple_zip.hpp b/include/boost/multi/detail/tuple_zip.hpp new file mode 100644 index 000000000..910657794 --- /dev/null +++ b/include/boost/multi/detail/tuple_zip.hpp @@ -0,0 +1,565 @@ +// Copyright 2021-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_TUPLE_ZIP_HPP +#define BOOST_MULTI_DETAIL_TUPLE_ZIP_HPP + +#include // for size_t +#include // for deprecated functions // for make_index_sequence, index_sequence, tuple_element, tuple_size, apply, tuple +#include // for declval, decay_t, conditional_t, true_type +#include // for forward, move + +#ifdef __NVCC__ +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4514) // boost::multi::detail::tuple<>::operator <': unreferenced inline function has been removed +#pragma warning(disable : 4623) // default constructor was implicitly defined as deleted +#pragma warning(disable : 4626) // assignment operator was implicitly defined as deleted +#endif + +namespace boost::multi { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat +namespace detail { + +// we need a custom tuple type, so some fundamental types (e.g. iterators) are trivially constructible +template class tuple; // TODO(correaa) consider renaming it to `tpl` + +template<> class tuple<> { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + public: + tuple() = default; + tuple(tuple const&) = default; + + auto operator=(tuple const&) -> tuple& = default; + + BOOST_MULTI_HD constexpr auto operator==(tuple const& /*other*/) const { return true; } + BOOST_MULTI_HD constexpr auto operator!=(tuple const& /*other*/) const { return false; } + + BOOST_MULTI_HD constexpr auto operator<(tuple const& /*other*/) const { return false; } + BOOST_MULTI_HD constexpr auto operator>(tuple const& /*other*/) const { return false; } + + template + BOOST_MULTI_HD constexpr friend auto apply(F&& fn, tuple<> const& /*self*/) -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std::forward(fn)(); + } +}; + +template class tuple : tuple { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + T0 head_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) can be a reference + using head_type = T0; + using tail_type = tuple; + + public: + BOOST_MULTI_HD constexpr auto head() const& -> T0 const& { return head_; } + BOOST_MULTI_HD constexpr auto head() && -> T0&& { return std::move(head_); } + BOOST_MULTI_HD constexpr auto head() & -> T0& { return head_; } + + BOOST_MULTI_HD constexpr auto tail() const& -> tail_type const& { return static_cast(*this); } + BOOST_MULTI_HD constexpr auto tail() && -> decltype(auto) { return static_cast(*this); } + BOOST_MULTI_HD constexpr auto tail() & -> tail_type& { return static_cast(*this); } + + constexpr tuple() = default; + constexpr tuple(tuple const&) = default; + + // this is horrible hack and can produce ODR reported by Circle + // operator std::tuple() & { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + // using std::apply; + // return apply([](auto&&... ts) {return std::tuple{std::forward(ts)...}; }, *this); + // } + // operator std::tuple() && { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + // using std::apply; + // return apply([](auto&&... ts) {return std::tuple{std::forward(ts)...}; }, *this); + // } + // operator std::tuple() const& { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + // using std::apply; + // return apply([](auto&&... ts) {return std::tuple{std::forward(ts)...}; }, *this); + // } + + // TODO(correaa) make conditional explicit constructor depending on the conversions for T0, Ts... + BOOST_MULTI_HD constexpr explicit tuple(T0 head, tuple tail) : tail_type{std::move(tail)}, head_{std::move(head)} {} + // cppcheck-suppress noExplicitConstructor ; allow bracket init in function argument // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr tuple(T0 head, Ts... tail) : tail_type{tail...}, head_{head} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) to allow bracket function calls + + // cppcheck-suppress noExplicitConstructor ; allow bracket init in function argument // NOLINTNEXTLINE(runtime/explicit) + template, int> =0, // NOLINT(modernize-use-constraints,modernize-type-traits) for C++20 + std::enable_if_t =0 // NOLINT(modernize-use-constraints) for C++20 + > + // cppcheck-suppress noExplicitConstructor ; see below + BOOST_MULTI_HD constexpr tuple(TT0 head) : tail_type{}, head_{head} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) to allow bracket function calls + + // cppcheck-suppress noExplicitConstructor ; allow bracket init in function argument // NOLINTNEXTLINE(runtime/explicit) + BOOST_MULTI_HD constexpr explicit tuple(::std::tuple other) : tuple(::std::apply([](auto... es) {return tuple(es...);}, other)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + + constexpr auto operator=(tuple const&) -> tuple& = default; + + template + constexpr auto operator=(tuple const& other) // NOLINT(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) signature used for SFINAE + -> decltype(std::declval() = other.head(), std::declval() = other.tail(), std::declval()) { + head_ = other.head(), tail() = other.tail(); + return *this; + } + + BOOST_MULTI_HD constexpr auto operator==(tuple const& other) const -> bool { + return + head_ == other.head_ + && + tail() == other.tail() + ; + } + BOOST_MULTI_HD constexpr auto operator!=(tuple const& other) const -> bool { return head_ != other.head_ || tail() != other.tail(); } + + BOOST_MULTI_HD constexpr auto operator<(tuple const& other) const { + if(head_ < other.head_) { + return true; + } + if(other.head_ < head_) { + return false; + } + return tail() < other.tail(); + } + BOOST_MULTI_HD constexpr auto operator>(tuple const& other) const { + if(head_ > other.head_) { + return true; + } + if(other.head_ > head_) { + return false; + } + return tail() > other.tail(); + } + + private: + + template + BOOST_MULTI_HD constexpr auto apply_impl_(F&& fn, std::index_sequence /*012*/) const& -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std::forward(fn)(this->get()...); + } + + template + BOOST_MULTI_HD constexpr auto apply_impl_(F&& fn, std::index_sequence /*012*/) & -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std::forward(fn)(this->get()...); + } + + template + BOOST_MULTI_HD constexpr auto apply_impl_(F&& fn, std::index_sequence /*012*/) && -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std::forward(fn)(std::move(*this).template get()...); + } + + public: + template + BOOST_MULTI_HD constexpr auto apply(F&& fn) const& -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return apply_impl_(std::forward(fn), std::make_index_sequence{}); + } + template + BOOST_MULTI_HD constexpr auto apply(F&& fn) & -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return apply_impl_(std::forward(fn), std::make_index_sequence{}); + } + template + BOOST_MULTI_HD constexpr auto apply(F&& fn) && -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std::move(*this).apply_impl_(std::forward(fn), std::make_index_sequence{}); + } + + template + friend BOOST_MULTI_HD constexpr auto apply(F&& fn, tuple const& self) -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return self.apply(std::forward(fn)); + } + + template + friend BOOST_MULTI_HD constexpr auto apply(F&& fn, tuple & self) -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return self.apply(std::forward(fn)); + } + + template + friend BOOST_MULTI_HD constexpr auto apply(F&& fn, tuple && self) -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std::move(self).apply(std::forward(fn)); + } + + private: + template struct priority : std::conditional_t> {}; + + template + constexpr auto at_aux_(priority<0> /*prio*/, Index idx) const + -> decltype(ht_tuple(std::declval(), std::declval()[idx])) { + return ht_tuple(head(), tail()[idx]); + } + + template + constexpr auto at_aux_(priority<1> /*prio*/, Index idx) const + -> decltype(ht_tuple(std::declval()[idx], std::declval())) { + return ht_tuple(head()[idx], tail()); + } + + public: + template + constexpr auto operator[](Index idx) const + -> decltype(std::declval const&>().at_aux_(priority<1>{}, idx)) { + return this->at_aux_(priority<1>{}, idx); + } + + template =0> // NOLINT(modernize-use-constraints) for C++20 + BOOST_MULTI_HD constexpr auto get() const& -> T0 const& { // NOLINT(readability-identifier-length) std naming + return head(); + } + + template =0> // NOLINT(modernize-use-constraints) for C++20 + BOOST_MULTI_HD constexpr auto get() const& -> auto const& { // NOLINT(readability-identifier-length) std naming + return this->tail().template get(); // this-> for msvc 19.14 compilation + } + + +#ifdef __NVCC__ // in place of global -Xcudafe \"--diag_suppress=implicit_return_from_non_void_function\" + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function +#endif + +#ifdef __NVCOMPILER +#pragma diagnostic push +#pragma diag_suppress = implicit_return_from_non_void_function +#endif + +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type" +#endif + + template + BOOST_MULTI_HD constexpr auto get() & -> decltype(auto) { // NOLINT(readability-identifier-length) std naming + if constexpr(N == 0) { + return head(); + } else { + return tail().template get(); + } + } + + template + BOOST_MULTI_HD constexpr auto get() && -> decltype(auto) { // NOLINT(readability-identifier-length) std naming + if constexpr(N == 0) { + return std::move(*this).head(); + } else { + return std::move(*this).tail().template get(); + } + } +}; + +#ifdef __NVCC__ +#pragma nv_diagnostic pop +#elif defined(__NVCOMPILER) +#pragma diagnostic pop +#endif + +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __INTEL_COMPILER // this instance is necessary due to a bug in intel compiler icpc +// TODO(correaa) : this class can be collapsed with the general case with [[no_unique_address]] in C++20 +template class tuple { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + T0 head_; + tuple<> tail_; + + public: + constexpr auto head() const& -> T0 const& { return head_; } + constexpr auto head() && -> T0&& { return std::move(head_); } + constexpr auto head() & -> T0& { return head_; } + + constexpr auto tail() const& -> tuple<> const& { return tail_; } + constexpr auto tail() && -> tuple<>&& { return std::move(tail_); } + constexpr auto tail() & -> tuple<>& { return tail_; } + + constexpr tuple() = default; + constexpr tuple(tuple const&) = default; // cppcheck-suppress noExplicitConstructor ; workaround cppcheck 2.11 + + // cppcheck-suppress noExplicitConstructor ; allow bracket init in function argument // NOLINTNEXTLINE(runtime/explicit) + constexpr tuple(T0 t0, tuple<> sub) : head_{std::move(t0)}, tail_{sub} {} + constexpr explicit tuple(T0 t0) : head_{std::move(t0)}, tail_{} {} + + constexpr auto operator=(tuple const& other) -> tuple& = default; + + constexpr auto operator==(tuple const& other) const { return head_ == other.head_; } + constexpr auto operator!=(tuple const& other) const { return head_ != other.head_; } + + constexpr auto operator<(tuple const& other) const { return head_ < other.head_; } + constexpr auto operator>(tuple const& other) const { return head_ > other.head_; } +}; +#endif + +#if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703L) +template tuple(T0, tuple) -> tuple; +#endif + +template constexpr auto mk_tuple(T0 head, Ts... tail) { + return tuple(std::move(head), std::move(tail)...); +} + +template constexpr auto tie(T0& head, Ts&... tail) { + return tuple(head, tail...); +} + +template BOOST_MULTI_HD constexpr auto ht_tuple(T0 head, tuple tail) +-> tuple { + return tuple(std::move(head), std::move(tail)); +} + +template struct tuple_prepend; + +template +struct tuple_prepend> { + using type = tuple; +}; + +template +using tuple_prepend_t = typename tuple_prepend::type; + +template +constexpr auto head(tuple const& t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming + return t.head(); +} + +template +constexpr auto head(tuple&& t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming + return std::move(t).head(); +} + +template +constexpr auto head(tuple& t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming + return t.head(); +} + +template +BOOST_MULTI_HD constexpr auto tail(tuple const& t) -> decltype(t.tail()) { return t.tail(); } // NOLINT(readability-identifier-length) std naming + +template +BOOST_MULTI_HD constexpr auto tail(tuple&& t) -> decltype(std::move(t).tail()) { return std::move(t).tail(); } // NOLINT(readability-identifier-length) std naming + +template +BOOST_MULTI_HD constexpr auto tail(tuple& t) -> decltype(t.tail()) { return t.tail(); } // NOLINT(readability-identifier-length) std naming + +#ifdef __NVCC__ // in place of global -Xcudafe \"--diag_suppress=implicit_return_from_non_void_function\" + #pragma nv_diagnostic push + #pragma nv_diag_suppress = implicit_return_from_non_void_function +#endif + +#ifdef __NVCOMPILER +#pragma diagnostic push +#pragma diag_suppress = implicit_return_from_non_void_function +#endif + +#ifndef _MSC_VER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreturn-type" +#endif + +template +BOOST_MULTI_HD constexpr auto get(tuple const& t) -> auto const& { // NOLINT(readability-identifier-length) std naming + if constexpr(N == 0) { + return t.head(); + } else { + using std::get; + return get(t.tail()); + } +} + +template +BOOST_MULTI_HD constexpr auto get(tuple& tup) -> auto& { + if constexpr(N == 0) { + return tup.head(); + } else { + return get(tup.tail()); + } +} + +template +BOOST_MULTI_HD constexpr auto get(tuple&& tup) -> auto&& { + if constexpr(N == 0) { + return std::move(std::move(tup)).head(); + } else { + return get(std::move(std::move(tup).tail())); + } +} + +#ifdef __NVCC__ +#pragma nv_diagnostic pop +#elif defined(__NVCOMPILER) +#pragma diagnostic pop +#endif + +#ifndef _MSC_VER +#pragma GCC diagnostic pop +#endif + +} // end namespace detail +} // end namespace boost::multi + +// Some versions of Clang throw warnings that stl uses class std::tuple_size instead +// of struct std::tuple_size like it should be +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmismatched-tags" +#endif + +template +struct std::tuple_size> : std::integral_constant { // NOLINT(cert-dcl58-cpp) to have structured bindings + // // cppcheck-suppress unusedStructMember + // static constexpr std::size_t value = sizeof...(Ts); +}; + +template<> +struct std::tuple_element<0, boost::multi::detail::tuple<>> { // NOLINT(cert-dcl58-cpp) to have structured bindings + using type = void; +}; + +template +struct std::tuple_element<0, boost::multi::detail::tuple> { // NOLINT(cert-dcl58-cpp) to have structured bindings + using type = T0; +}; + +template +struct std::tuple_element<0, boost::multi::detail::tuple> { // NOLINT(cert-dcl58-cpp) to have structured bindings + using type = T0; +}; + +template +struct std::tuple_element> { // NOLINT(cert-dcl58-cpp) to have structured bindings + using type = tuple_element_t>; +}; + +template +BOOST_MULTI_HD constexpr auto std_apply_timpl(F&& fn, Tuple&& tp, std::index_sequence /*012*/) -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + (void)tp; // fix "error #827: parameter "t" was never referenced" in NVC++ and "error #869: parameter "t" was never referenced" in oneAPI-ICPC + return std::forward(fn)(boost::multi::detail::get(std::forward(tp))...); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) use forward_as? +} + +namespace std { // NOLINT(cert-dcl58-cpp) to implement structured bindings + +template +BOOST_MULTI_HD constexpr auto apply(F&& fn, boost::multi::detail::tuple const& tp) -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std_apply_timpl( + std::forward(fn), tp, + std::make_index_sequence{} + ); +} + +template +BOOST_MULTI_HD constexpr auto apply(F&& fn, boost::multi::detail::tuple& tp) -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std_apply_timpl( + std::forward(fn), tp, + std::make_index_sequence{} + ); +} + +template +BOOST_MULTI_HD constexpr auto apply(F&& fn, boost::multi::detail::tuple&& tp) -> decltype(auto) { // NOLINT(cert-dcl58-cpp) normal idiom to defined tuple get + return std_apply_timpl( + std::forward(fn), std::move(tp), + std::make_index_sequence{} + ); +} + +} // end namespace std + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace boost::multi { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat +namespace detail { + +template +constexpr auto tuple_zip_impl(Tuple1&& tup1, Tuple2&& tup2, std::index_sequence /*012*/) { + using boost::multi::detail::get; + return boost::multi::detail::mk_tuple( + boost::multi::detail::mk_tuple( + get(std::forward(tup1)), + get(std::forward(tup2)) + )... + ); +} + +template +constexpr auto tuple_zip_impl(Tuple1&& tup1, Tuple2&& tup2, Tuple3&& tup3, std::index_sequence /*012*/) { + using boost::multi::detail::get; + return boost::multi::detail::mk_tuple( + boost::multi::detail::mk_tuple( + get(std::forward(tup1)), + get(std::forward(tup2)), + get(std::forward(tup3)) + )... + ); +} + +template +constexpr auto tuple_zip_impl(Tuple1&& tup1, Tuple2&& tup2, Tuple3&& tup3, Tuple4&& tup4, std::index_sequence /*012*/) { + using boost::multi::detail::get; + return boost::multi::detail::mk_tuple( + boost::multi::detail::mk_tuple( + get(std::forward(tup1)), // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) use forward_as + get(std::forward(tup2)), // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) use forward_as + get(std::forward(tup3)), // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) use forward_as + get(std::forward(tup4)) // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) use forward_as + )... + ); +} + +template +constexpr auto tuple_zip_impl(Tuple1&& tup1, Tuple2&& tup2, Tuple3&& tup3, Tuple4&& tup4, Tuple5&& tup5, std::index_sequence /*012*/) { + using boost::multi::detail::get; + return boost::multi::detail::mk_tuple( + boost::multi::detail::mk_tuple( + get(std::forward(tup1)), + get(std::forward(tup2)), + get(std::forward(tup3)), + get(std::forward(tup4)), + get(std::forward(tup5)) + )... + ); +} + +template +constexpr auto tuple_zip(T1&& tup1, T2&& tup2) { + return detail::tuple_zip_impl( + std::forward(tup1), std::forward(tup2), + std::make_index_sequence>>() + ); +} + +template +constexpr auto tuple_zip(T1&& tup1, T2&& tup2, T3&& tup3) { + return detail::tuple_zip_impl( + std::forward(tup1), std::forward(tup2), std::forward(tup3), + std::make_index_sequence>>() + ); +} + +template +constexpr auto tuple_zip(T1&& tup1, T2&& tup2, T3&& tup3, T4&& tup4) { + return detail::tuple_zip_impl( + std::forward(tup1), std::forward(tup2), std::forward(tup3), std::forward(tup4), + std::make_index_sequence>>() + ); +} + +template +constexpr auto tuple_zip(T1&& tup1, T2&& tup2, T3&& tup3, T4&& tup4, T5&& tup5) { + return detail::tuple_zip_impl( + std::forward(tup1), std::forward(tup2), std::forward(tup3), std::forward(tup4), std::forward(tup5), + std::make_index_sequence>>() + ); +} + +} // end namespace detail + +using detail::tie; + +} // end namespace boost::multi + +#undef BOOST_MULTI_HD + +#endif diff --git a/include/boost/multi/detail/types.hpp b/include/boost/multi/detail/types.hpp new file mode 100644 index 000000000..553c33fb7 --- /dev/null +++ b/include/boost/multi/detail/types.hpp @@ -0,0 +1,23 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_TYPES_HPP +#define BOOST_MULTI_DETAIL_TYPES_HPP + +#include // for std::size_t +#include // for make_signed_t + +namespace boost::multi { + +using size_t = std::make_signed_t; +using size_type = std::make_signed_t; + +using index = std::make_signed_t; +using difference_type = std::make_signed_t; + +using dimensionality_t = index; +using dimensionality_type = dimensionality_t; + +} // end namespace boost::multi +#endif // BOOST_MULTI_DETAIL_TYPES_HPP diff --git a/include/boost/multi/detail/what.hpp b/include/boost/multi/detail/what.hpp new file mode 100644 index 000000000..17d100077 --- /dev/null +++ b/include/boost/multi/detail/what.hpp @@ -0,0 +1,16 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_DETAIL_WHAT_HPP +#define BOOST_MULTI_DETAIL_WHAT_HPP + +namespace boost::multi::detail { + template auto what() -> std::tuple = delete; + template auto what(Ts&&...) -> std::tuple = delete; // NOLINT(cppcoreguidelines-missing-std-forward) + + template auto what_value() -> std::integral_constant = delete; + template struct what_value_t; +} // namespace boost::multi::detail + +#endif // BOOST_MULTI_DETAIL_WHAT_HPP diff --git a/include/boost/multi/utility.hpp b/include/boost/multi/utility.hpp new file mode 100644 index 000000000..b3c306c34 --- /dev/null +++ b/include/boost/multi/utility.hpp @@ -0,0 +1,658 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_MULTI_UTILITY_HPP +#define BOOST_MULTI_UTILITY_HPP + +#include // IWYU pragma: export +#include + +#include // for std::invoke +#include // for std::size (in c++17) +#include // for allocator<> +#include // for std::invoke_result + +#ifdef __NVCC__ +#define BOOST_MULTI_HD __host__ __device__ +#else +#define BOOST_MULTI_HD +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4626) // 'boost::multi::transform_ptr,main::complex *,std::complex>': assignment operator was implicitly defined as deleted [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\test\element_transformed.cpp.x.vcxproj] +#endif + +namespace boost::multi { + +struct uninitialized_elements_t { + explicit uninitialized_elements_t() = default; +}; + +inline constexpr uninitialized_elements_t uninitialized_elements{}; + +template +struct move_ptr : private std::move_iterator { + using difference_type = typename std::iterator_traits>::difference_type; + using value_type = typename std::iterator_traits>::value_type; + using pointer = Ptr; + using reference = typename std::move_iterator::reference; + using iterator_category = typename std::iterator_traits>::iterator_category; + + template using rebind = std::conditional_t< + std::is_const_v, + typename std::pointer_traits::template rebind, + move_ptr>; + + using std::move_iterator::move_iterator; + + BOOST_MULTI_HD constexpr /**/ operator Ptr() const { return std::move_iterator::base(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR(cpp:S1709) decay to lvalue should be easy + + BOOST_MULTI_HD constexpr auto operator+=(difference_type n) -> move_ptr& { + static_cast&>(*this) += n; + return *this; + } + + BOOST_MULTI_HD constexpr auto operator-=(difference_type n) -> move_ptr& { + static_cast&>(*this) -= n; + return *this; + } + + BOOST_MULTI_HD constexpr auto operator+(difference_type n) const -> move_ptr { + move_ptr ret{*this}; + ret += n; + return ret; + } + BOOST_MULTI_HD constexpr auto operator-(difference_type n) const -> move_ptr { + move_ptr ret{*this}; + ret -= n; + return ret; + } + + BOOST_MULTI_HD constexpr auto operator-(move_ptr const& other) const -> difference_type { return static_cast const&>(*this) - static_cast const&>(other); } + + BOOST_MULTI_HD constexpr auto operator<(move_ptr const& other) const -> bool { return static_cast const&>(*this) < static_cast const&>(other); } + + constexpr auto operator*() const -> decltype(auto) { return *static_cast const&>(*this); } + BOOST_MULTI_HD constexpr auto operator[](difference_type n) const -> decltype(auto) { return *((*this) + n); } + + BOOST_MULTI_HD constexpr auto operator==(move_ptr const& other) const -> bool { return static_cast const&>(*this) == static_cast const&>(other); } + BOOST_MULTI_HD constexpr auto operator!=(move_ptr const& other) const -> bool { return static_cast const&>(*this) != static_cast const&>(other); } +}; + +template struct ref_add_const { + using type = T const; +}; // this is not the same as std::add_const + +template struct ref_add_const { + using type = T const; +}; +template struct ref_add_const { + using type = T const&; +}; +template struct ref_add_const { + using type = T const&; +}; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +template::reference>> +struct transform_ptr { + using difference_type = typename std::iterator_traits::difference_type; + using value_type = std::decay_t; // typename std::iterator_traits>::value_type; + using pointer = Ptr; + using reference = Ref; + using iterator_category = typename std::iterator_traits::iterator_category; + + template using rebind = + transform_ptr< + std::remove_cv_t, + UF, Ptr, + std::conditional_t< + std::is_const_v, + typename ref_add_const::type, + Ref> + // typename std::conditional< + // std::is_const_v, + // typename ref_add_const::type, + // Ref + // >::type + >; + +#if defined(__GNUC__) && (__GNUC__ < 9) + constexpr explicit transform_ptr(std::nullptr_t nil) : p_{nil} /*, f_{}*/ {} // seems to be necessary for gcc 7 +#endif + + constexpr transform_ptr(pointer ptr, UF fun) : p_{ptr}, f_(std::move(fun)) {} + + template(std::declval

()))* = nullptr> + // cppcheck-suppress noExplicitConstructor + constexpr /*mplc*/ transform_ptr(Other const& other) : p_{other.p_}, f_{other.f_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR(cpp:S1709) + + template(std::declval

()))* = nullptr> + constexpr explicit transform_ptr(Other const& other) : p_{other.p_}, f_{other.f_} {} + + // constexpr auto functor() const -> UF {return f_;} + constexpr auto base() const -> Ptr const& { return p_; } + constexpr auto operator*() const -> reference { // NOLINT(readability-const-return-type) in case synthesis reference is a `T const` + // invoke allows for example to use .transformed( &member) instead of .transformed( std::mem_fn(&member) ) + return std::invoke(f_, *p_); // NOLINT(readability-const-return-type) in case synthesis reference is a `T const` + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + constexpr auto operator+=(difference_type n) -> transform_ptr& { + p_ += n; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + constexpr auto operator-=(difference_type n) -> transform_ptr& { + p_ -= n; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + constexpr auto operator+(difference_type n) const -> transform_ptr { return transform_ptr{*this} += n; } + constexpr auto operator-(difference_type n) const -> transform_ptr { return transform_ptr{*this} -= n; } + + constexpr auto friend operator+(difference_type n, transform_ptr const& self) { return self + n; } + + constexpr auto operator-(transform_ptr const& other) const -> difference_type { return p_ - other.p_; } + + constexpr auto operator[](difference_type n) const -> reference { return *((*this) + n); } // NOLINT(readability-const-return-type) transformed_view might return by const value. + + constexpr auto operator==(transform_ptr const& other) const -> bool { return p_ == other.p_; } + constexpr auto operator!=(transform_ptr const& other) const -> bool { return p_ != other.p_; } + + constexpr auto operator==(std::nullptr_t const& nil) const -> bool { return p_ == nil; } + constexpr auto operator!=(std::nullptr_t const& nil) const -> bool { return p_ != nil; } + + constexpr auto operator<(transform_ptr const& other) const -> bool { return p_ < other.p_; } + + private: + Ptr p_; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // '7' bytes padding added after data member 'boost::multi::transform_ptr,main::complex *,std::complex>::f_' +#pragma warning(disable : 4371) // layout of class may have changed from a previous version of the compiler due to better packing of member 'boost::multi::transform_ptr::f_' +#endif + BOOST_MULTI_NO_UNIQUE_ADDRESS + UF f_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) technically this type can be const +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template friend struct transform_ptr; +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template +struct array_traits; + +template +struct array_traits { + using reference = typename Array::reference; + using element = typename Array::element; + using element_ptr = typename Array::element_ptr; + using decay_type = typename Array::decay_type; + using default_allocator_type = typename Array::default_allocator_type; +}; + +template +auto has_rank_aux(T const&) -> std::true_type; +inline auto has_rank_aux(...) -> std::false_type; + +template struct has_rank : decltype(has_rank_aux(std::declval())){}; + +template struct rank; + +template().move())> +auto has_member_move_aux(T const&) -> std::true_type; +inline auto has_member_move_aux(...) -> std::false_type; + +template struct has_member_move : decltype(has_member_move_aux(std::declval())){}; + +template{}>> +constexpr auto rank_aux(T const&) -> typename T::rank; + +template::value>> +constexpr auto rank_aux(T const&) -> std::integral_constant>; + +template struct rank : decltype(rank_aux(std::declval())){}; + +template{}, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) special sfinae trick +constexpr auto stride(Pointer /*ptr*/) -> std::ptrdiff_t { return 1; } + +template{}, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) special sfinae trick +constexpr auto base(Pointer ptr) -> Pointer { return ptr; } + +template +constexpr auto reinterpret_pointer_cast(U* other) // name taken from thrust::reinterpret_pointer_cast, which is difference from std::reinterpret_pointer_cast(std::shared_ptr) + -> decltype(reinterpret_cast(other)) { return reinterpret_cast(other); } // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : unavoidalbe implementation? + +template +constexpr auto size(T const (& /*array*/)[N]) noexcept { return static_cast(N); } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + +template +auto has_get_allocator_aux(T const&) -> std::true_type; +inline auto has_get_allocator_aux(...) -> std::false_type; + +template +constexpr auto get_allocator(T (& /*array*/)[N]) noexcept -> std::allocator>> { return {}; } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + +template +constexpr auto get_allocator(T* const& /*t*/) + -> decltype(std::allocator::value_type>{}) { + return std::allocator::value_type>{}; +} + +template +constexpr auto default_allocator_of(T* /*unused*/) { + return std::allocator::value_type>{}; +} + +template +constexpr auto to_address(T* const& ptr) + -> decltype(ptr) { + return ptr; +} + +template +auto has_get_allocator_aux(T const& cont) -> decltype(cont.get_allocator(), std::true_type{}); + +template struct has_get_allocator : decltype(has_get_allocator_aux(std::declval())){}; + +// template // std::common_type_t> +// auto common(T1 const& val1, T2 const& val2) -> Ret { +// return val1 == val2? +// val1: +// Ret{} +// ; +// } + +template +auto has_num_elements_aux(T const& /*array*/) -> decltype(std::declval().num_elements() + 1, std::true_type{}); +inline auto has_num_elements_aux(...) -> decltype(std::false_type{}); +template struct has_num_elements : decltype(has_num_elements_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +template{}>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +constexpr auto num_elements(A const& arr) + -> std::make_signed_t { + return static_cast>(arr.num_elements()); +} + +template +auto has_size_aux(T const& cont) -> decltype(std::size(cont), std::true_type{}); +inline auto has_size_aux(...) -> decltype(std::false_type{}); +template struct has_size : decltype(has_size_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +template +auto has_data_elements_aux(T&& array) -> decltype(array.data_elements() + 1, std::true_type{}); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) why +1? +inline auto has_data_elements_aux(...) -> decltype(std::false_type{}); +template struct has_data_elements : decltype(has_data_elements_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +template +auto has_base_aux(T&& array) -> decltype(array.base() + 1, std::true_type{}); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) why +1? +inline auto has_base_aux(...) -> decltype(std::false_type{}); +template struct has_base : decltype(has_base_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +namespace detail { +template +auto has_data_aux(T&& cont) -> decltype(cont.data_elements() + 1, std::true_type{}); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) why +1? +inline auto has_data_aux(...) -> decltype(std::false_type{}); +} // end namespace detail +template struct has_data : decltype(detail::has_data_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +template>::value && !has_data_elements>::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto data_elements(Array& arr) { return std::data(arr); } + +template>::value && !has_data_elements>::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto data_elements(Array const& arr) { return std::data(arr); } // .data();} + +template::value && has_size::value && has_data::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 +constexpr auto num_elements(A const& arr) -> std::make_signed_t { + return static_cast>(std::size(arr)); // (arr.size()); +} + +template{}, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +constexpr auto data_elements(A const& arr) + -> decltype(arr.data_elements()) { + return arr.data_elements(); +} + +template> && !has_data_elements>::value && !has_data>::value, int> = 0> // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,modernize-use-constraints) for C++20 +constexpr auto data_elements(T& value) -> decltype(&value) { return &value; } + +template struct num_elements_t : std::integral_constant {}; + +template struct num_elements_t : std::integral_constant{})> {}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + +template struct num_elements_t : num_elements_t {}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + +template +constexpr auto num_elements(T const (& /*array*/)[N]) noexcept { return num_elements_t{}; } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + +template().data())>>, class = decltype(Vector{}.resize(1))> +auto data_elements(Vector const& vec) + -> decltype(vec.data()) { + return vec.data(); +} + +template +constexpr auto stride(T const (& /*array*/)[N]) noexcept -> std::ptrdiff_t { return num_elements_t{}; } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility + +template +constexpr auto is_compact(T const (& /*t*/)[N]) noexcept -> bool { return true; } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility + +template +constexpr auto offset(T const (& /*t*/)[N]) noexcept -> std::ptrdiff_t { return 0; } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility + +template +[[deprecated("use data_elements instead")]] // this name is bad because when the element belongs to std:: then std::data is picked up by ADL and the +constexpr auto +data(T (&array)[N]) noexcept { return data(array[0]); } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + +template +constexpr auto data_elements(T (&array)[N]) noexcept { return data_elements(array[0]); } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + +template +auto has_dimensionality_aux(T const& /*array*/) -> decltype(T::rank_v, std::true_type{}); +inline auto has_dimensionality_aux(...) -> decltype(std::false_type{}); +template struct has_dimensionality : decltype(has_dimensionality_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +template{}, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +constexpr auto dimensionality(Container const& /*container*/) + -> std::decay_t { + return Container::rank_v; +} + +template +auto has_dimensionaliy_member_aux(T const& /*array*/) -> decltype(static_cast(static_cast(T::rank_v)), std::true_type{}); +inline auto has_dimensionaliy_member_aux(...) -> decltype(std::false_type{}); +template struct has_dimensionality_member : decltype(has_dimensionaliy_member_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +template{}>> // NOLINT(modernize-use-constraints) TODO(correaa) +constexpr auto dimensionality(T const& /*, void* = nullptr*/) { return 0; } + +template +constexpr auto dimensionality(T const (&array)[N]) { return 1 + dimensionality(array[0]); } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + +template().sizes())> +constexpr auto sizes(T const& arr) noexcept -> Ret { return arr.sizes(); } + +template::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 +constexpr auto sizes(T const& /*unused*/) noexcept { return tuple<>{}; } + +template +constexpr auto sizes(T const (&array)[N]) noexcept { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility + // using std::size; // this line needs c++17 + return multi::detail::ht_tuple(multi::size(array), multi::sizes(array[0])); +} + +template +constexpr auto base(T (&array)[N]) noexcept { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility + return data_elements(array); +} + +template +constexpr auto base(T (*&array)[N]) noexcept { return base(*array); } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility + +template>> // NOLINT(modernize-use-constraints) TODO(correaa) +constexpr auto base(T const* ptr) noexcept { return ptr; } + +template>> // NOLINT(modernize-use-constraints) TODO(correaa) +constexpr auto base(T* ptr) noexcept { return ptr; } + +template +constexpr auto corigin(T const& value) { return &value; } + +template +constexpr auto corigin(T const (&array)[N]) noexcept { return corigin(array[0]); } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility + +template().extension())> +auto has_extension_aux(T const&) -> std::true_type; +inline auto has_extension_aux(...) -> std::false_type; +template struct has_extension : decltype(has_extension_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +template::value>> // NOLINT(modernize-use-constraints) TODO(correaa) +auto extension(Container const& cont) // TODO(correaa) consider "extent" + -> decltype(multi::extension_t>(0, static_cast>(size(cont)))) { + return multi::extension_t>(0, static_cast>(size(cont))); +} + +template::value, int> = 0> // NOLINT(modernize-use-constraints) for C++20 +auto extensions(Container const& cont) { + if constexpr(Rank == 0) { + return multi::extensions_t<0>{}; + } else { + using std::size; + return multi::extension_t>(0, static_cast>(size(cont))) * extensions(cont.front()); + } +} + +// template().shape())> +// auto has_shape_aux(T const&) -> std::true_type; +// inline auto has_shape_aux(... ) -> std::false_type; + +template struct has_shape : decltype(has_shape_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) trick + +template().elements())> +auto has_elements_aux(T const&) -> std::true_type; +inline auto has_elements_aux(...) -> std::false_type; + +template struct has_elements : decltype(has_elements_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) trick + +template().extensions())> +auto has_extensions_aux(T const&) -> std::true_type; +inline auto has_extensions_aux(...) -> std::false_type; + +template struct has_extensions : decltype(has_extensions_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) trick + +template::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +[[nodiscard]] constexpr auto extensions(T const& array) -> std::decay_\ +t { + return array.extensions(); +} + +template +constexpr auto extensions_aux2(BoostMultiArray const& arr, std::index_sequence /*012*/) { + return boost::multi::extensions_t( + boost::multi::iextension{static_cast(arr.index_bases()[I]), static_cast(arr.index_bases()[I]) + static_cast(arr.shape()[I])}... // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + ); +} + +template::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 +[[nodiscard]] auto extensions_of(T const& array) { + if constexpr(std::is_convertible_v) { + return boost::multi::extensions_t<0>{}; + } + if constexpr(std::is_convertible_v) { + return boost::multi::extensions_t<1>{array.extension()}; + } +} + +template +auto transposed(Arr2D&& arr) + -> decltype(std::forward(arr).transposed()) { + return std::forward(arr).transposed(); +} + +// template::value && !has_extensions::value, int> =0> +// constexpr auto extensions(BoostMultiArray const& array) { +// return extensions_aux2(array, std::make_index_sequence{}); +// } + +template::value /*&& !has_shape::value*/, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 +constexpr auto extensions(T const& /*unused*/) -> multi::layout_t<0>::extensions_type { return {}; } + +template +constexpr auto extensions(T (&array)[N]) { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility + return index_extension{N} * extensions(array[0]); +} + +template +struct extensions_aux { + template + static auto call(T const& array) { + return array.extension() * extensions(array); + // return tuple_cat(std::make_tuple(array.extension()), extensions(array)); + } +}; + +template<> struct extensions_aux<0> { + template static auto call(T const& /*unused*/) { return multi::extensions_t<0>{}; } // std::make_tuple();} +}; + +template::value, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 +auto extensions(T const& array) { + return extensions_aux::call(array); +} + +template struct extensions_t_aux; + +template auto extensions_me(T2 const& array) { + return extensions_t_aux::call(array); +} + +template struct extension_t_aux { + static auto call(T1 const& /*unused*/) { return std::make_tuple(); } + template + static auto call(T2 const& array) { return tuple_cat(std::make_tuple(array.extension()), extensions_me(*begin(array))); } +}; + +template().layout())> +auto has_layout_member_aux(T const&) -> std::true_type; +inline auto has_layout_member_aux(...) -> std::false_type; + +template +struct has_layout_member : decltype(has_layout_member_aux(std::declval())){}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + +template{}>> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 +auto layout(T const& array) + -> decltype(array.layout()) { + return array.layout(); +} + +template{}>> // NOLINT(modernize-use-constraints) TODO(correaa) in C++20 +auto layout(T const& /*unused*/) -> layout_t<0> { return {}; } + +template +constexpr auto layout(T (&array)[N]) { return multi::layout_t>{multi::extensions(array)}; } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): for backward compatibility + +template +constexpr auto strides(T (&array)[N]) { return layout(array).strides(); } // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): for backward compatibility + +template +struct array_traits> { + static constexpr auto dimensionality() -> dimensionality_type { return 1; } + + using reference = T&; + using value_type = std::decay_t; + using pointer = T*; + using element = value_type; + using element_ptr = pointer; + using decay_type = std::array; +}; + +template +struct array_traits, N>> { + static constexpr auto dimensionality() -> dimensionality_type { return 1 + array_traits>::dimensionality(); } + + using reference = std::array&; + using value_type = std::array, M>; + using pointer = std::array*; + using element = typename array_traits>::element; + using element_ptr = typename array_traits>::element; + using decay_type = std::array; +}; + +template constexpr auto data_elements(std::array& arr) noexcept { return arr.data(); } +template constexpr auto data_elements(std::array, N>& arr) noexcept { return data_elements(arr[0]); } + +template constexpr auto data_elements(std::array const& arr) noexcept { return arr.data(); } +template constexpr auto data_elements(std::array, N> const& arr) noexcept { return data_elements(arr[0]); } + +template constexpr auto data_elements(std::array&& arr) noexcept { return std::move(arr).data(); } + +template +constexpr auto data_elements(std::array, N>&& arr) noexcept { return data_elements(std::move(arr)[0]); } + +template constexpr auto num_elements(std::array const& /*unused*/) noexcept + -> std::ptrdiff_t { return N; } + +template +constexpr auto num_elements(std::array, N> const& arr) + -> std::ptrdiff_t { return static_cast(N) * num_elements(arr[0]); } + +template +constexpr auto dimensionality(std::array const& /*unused*/) -> boost::multi::dimensionality_type { return 1; } + +template +constexpr auto dimensionality(std::array, N> const& arr) -> boost::multi::dimensionality_type { + return 1 + dimensionality(arr[0]); +} + +template +constexpr auto extensions(std::array const& /*arr*/) { + return multi::extensions_t<1>{multi::index_extension(0, N)}; +} + +template +auto extensions(std::array, M> const& arr) { + return multi::iextension{M} * extensions(arr[0]); +} + +template +constexpr auto stride(std::array const& /*arr*/) { + return static_cast(1U); // multi::stride_type? +} + +template +constexpr auto stride(std::array, M> const& arr) { + return num_elements(arr[0]); +} + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wlarge-by-value-copy" // TODO(correaa) use checked span +#endif + +template +constexpr auto layout(std::array const& arr) { + return multi::layout_t>::dimensionality()>{multi::extensions(arr)}; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace detail { +inline auto valid_mull(int age) -> bool { + return age >= 21; +} +} // end namespace detail + +} // end namespace boost::multi + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#undef BOOST_MULTI_HD + +#endif // BOOST_MULTI_UTILITY_HPP diff --git a/include/multi b/include/multi new file mode 120000 index 000000000..f91c3d23c --- /dev/null +++ b/include/multi @@ -0,0 +1 @@ +boost/multi \ No newline at end of file diff --git a/include/multi/adaptors/blas.hpp b/include/multi/adaptors/blas.hpp deleted file mode 100644 index 044b461c7..000000000 --- a/include/multi/adaptors/blas.hpp +++ /dev/null @@ -1,24 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_HPP -#define MULTI_ADAPTORS_BLAS_HPP -#pragma once - -#include "../adaptors/blas/asum.hpp" -#include "../adaptors/blas/axpy.hpp" -#include "../adaptors/blas/copy.hpp" -#include "../adaptors/blas/dot.hpp" -#include "../adaptors/blas/gemm.hpp" -#include "../adaptors/blas/gemv.hpp" -//#include "../adaptors/blas/ger.hpp" -#include "../adaptors/blas/herk.hpp" -#include "../adaptors/blas/iamax.hpp" -#include "../adaptors/blas/nrm2.hpp" -#include "../adaptors/blas/scal.hpp" -#include "../adaptors/blas/swap.hpp" -#include "../adaptors/blas/syrk.hpp" -#include "../adaptors/blas/trsm.hpp" - -#endif - diff --git a/include/multi/adaptors/blas/CMakeLists.txt b/include/multi/adaptors/blas/CMakeLists.txt deleted file mode 100644 index 9afd59a8f..000000000 --- a/include/multi/adaptors/blas/CMakeLists.txt +++ /dev/null @@ -1,66 +0,0 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- -cmake_minimum_required(VERSION 3.16) - -#project( -# boost-multi-adaptors-blas -# VERSION 0.1 -# LANGUAGES CXX -#) - -message("CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") - -set(BLA_VENDOR Intel10_64lp) -find_package(BLAS) -if(BLAS_FOUND) # in some systems with MKL, regular BLAS headers need to be found for it to work - message("Multi/BLAS: MKL environment detected") - add_definitions(-DBLAS_DOT_RETURNS_VOID) - # add_definitions(-DFORTRAN_COMPLEX_FUNCTIONS_RETURN_VOID) - SET(CMAKE_SKIP_BUILD_RPATH FALSE) - SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) - SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib64") - SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib64") -else() - message("Multi/BLAS: MKL environment not detected, looking for other BLAS") - if(CMAKE_CXX_COMPILER_ID STREQUAL "NVHPC") - set(BLA_VENDOR NVHPC) - find_package(BLAS) - if(BLAS_FOUND) - message("Multi/BLAS: NVHPC BLAS environment detected") - add_definitions(-DBLAS_DOT_RETURNS_VOID) - # add_definitions(-DFORTRAN_COMPLEX_FUNCTIONS_RETURN_VOID) - # add_definitions(-DRETURN_BY_STACK) # TODO(correaa) check - endif() - else() - message("Multi/BLAS: NVHPC environment not detected, looking for other BLAS") - unset(BLA_VENDOR) - find_package(BLAS) - find_path( - BLAS_INCLUDE_DIRS - cblas.h - /usr/include - /usr/local/include - $ENV{BLAS_HOME}/include - ) - if(BLAS_FOUND) - message("Some BLAS found") - endif() - endif() -endif() - -if(BLAS_FOUND) - message("Some BLAS found: linker flags ${BLAS_LINKER_FLAGS}, libs ${BLAS_LIBRARIES}, libs95 ${BLAS95_LIBRARIES}") -endif() - -link_libraries(${BLAS_LIBRARIES}) -# link_libraries(BLAS::BLAS) # needs cmake 3.18 - -# include_directories(${CMAKE_BINARY_DIR}) - -if(BLAS_FOUND) - add_subdirectory(test) -else() - message(WARNING "BLAS not found, BLAS-adaptor tests will not be compiled and run. If you want this feature install BLAS, for example please run:") - message(WARNING " sudo apt install libblas-dev") - message(WARNING " sudo dnf install blas-devel # in Fedora") -endif() diff --git a/include/multi/adaptors/blas/README.md b/include/multi/adaptors/blas/README.md deleted file mode 100644 index 12f335a9e..000000000 --- a/include/multi/adaptors/blas/README.md +++ /dev/null @@ -1,122 +0,0 @@ - -# [Boost.]Multi BLAS Adaptor - -(not an official Boost library) - -_© Alfredo A. Correa, 2018-2023_ - -The BLAS Adaptor provides an interface for BLAS and BLAS-like libraries (namely cuBLAS). - -## Contents -[[_TOC_]] - -## Numeric Arrays, Conjugation Real and Imaginary parts - -This functions produce views (not copies) related to conjugation, real and imaginary parts. - -```cpp - using complex = std::complex; - complex const I{0.0, 1.0}; - multi::array B = { - {1.0 - 3.0*I, 6.0 + 2.0*I}, - {8.0 + 2.0*I, 2.0 + 4.0*I}, - {2.0 - 1.0*I, 1.0 + 1.0*I} - }; - - namespace blas = multi::blas; - multi::array conjB = blas::conj(B); - - assert( blas::conj(B)[2][1] == std::conj(B[2][1]) ); - - assert( blas::transposed(B)[1][2] == B[2][1] ); - assert( blas::transposed(B) == ~B ); - - assert( blas::hermitized(B)[2][1] == blas::conj(B)[1][2] ); - assert( blas::hermitized(B) == blas::conj(blas::transposed(B)) ); - - assert( blas::real(B)[2][1] == std::real(B[2][1]) ); - assert( blas::imag(B)[2][1] == std::imag(B[2][1]) ); - - multi::array B_real_doubled = { - { 1.0, -3.0, 6.0, 2.0}, - { 8.0, 2.0, 2.0, 4.0}, - { 2.0, -1.0, 1.0, 1.0} - }; - assert( blas::real_doubled(B) == B_real_doubled ); -``` - -Usage: -```cpp - multi::array const a_real = { - { 1.0, 3.0, 1.0}, - { 9.0, 7.0, 1.0}, - }; - - multi::array const b = { - { 11.0 + 1.0*I, 12.0 + 1.0*I, 4.0 + 1.0*I, 8.0 - 2.0*I}, - { 7.0 + 8.0*I, 19.0 - 2.0*I, 2.0 + 1.0*I, 7.0 + 1.0*I}, - { 5.0 + 1.0*I, 3.0 - 1.0*I, 3.0 + 8.0*I, 1.0 + 1.0*I} - }; - - multi::array c({2, 4}); - - blas::real_doubled(c) = blas::gemm(1., a_real, blas::real_doubled(b)); // c = a_real*b -``` - -## Table of features - -All these operations are now supported for CPU and GPU memory, real and complex. - -scalars: `aa` ($`\alpha`$), `bb` ($`\beta`$) \ -vectors: `x`, `y` \ -matrices: `A`, `B`, `C` - -vector operations: `C` (`*`) conjugation (element-wise) \ -matrix operations: `J` (`*`) conjugation (element-wise) (use `C` for vectors), `T` transpose, `H` transpose conjugate (also `C`, discouraged), `U`/`L` upper or lower triangular part (logical zeroing other side) - - -| BLAS | mutable form | effect | operator form [³] | functional form | thrust/STL [¹] | -|--- |--- | --- | --- | --- | --- | -| SWAP |`blas::swap(x, y)` | $`x_i \leftrightarrow y_i`$ | `(x^y)` | | `swap_ranges(begin(x), end(x), begin(y))` | -| COPY |`blas::copy(x, y)` | $`y_i \leftrightarrow x_i`$ | `y << x` | `y = blas::copy(x)` | `copy(begin(x), end(x), begin(y))` | -| ASUM |`blas::asum(x, res)` | $`r \leftarrow \sum_i \|\Re x_i\| + \|\Im x_i\|`$ | `x==0`/`x!=0` `isinf(x)` `isnan(x)`[²] | `res = blas::asum(x)` | `transform_reduce(begin(x), end(x), 0.0, plus<>{}, [](auto const& e){return abs(e.real()) + abs(e.imag());})` | -| NRM2 |`blas::nrm2(x, res)` | $`r \leftarrow \sqrt{\sum_i \|x_i\|^2}`$ | `abs(x)` | `res = blas::nrm2(x);` | `sqrt(trasnform_reduce(begin(x), end(x), 0.0, plus<>{}, [](auto const& e){return norm(e);}));` | -| SCAL |`blas::scal(aa, x);` | $`x_i \leftarrow \alpha x_i`$ | `x*=aa;` | | `for_each(begin(x), end(x), [aa](auto& e){return e*=aa;})` | -| AXPY |`blas::axpy(aa, x, y)` | $`y_i \leftarrow \alpha x_i + y_i`$ | `y+=x` `y-=x` `y+=aa*x` `y-=aa*x` | | `transform(x.begin(), x.end(), y.begin(), y.begin(), [aa](auto ex, auto ey) {return aa*ex + ey;}` | -| DOT | `blas::dot(x, y, res)` | $`r = \sum_i x_i y_i`$ | `res = (x, y);` | `res = blas::dot(x, y)` | `inner_product(begin(x), end(x), begin(y), T{});` | -| | `blas::dot(blas::C(x), y, res)` | $`r = \sum_i \bar x_i y_i`$ | `res = (*x, y);` | `res = blas::dot(blas::C(x), y)` | `inner_product(begin(x), end(x), begin(y), T{}, plus<>{}, [](T const& t1, T const& t2) {return conj(t1)*t2;});` | -| | `blas::dot(x, blas::C(y), res)` | $`r = \sum_i x_i \bar y_i`$ | `res = (x, *y);` | `res = blas::dot(x, blas::C(y));` | `inner_product(x.begin(), x.end(), y.begin(), T{}, plus<>{}, [](T const& t1, T const& t2) {return t1*conj(t2);});` | -| | ~~`blas::dot(blas::C(x), blas::C(y), res)`~~ | $`r = \sum_i \bar x_i \bar y_i`$ not implemented in BLAS, conjugate result | | | `auto res = conj(inner_product(x.begin(), x.end(), y.begin(), T{});` | -| GEMV | `blas::gemv(aa, A, x, bb, y)` | $`y_i \leftarrow \alpha\sum_j A_{ij}x_j + \beta y_i`$ | `y=A%x` `y=aa*A%x` `y+=A%x` `y+=aa*A%x`[¤] | `y=blas::gemv(aa, A, x)` `y+=blas::gemv(aa, A, x)` | `transform(begin(A), end(A), begin(y), [&x, aa] (auto const& Ac) {return aa*blas::dot(Ac, x);})` | -| | `blas::gemv(aa, blas::T(A), x, bb, y)` | $`y_i \leftarrow \alpha\sum_j A_{ji}x_j + \beta y_i`$ | `y= ~A % x` `y=aa*(~A)%x` `y+=(~A)%x` `y+=aa*(~A)%x` | `y=blas::gemv(aa, blas::T(A), x)` `y+=blas::gemv(aa, blas::T(A), x)` | `transform(begin(transposed(A)), end(transposed(A)), begin(y), [&x, aa] (auto const& Ac) {return aa*blas::dot(Ac, x);})` | -| | `blas::gemv(aa, blas::J(A), x, bb, y)` | $`y_i \leftarrow \alpha\sum_j A_{ij}^*x_j + \beta y_i`$ | `y= *A % x` `y=aa*(*A)%x` `y+=(*A)%x` `y+=aa*(*A)%x` | `y=blas::gemv(aa, blas::J(A), x)` `y+=blas::gemv(aa, blas::J(A), x)` | `transform(begin(A), end(A), begin(y), [&x, aa] (auto const& Ac) {return aa*blas::dot(*Ac, x);})` | -| | ~~`blas::gemv(aa, blas::H(A), x, bb, y)`~~ | $`y_i \leftarrow \alpha\sum_j A_{ji}^*x_j + \beta y_i`$ (not BLAS-implemented)| | | `transform(begin(transposed(A)), end(transposed(A)), begin(y), [&x, aa] (auto const& Ac) {return aa*blas::dot(*Ac, x);})` | -| GEMM | `blas::gemm(aa, A, B, bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ik} B_{kj} + \beta C_{ij}`$ | `C = aa*(A*B)` | `C = blas::gemm(aa, A, B)` `C += blas::gemm(aa, A, B)` | `transform(begin(A), end(A), begin(C), begin(C), [&B, aa, bb] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, blas::T(B), Ar, bb, move(Cr));})` | -| | `blas::gemm(aa, A, blas::T(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ik} B_{jk} + \beta C_{ij}`$ | `C = aa*(A* ~B)` | `C = blas::gemm(aa, A, blas::T(B))` `C += blas::gemm(aa, A, blas::T(B))` | `transform(begin(A), end(A), begin(C), begin(C), [&B, aa, bb] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, B, Ar, bb, move(Cr));})` | -| | `blas::gemm(aa, blas::T(A), B, bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ki} B_{kj} + \beta C_{ij}`$ | `C =~A * B` `C = aa*(~A * B)` `C+=~A * B` `C+=aa*(~A * B)` | `C = blas::gemm(aa, blas::T(A), B, bb, C)` (or `+=`) | `transform(begin(transposed(A)), end(transposed(A)), begin(C), begin(C), [&B, aa, bb] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr));})` | -| | `blas::gemm(aa, blas::T(A), blas::T(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ki} B_{jk} + \beta C_{ij}`$ | `C =~A * ~B` `C = aa*(~A * ~B)` `C+=~A * ~B` `C+=aa*(~A * ~B)` | `C = blas::gemm(aa, blas::T(A), blas::T(B), bb, C)` (or `+=`) | `transform(begin(transposed(A)), end(transposed(A)), begin(C), begin(C), [&B, aa, bb] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, B, Ar, bb, std::move(Cr));})` | -| | `blas::gemm(aa, A, blas::J(B), bb, C)` (use `blas::gemm(..., blas::T(B), blas::H(A), ..., HC)` and conjtranspose result) | $`C_{ij} \leftarrow \alpha \sum_k A_{ik} B_{kj}^* + \beta C_{ij}`$ (not BLAS-implemented) | | | `transform(begin(A), end(A), begin(C), begin(C), [BT=transposed(B)](auto const& Ar, auto&& Cr) {transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto&& c) {return aa*blas::dot(Ar, blas::C(Bc)) + bb*c;}); return std::move(Cr);});` | -| | ~~`blas::gemm(aa, blas::J(A), B, bb, C)`~~ | $`C_{ij} \leftarrow \alpha \sum_k A_{ik}^* B_{kj} + \beta C_{ij}`$ (not BLAS-implemented) | | | `transform(begin(A), end(A), begin(C), begin(C), [BT=transposed(B)](auto const& Ar, auto&& Cr) {transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto&& c) {return aa*blas::dot(blas::C(Ar), Bc) + bb*c;}); return std::move(Cr);});` | -| | `blas::gemm(aa, blas::J(A), blas::J(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k \bar{A_{ik}} \bar{B_{kj}} + \beta C_{ij}`$ (not BLAS-implemented) | | | `transform(begin(A), end(A), begin(C), begin(C), [BT=transposed(B)](auto const& Ar, auto&& Cr) {transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto&& c) {return aa*blas::dot(blas::C(Ar), blas::C(Bc)) + bb*c;}); return std::move(Cr);});` | -| | `blas::gemm(aa, A, blas::H(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ik} \bar B_{jk} + \beta C_{ij}`$ | `C = aa*(A* ~*B)` (or `+=`) | `C = blas::gemm(aa, A, blas::H(B))` `C += blas::gemm(aa, A, blas::H(B))` | `transform(begin(A), end(A), begin(CC), begin(CC), [&](auto const& Ar, auto&& Cr){return blas::gemv(aa, blas::J(B), Ar, bb, move(Cr));})` | -| | `blas::gemm(aa, blas::H(A), B, bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ki} B_{kj} + \beta C_{ij}`$ | `CC=~*A *B` | `C=blas::gemm(aa, blas::H(A), B)` | `transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BT=transposed(B)](auto const& Ac, auto&& Cr) {transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c){return aa*blas::dot(blas::C(Ac), Bc) + bb*c;}); return move(Cr);})` | -| | `blas::gemm(aa, blas::H(A), blas::H(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ki} \bar B_{jk} + \beta C_{ij}`$ | `CC=~*A * ~*B` | `C=blas::gemm(aa, blas::H(A), blas::H(B))` | `transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [&B](auto const& Ac, auto&& Cr) {transform(begin(B), end(B), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c) {return conj(std::transform_reduce(begin(Ac), end(Ac), begin(Bc), 0.0*c, std::plus<>{}, [](auto const& a, auto const& b) {return a*b;}));}); return move(Cr);})` | -| | `blas::gemm(aa, blas::T(A), blas::H(B), bb, C)` | $`C_{ij} \leftarrow \alpha \sum_k A_{ki} \bar B_{jk} + \beta C_{ij}`$ | `CC=~A * ~*B` | `C=blas::gemm(aa, blas::T(A), blas::H(B))` | `transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [&B](auto const& Ac, auto&& Cr) {transform(begin(B), end(B), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c) {return std::transform_reduce(begin(Ac), end(Ac), begin(Bc), 0.0*c, std::plus<>{}, [](auto const& a, auto const& b) {return a*conj(b);});}); return move(Cr);})` | -| | ~~`blas::gemm(aa, blas::H(A), blas::T(B), bb, C)`~~ | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ki} B_{jk} + \beta C_{ij}`$ (not BLAS-implemented) | | | `transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [&B](auto const& Ac, auto&& Cr) {transform(begin(B), end(B), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c) {return std::transform_reduce(begin(Ac), end(Ac), begin(Bc), 0.0*c, std::plus<>{}, [](auto const& a, auto const& b) {return conj(a)*b;});}); return move(Cr);})` | -| | ~~`blas::gemm(aa, blas::J(A), blas::H(B), bb, C)`~~ | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ik} \bar B_{jk} + \beta C_{ij}`$ (not BLAS-implemented) | | | | -| | ~~`blas::gemm(aa, blas::H(A), blas::J(B), bb, C)`~~ | $`C_{ij} \leftarrow \alpha \sum_k \bar A_{ki} \bar B_{kj} + \beta C_{ij}`$ (not BLAS-implemented) | | | | -| TRSM | `blas::trsm(blas::side::right, aa, blas::U(A), B)` | $`B\leftarrow B.U^{-1}`$ | `B /= U(A)` | | TODO | -| | `blas::trsm(blas::side::right, aa, blas::L(A), B)` | $`B\leftarrow B.L^{-1}`$ | `B /= L(A)` | | TODO | -| | `blas::trsm(blas::side::left, aa, blas::U(A), B)` | $`B\leftarrow U^{-1}.B`$ | `B \|= U(A)` | | TODO | -| | `blas::trsm(blas::side::left, aa, blas::L(A), B)` | $`B\leftarrow L^{-1}.B`$ | `B \|= L(A)` | | TODO | -| | ~~`blas::trsm(blas::side::right, aa, blas::U(A), blas::J(B))`~~ | $`B*\leftarrow B*.U^{-1}`$ $`B\leftarrow B.U*^{-1}`$ | | | TODO | -| | ~~`blas::trsm(blas::side::right, aa, blas::L(A), blas::J(B))`~~ | $`B*\leftarrow B*.L^{-1}`$ $`B\leftarrow B.L*^{-1}`$ | | | TODO | -| | `blas::trsm(blas::side::right, aa, blas::U(A), blas::H(B))` | $`B^\dagger\leftarrow B^\dagger.U^{-1}`$ $`B\leftarrow U^\dagger^{-1}.B`$ | | | TODO | -| | `blas::trsm(blas::side::right, aa, blas::L(A), blas::H(B))` | $`B^\dagger\leftarrow B^\dagger.L^{-1}`$ $`B\leftarrow L^\dagger^{-1}.B`$ | | | TODO | - -[¹]: for reference, not optimal. \ -[²]: `asum` is interpreted as a mechanism to detect null vectors or vectors containing NaN or infinities. \ -[³]: needs explicit invocation `using namespace multi::operators` namespace or of specific symbols `using multi::operator*`/`operator/=`/etc. \ -[¤]: `y *=bb +=aa*A%x` (`gemv(aa, A, x, bb, y)`) would also be possible. diff --git a/include/multi/adaptors/blas/copy.hpp b/include/multi/adaptors/blas/copy.hpp deleted file mode 100644 index 83aa2c71b..000000000 --- a/include/multi/adaptors/blas/copy.hpp +++ /dev/null @@ -1,204 +0,0 @@ - // -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_COPY_HPP -#define MULTI_ADAPTORS_BLAS_COPY_HPP -#pragma once - -#include -#include - -#include - -namespace boost::multi::blas { - -using core::copy; - -template -auto copy_n(It first, Size n, OutIt d_first) -->decltype(blas::default_context_of(first.base())->copy(n, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n) { - return blas::default_context_of(first.base())->copy(n, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n; } - -// template{}>> -// auto copy_n(Context&& ctxt, It first, Size n, OutIt d_first) -// //->decltype(copy(std::forward(ctxt), n, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n) { -// { return copy(std::forward(ctxt), n, first.base(), first.stride(), d_first.base(), d_first.stride()), d_first + n; } - -// template -// auto copy(It first, It last, OutIt d_first) -// ->decltype(copy_n(first, last - first, d_first)) { -// return copy_n(first, last - first, d_first); } - -// template{}>> -// auto copy(Context&& ctxt, It first, It last, OutIt d_first) -// ->decltype(copy_n(std::forward(ctxt), first, last - first, d_first)) { -// return copy_n(std::forward(ctxt), first, last - first, d_first); } - -template -auto copy(X1D const& x, Y1D&& y) // NOLINT(readability-identifier-length) BLAS naming -->decltype(blas::copy_n(x.begin(), size(x), y.begin()), std::forward(y)) { - assert( (x.size() == y.size()) ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : assert - return blas::copy_n(x.begin(), x.size(), y.begin()), std::forward(y); } - -// template -// auto copy(Context&& ctxt, X1D const& x, Y1D&& y) // NOLINT(readability-identifier-length) BLAS naming -// ->decltype(blas::copy_n(std::forward(ctxt), x.begin(), size(x), y.begin()), std::forward(y)) { -// assert(x.size()==y.size()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr assert -// return blas::copy_n(std::forward(ctxt), x.begin(), x.size(), y.begin()), std::forward(y); -// } - -// template -// class copy_iterator{ -// ContextPtr ctxt = {}; -// It1D it_; - -// public: -// using difference_type = typename std::iterator_traits::difference_type; -// using value_type = typename std::iterator_traits::value_type; -// using pointer = void; -// using reference = void; -// using iterator_category = std::output_iterator_tag; -// using iterator_type = It1D; -// using context_type = ContextPtr; -// constexpr explicit copy_iterator(It1D it) : it_{it}{} -// constexpr copy_iterator(ContextPtr cp, It1D it) : ctxt{cp}, it_{it}{} -// constexpr auto base() const -> iterator_type{return it_;} -// template -// friend constexpr auto copy_n(copy_iterator first, difference_type count, It1DOut result) -> It1DOut{ -// return blas::copy_n(first.ctxt, first.base(), count, result); -// } -// template -// friend constexpr auto copy(copy_iterator first, copy_iterator last, It1DOut d_first) -> It1DOut{ -// return copy_n(first, distance(first, last), d_first); -// } -// template -// friend constexpr auto uninitialized_copy(copy_iterator first, copy_iterator last, It1DOut d_first) -> It1DOut{ -// return copy_n(first, distance(first, last), d_first); -// } -// friend constexpr auto distance(copy_iterator const& self, copy_iterator const& other) -> difference_type{ -// assert(stride(other.it_) == stride(self.it_)); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr assert -// return other.it_ - self.it_; -// } -// constexpr auto operator*() const -> value_type{return *it_;} -// }; - -// template::difference_type> -// class copy_range { -// ContextPtr ctxp_ = {}; -// It1D begin_, end_; - -// public: -// using difference_type = DiffType; -// using iterator = copy_iterator; -// using decay_type = DecayType; - -// copy_range(copy_range const&) = delete; -// copy_range(copy_range&&) noexcept = default; -// auto operator=(copy_range const&) -> copy_range& = delete; -// auto operator=(copy_range&&) -> copy_range& = delete; -// ~copy_range() = default; - -// constexpr copy_range(It1D first, It1D last) : begin_{first}, end_{last} {} -// constexpr copy_range(ContextPtr ctxp, It1D first, It1D last) : ctxp_{ctxp}, begin_{first}, end_{last} {} -// constexpr auto size() const -> difference_type {return end_ - begin_;} -// constexpr auto begin() const {return iterator{ctxp_, begin_};} -// constexpr auto end() const {return iterator{ctxp_, end_ };} -// constexpr auto extensions() const -> typename decay_type::extensions_type {return {multi::iextension{size()}};} -// template(), std::declval()))> -// operator Other() const{return Other(begin(), end());} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax -// friend auto operator+(copy_range const& self) {return self.operator decay_type();} -// }; - -// template [[nodiscard]] -// auto copy(It const& first, It const& last) -// ->decltype(copy_range{first, last}){ -// return copy_range{first, last};} - -// template [[nodiscard]] -// auto copy(Context&& ctxt, It const& first, It const& last) -// ->decltype(copy_range{ctxt, first, last}){ -// return copy_range{ctxt, first, last};} - -template -struct copy_it { - It it_; // NOLINT(misc-non-private-member-variables-in-classes) - - using difference_type = typename std::iterator_traits::difference_type; - using value_type = typename std::iterator_traits::value_type; - using pointer = void; - using reference = void; - using iterator_category = std::output_iterator_tag; - using iterator_type = copy_it; - -// using difference_type = typename It::difference_type; - friend auto operator-(copy_it const& c1, copy_it const& c2) {return c1.it_ - c2.it_;} - - // template - // friend auto copy_n(copy_it first, Size2 count, ItOut d_first) - // ->decltype(blas::copy_n(It{} , count, d_first)) {assert(count == 1); - // return blas::copy_n(first.it_, count, d_first); } - - // template - // friend auto uninitialized_copy_n(copy_it first, Size2 count, ItOut d_first) - // ->decltype(blas::copy_n(It{} , count, d_first)) {assert(count == 1); - // return blas::copy_n(first.it_, count, d_first); } - - template - friend constexpr auto copy_n(copy_it first, difference_type count, It1DOut result) -> It1DOut{ - return blas::copy_n(first.it_, count, result); - } - template - friend constexpr auto copy(copy_it first, copy_it last, It1DOut d_first) -> It1DOut{ - return copy_n(first, distance(first, last), d_first); - } - template - friend constexpr auto uninitialized_copy(copy_it first, copy_it last, It1DOut d_first) -> It1DOut{ - return copy_n(first, distance(first, last), d_first); - } - friend constexpr auto distance(copy_it const& self, copy_it const& other) -> difference_type { - return other.it_ - self.it_; - } - constexpr auto operator*() const -> value_type {return *it_;} -}; - -template [[nodiscard]] -auto copy(A1D const& x) { // NOLINT(readability-identifier-length) BLAS naming - struct ref { - A1D const& x_; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-avoid-const-or-ref-data-members) - using iterator = copy_it; - auto begin() const {return iterator{x_.begin()};} - auto end() const {return iterator{x_.end() };} - auto size() const {return x_.size();} - auto extensions() const {return x_.extensions();} -// auto operator&() const& {return asum_ptr{&x_};} // NOLINT(google-runtime-operator) reference type -// using decay_type = decltype(abs(std::declval())); -// operator decay_type() const {decay_type ret; blas::asum(x_, ret); return ret;} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) -// auto operator+() const -> decay_type {return operator decay_type();} - }; - return ref{x}; -} - -// template [[nodiscard]] -// auto copy(A const& array) // need to specify templates (instead of deduced for intel) -// { -// copy_range{first, last} -// } - -// ->decltype(copy(array.begin(), array.end())){ -// return copy(array.begin(), array.end());} - -// template{}>> [[nodiscard]] -// auto copy(Context&& ctxt, A const& a) // NOLINT(readability-identifier-length) conventional name in BLAS -// ->decltype(copy(std::forward(ctxt), a.begin(), a.end())){ -// return copy(std::forward(ctxt), a.begin(), a.end());} - -namespace operators { - template - auto operator<<(A1D&& lhs, B1D const& rhs) -> A1D&& { - return boost::multi::blas::copy(rhs, lhs); - } -} // end namespace operators - -} // end namespace boost::multi::blas - -#endif diff --git a/include/multi/adaptors/blas/core.hpp b/include/multi/adaptors/blas/core.hpp deleted file mode 100644 index a2c4eeed3..000000000 --- a/include/multi/adaptors/blas/core.hpp +++ /dev/null @@ -1,620 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_CORE_HPP // -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -#define MULTI_ADAPTORS_BLAS_CORE_HPP - -// https://software.intel.com/en-us/articles/intel-mkl-link-line-advisor - -//#include // consider being replaceable by cblas.h - -#include -#include -#include // int64_t -#include // std::memcpy -#include // for debug -#include // numeric_limits -#include // is_convertible - -#include "../../config/MARK.hpp" - -#include "../blas/traits.hpp" - -#if 0 - #define MULTI_ASSERT1(ExpR) assert (ExpR) - #define MULTI_ASSERT2(ExpR, DescriptioN) MULTI_ASSERT1(ExpR && ##DescriptioN) -#else - #if not defined(NDEBUG) - #include - #include - #define MULTI_ASSERT1(ExpR) (void)((ExpR)?0:throw std::logic_error("\n" __FILE__ ":"+std::to_string(__LINE__)+"::\n"+std::string(__PRETTY_FUNCTION__)+"\nLogic assertion `" #ExpR "' failed.")) /*NOLINT(fuchsia-default-arguments-calls,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ - #define MULTI_ASSERT2(ExpR, DescriptioN) (void)((ExpR)?0:throw std::DescriptioN("\n" __FILE__ ":"+std::to_string(__LINE__)+"::\n"+std::string(__PRETTY_FUNCTION__)+"\nLogic assertion `" #ExpR "' failed.")) - #else - #define MULTI_ASSERT1(ExpR) assert(ExpR) - #define MULTI_ASSERT2(ExpR, DescriptioN) assert(EXpR) - #endif -#endif - -#ifdef CBLAS_H -#define BLAS(NamE) cblas_##NamE -#else -#define BLAS(NamE) NamE##_ -extern "C" { - -#ifndef MULTI_BLAS_INT -#if defined(__INTPTR_WIDTH__) - #define MULTI_BLAS_INT __INTPTR_WIDTH__ -#endif -#endif - -#define s float -#define d double -#define c std::complex -#define z std::complex -#define v void - -// cppcheck-suppress unusedStructMember -using Complex_float = struct { float real, imag; }; -// cppcheck-suppress unusedStructMember -using Complex_double = struct { double real, imag; }; - -#define C Complex_float // _Complex s -#define Z Complex_double // _Complex d - -#if defined(MULTI_BLAS_INT) - #if MULTI_BLAS_INT==32 - #define INT int32_t - #elif MULTI_BLAS_INT==64 - #define INT int64_t - #else - #define INT int32_t // 32bit safe? pesimistic? - #endif -#else - #define INT int32_t // 32bit safe? pesimistic? -#endif - -namespace core{ - using size_t = INT; - using ssize_t = std::make_signed_t; -} // end namespace core - -#define INTEGER INT const& -#define N INTEGER n -#define INCX INTEGER incx -#define INCY INTEGER incy - -static_assert(sizeof(INT)==32/8 or sizeof(INT)==64/8, "please set MULTI_BLAS_INT to int32_t or int64_t"); - -// indented declarations like in https://www.netlib.org/lapack/lug/node145.html - -#define xROTG(T1, T2) v T1##rotg ##_ ( T1 const*, T1 const*, T2*, T1* ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xROTMG(T) v T ##rotmg##_ ( T*, T*, T* , T const&, T(¶m)[5] ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xROT(TT, T, S) v TT##rot ##_ ( N, T *x, INCX, T *y, INCY, S const&, S const& ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xROTM(T) v T ##rotm ##_ ( N, T *x, INCX, T *y, INCY, T const(&p)[5]) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xSWAP(T) v T ##swap ##_ ( N, T *x, INCX, T *y, INCY ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xSCAL(TT, TA, TX) v TT##scal ##_ ( N, TA const& a, TX *x, INCX ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xCOPY(T) v T ##copy ##_ ( N, T const *x, INCX, T *y, INCY ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xAXPY(T) v T ##axpy ##_ ( N, T const* a, T const *x, INCX, T *y, INCY ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type - -// in MKL and vanilla BLAS, OpenBLAS, real dot always return by stack -#define xDOT(R, TT, T) auto TT##dot ##_ ( N, T const *x, INCX, T const *y, INCY) -> R // NOLINT(readability-identifier-length) conventional BLAS naming - -// PGI/NVC++ compiler uses a blas version that needs -DRETURN_BY_STACK -#if defined(BLAS_DOT_RETURNS_VOID) -//#if defined(RETURN_BY_STACK) || (defined(FORTRAN_COMPLEX_FUNCTIONS_RETURN_VOID) && FORTRAN_COMPLEX_FUNCTIONS_RETURN_VOID) -//#define xDOT(R, TT, T) v TT##dot ##_ (R*, N, T const *x, INCX, T const *y, INCY) -#define xDOTU(R, T) v T ##dotu ##_ (R*, N, T const *x, INCX, T const *y, INCY) // NOLINT(bugprone-macro-parentheses) : macro arg expands to type -#define xDOTC(R, T) v T ##dotc ##_ (R*, N, T const *x, INCX, T const *y, INCY) // NOLINT(bugprone-macro-parentheses) : macro arg expands to type -#else -#define xDOTU(R, T) auto T ##dotu ##_ ( N, T const *x, INCX, T const *y, INCY) -> R // NOLINT(readability-identifier-length) conventional BLAS naming -#define xDOTC(R, T) auto T ##dotc ##_ ( N, T const *x, INCX, T const *y, INCY) -> R // NOLINT(readability-identifier-length) conventional BLAS naming -//#define xxDOT(TT, T) auto TT##dot ##_ ( N, T const& a, T const *x, INCX, T const *y, INCY) -> T -#endif - -#define xNRM2(R, TT, T) auto TT##nrm2##_ ( N, T const *x, INCX) -> R // NOLINT(readability-identifier-length) conventional BLAS naming -#define xASUM(R, TT, T) auto TT##asum##_ ( N, T const *x, INCX) -> R // NOLINT(readability-identifier-length) conventional BLAS naming -#define IxAMAX(T) auto i##T ##amax##_ ( N, T const* x, INCX) -> INT // NOLINT(readability-identifier-length) conventional BLAS naming - -xROTG(s, s) ; xROTG(d,d) ;// MKL extension xROTG(c, s); xROTG(z, d); -xROTMG(s) ; xROTMG(d) ; -xROT(s, s, s) ; xROT(d, d, d) ; xROT(cs, c, s); xROT(zd, z, d); -xROTM(s) ; xROTM(d) ; -xSWAP(s) ; xSWAP(d) ; xSWAP(c) ; xSWAP(z); -xSCAL(s, s, s); xSCAL(d, d, d); xSCAL(c, c, c); xSCAL(z, z, z); xSCAL(zd, d, z); xSCAL(cs, s, c); -xCOPY(s) ; xCOPY(d) ; xCOPY(c) ; xCOPY(z) ; -xAXPY(s) ; xAXPY(d) ; xAXPY(c) ; xAXPY(z) ; -xDOT(s, s, s) ; xDOT(d, d, d) ; xDOT(d, ds, s); - -xDOTU(C, c); xDOTU(Z, z); -//xDOTU(c, c); xDOTU(z, z); -xDOTC(C, c); xDOTC(Z, z); -//xxDOT(sds, s); -xNRM2(s, s, s); xNRM2(d, d, d); xNRM2(s, sc, c); xNRM2(d, dz, z); -xASUM(s, s, s); xASUM(d, d, d); xASUM(s, sc, c); xASUM(d, dz, z); -IxAMAX(s); IxAMAX(d); IxAMAX(c); IxAMAX(z); - -#define TRANS const char& trans -#define NR INTEGER nr -#define NC INTEGER nc -#define LDA INTEGER lda -#define UPLO const char& uplo -#define DIAG const char& diag - -#define xGEMV(T) void T## gemv ##_ ( TRANS, NR, NC, T const& a, T const* A, LDA, T const* X, INCX, T const& beta, T* Y, INCY ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xGER( T) void T## ger ##_ ( NR, NC, T const& a, T const* X, INCX, T const* Y, INCY, T* A, LDA) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xGERU(T) void T## geru ##_ ( NR, NC, T const& a, T const* X, INCX, T const* Y, INCY, T* A, LDA) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xGERC(T) void T## gerc ##_ ( NR, NC, T const& a, T const* X, INCX, T const* Y, INCY, T* A, LDA) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xTRSV(T) void T## trsv ##_ (UPLO, TRANS, DIAG, N, T const* A, LDA, T* X , INCX ) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type - -xGEMV(s); xGEMV(d); xGEMV(c); xGEMV(z); -xGER(s) ; xGER(d) ; -xGERU(c); xGERU(z); -xGERC(c); xGERC(z); -xTRSV(s); xTRSV(d); xTRSV(c); xTRSV(z); - -#define TRANSA const char& transa -#define TRANSB const char& transb -#define NK INTEGER nk -#define LDB INTEGER ldb -#define LDC INTEGER ldc - -#define SIDE const char& side - -#define xGEMM(T) void T ##gemm ##_ ( TRANSA, TRANSB, NR, NC, NK, T const& a, T const* A, LDA, T const* B, LDB, T const& b , T const* CC, LDC) // NOLINT(readability-identifier-length) conventional BLAS naming -#define xSYRK(T) void T ##syrk ##_ ( UPLO, TRANSA, NR, NK, T const& a, T const* A, LDA, T const& b , T* CC, LDC) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xHERK(TT, T) void T ##herk ##_ ( UPLO, TRANSA, NR, NK, TT const& a, T const* A, LDA, TT const& b , T* CC, LDC) // NOLINT(bugprone-macro-parentheses,readability-identifier-length) macro arg expands to type -#define xTRSM(T) void T ##trsm ##_ (SIDE, UPLO, TRANSA, DIAG, NR, NK, T const& a, T const* A, LDA, T const* B, LDB ) // NOLINT(readability-identifier-length) conventional BLAS naming - -xGEMM(s); xGEMM(d); xGEMM(c) ; xGEMM(z) ; -xSYRK(s); xSYRK(d); xSYRK(c) ; xSYRK(z) ; - xHERK(s, c); xHERK(d, z); -xTRSM(s); xTRSM(d); xTRSM(c) ; xTRSM(z) ; - -#undef TRANS -#undef UPLO -#undef SIDE -#undef DIAG -#undef xROTG -#undef xROTMG -#undef xROT -#undef xROTM -#undef xSCAL -#undef xSWAP -#undef xCOPY -#undef xAXPY -#undef xDOT -#undef xDOTU -#undef xDOTC -#undef xxDOT -#undef xNRM2 -#undef xASUM -#undef IxAMAX -#undef xGEMV -#undef xGER -#undef xGERU -#undef xGERC -#undef xGEMM -#undef xHERK -#undef xTRSM - -#undef s -#undef d -#undef c -#undef z -#undef C -#undef Z -#undef v -#undef INTEGER -#undef N -#undef INCX -#undef INCY -#undef TRANSA -#undef TRANSB -#undef LDA -#undef LDB -#undef LDC -} // end of extern "C" -#endif - -namespace boost::multi::blas { - -//namespace t { - using s = float; - using d = double; - using c = std::complex; //using C = Complex_float ; - using z = std::complex; //using Z = Complex_double; - using v = void; -//} // end namespace types - -// Boundary Checked value -#define BC(value) [](auto checked) {assert(checked >= std::numeric_limits::min() and checked < std::numeric_limits::max()); return checked;}(value) /*NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ - -//xrotg(s, s) xrotg(d, d) //MKL extension xrotg(c, s); xrotg(z, d); -//xrotmg(s) xrotmg(d) -//xrot(s, s, s) xrot(d, d, d) xrot(c, cs, s) xrot(z, zd, d) -//xrotm(s) xrotm(d) -//xswap(s) xswap(d) xswap(c) xswap(z) - -namespace core { - -using std::enable_if_t; -using std::is_assignable; - -template{} and is_s{} and is_assignable{},int> =0> void swap(ssize_t n, SX* x, ptrdiff_t incx, SY* y, ptrdiff_t incy) {BLAS(sswap)(n, reinterpret_cast< float *>(x), incx, ( float *)(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template{} and is_d{} and is_assignable{},int> =0> void swap(ssize_t n, DX* x, ptrdiff_t incx, DY* y, ptrdiff_t incy) {BLAS(dswap)(n, reinterpret_cast< double *>(x), incx, ( double *)(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template{} and is_c{} and is_assignable{},int> =0> void swap(ssize_t n, CX* x, ptrdiff_t incx, CY* y, ptrdiff_t incy) {BLAS(cswap)(n, reinterpret_cast*>(x), incx, (std::complex*)(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template{} and is_z{} and is_assignable{},int> =0> void swap(ssize_t n, ZX* x, ptrdiff_t incx, ZY* y, ptrdiff_t incy) {BLAS(zswap)(n, reinterpret_cast*>(x), incx, (std::complex*)(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) - -template{} and is_s{} and is_assignable{},int> =0> void copy(ssize_t n, SX* x, ptrdiff_t incx, SY* y, ptrdiff_t incy) {BLAS(scopy)(n, ( float const*)(x), incx, ( float *)(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template{} and is_d{} and is_assignable{},int> =0> void copy(ssize_t n, DX* x, ptrdiff_t incx, DY* y, ptrdiff_t incy) {BLAS(dcopy)(n, ( double const*)(x), incx, ( double *)(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template{} and is_c{} and is_assignable{},int> =0> void copy(ssize_t n, CX* x, ptrdiff_t incx, CY* y, ptrdiff_t incy) {BLAS(ccopy)(n, (std::complex const*)(x), incx, (std::complex*)(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template{} and is_z{} and is_assignable{},int> =0> void copy(ssize_t n, ZX* x, ptrdiff_t incx, ZY* y, ptrdiff_t incy) {BLAS(zcopy)(n, (std::complex const*)(x), incx, (std::complex*)(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) - -// TODO(correaa) : add mixed-type scal (zdscal, csscal) -template::element_type, class ALPHA = typename std::pointer_traits::element_type, enable_if_t{} and is_s{} and is_assignable{}>* = nullptr> void scal(ssize_t n, ALPHAP a, SXP xp, ptrdiff_t incx) {BLAS(sscal)(n, *( float const*)a, ( float *)xp, incx);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template::element_type, class ALPHA = typename std::pointer_traits::element_type, enable_if_t{} and is_d{} and is_assignable{}>* = nullptr> void scal(ssize_t n, ALPHAP a, DXP xp, ptrdiff_t incx) {BLAS(dscal)(n, *( double const*)a, ( double *)xp, incx);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template::element_type, class ALPHA = typename std::pointer_traits::element_type, enable_if_t{} and is_c{} and is_assignable{}>* = nullptr> void scal(ssize_t n, ALPHAP a, CXP xp, ptrdiff_t incx) {BLAS(cscal)(n, *(std::complex const*)a, (std::complex*)xp, incx);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template::element_type, class ALPHA = typename std::pointer_traits::element_type, enable_if_t{} and is_z{} and is_assignable{}>* = nullptr> void scal(ssize_t n, ALPHAP a, ZXP xp, ptrdiff_t incx) {BLAS(zscal)(n, *(std::complex const*)a, (std::complex*)xp, incx);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) - -using std::pointer_traits; -using std::enable_if_t; -using std::is_convertible_v; - -#define xaxpy(T) \ -template::element_type, class SYP, class SY = typename pointer_traits::element_type, enable_if_t< \ - is_##T{} and is_##T{} and is_##T{} and is_assignable{} \ - and is_convertible_v and is_convertible_v \ -, int> =0> \ -void axpy(size_t n, ALPHA const* a, SXP x, size_t incx, SYP y, size_t incy) {BLAS(T##axpy)(n, (T const *)a, (T const*)static_cast(x), incx, (T*)static_cast(y), incy);} /*NOLINT(readability-identifier-length) NOLINT(readability-identifier-length) conventional BLAS name*/ - -xaxpy(s) xaxpy(d) xaxpy(c) xaxpy(z) -#undef xaxpy -} // end namespace core - -#undef xrotg -#undef xrot -#undef xswap -#undef xscal -#undef xcopy -#undef xaxpy -#undef xdot - -#ifndef CBLAS_H - -namespace core { - -using std::enable_if_t; -using std::is_assignable; - -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_s{} and is_assignable{}, int> =0> void dot (ssize_t n, XP* x, ptrdiff_t incx, YP* y, ptrdiff_t incy, RP* r) {auto const rr = BLAS(sdot )(n, (s const*)static_cast(x), incx, (s const*)static_cast(y), incy); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r));} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_d{} and is_assignable{}, int> =0> void dot (ssize_t n, XP* x, ptrdiff_t incx, YP* y, ptrdiff_t incy, RP* r) {auto const rr = BLAS(ddot )(n, (d const*)static_cast(x), incx, (d const*)static_cast(y), incy); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r));} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) - -// PGI/NVC++ compiler uses a blas version that needs -DRETURN_BY_STACK -#if defined(BLAS_DOT_RETURNS_VOID) -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_c{} and is_assignable{}, int> =0> void dotu(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) { BLAS(cdotu)(reinterpret_cast(r), n, (c const*)static_cast(x), incx, (c const*)static_cast(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,google-readability-casting) : adapt types -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_z{} and is_assignable{}, int> =0> void dotu(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) { BLAS(zdotu)(reinterpret_cast(r), n, (z const*)static_cast(x), incx, (z const*)static_cast(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,google-readability-casting) : adapt types - -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_c{} and is_assignable{}, int> =0> void dotc(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) { BLAS(cdotc)(reinterpret_cast(r), n, (c const*)static_cast(x), incx, (c const*)static_cast(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,google-readability-casting) : adapt types -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_z{} and is_assignable{}, int> =0> void dotc(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) { BLAS(zdotc)(reinterpret_cast(r), n, (z const*)static_cast(x), incx, (z const*)static_cast(y), incy);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,google-readability-casting) : adapt types -#else -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_c{} and is_assignable{}, int> =0> void dotu(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) {auto const rr = BLAS(cdotu)( n, (c const*)static_cast(x), incx, (c const*)static_cast(y), incy); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r));} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_z{} and is_assignable{}, int> =0> void dotu(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) {auto const rr = BLAS(zdotu)( n, (z const*)static_cast(x), incx, (z const*)static_cast(y), incy); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r));} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) - -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_c{} and is_assignable{}, int> =0> void dotc(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) {auto const rr = BLAS(cdotc)( n, (c const*)static_cast(x), incx, (c const*)static_cast(y), incy); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r));} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -template::element_type, class YP, class Y = typename std::pointer_traits::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_z{} and is_assignable{}, int> =0> void dotc(ssize_t n, XP x, ptrdiff_t incx, YP y, ptrdiff_t incy, RP r) {auto const rr = BLAS(zdotc)( n, (z const*)static_cast(x), incx, (z const*)static_cast(y), incy); std::memcpy(reinterpret_cast(static_cast(r)), &rr, sizeof(rr)); static_assert(sizeof(rr)==sizeof(*r));} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,google-readability-casting,readability-identifier-length) -#endif - -} // end namespace core -#else -// TODO(correaa) : make cblas version -#define xdotu(T) template v dotu(S n, add_const_ptr_t x, S incx, add_const_ptr_t y, S incy, add_ptr_t r){BLAS(T##dotu_sub)(BC(n), x, BC(incx), y, BC(incy), r);} -#define xdotc(T) template v dotc(S n, add_const_ptr_t x, S incx, add_const_ptr_t y, S incy, add_ptr_t r){BLAS(T##dotc_sub)(BC(n), x, BC(incx), y, BC(incy), r);} - -namespace core { - xdotu(c) xdotu(z) - xdotc(c) xdotc(z) -} - -#undef xdotu -#undef xdotc -#endif - -namespace core { - template auto dot(S n, s const& b, s const* x, S incx, s const* y, S incy) -> s {return BLAS(sdsdot)(BC(n), b, x, BC(incx), y, BC(incy));} // NOLINT(readability-identifier-length) conventional BLAS name - -//template void dot(S n, s const& b, s const* x, S incx, s const* y, S incy, s* result){*result = BLAS(sdsdot)(BC(n), b, x, BC(incx), y, BC(incy));} -} // end namespace core - -//#define xnrm2(R, T, TT) template v nrm2 (S n, add_const_ptr_t x, S incx, R* r){*r = BLAS(TT##nrm2 )(BC(n), x, BC(incx));} - -#define xasum(T, TT) template auto asum (S n, T const* x, S incx){return BLAS(TT##asum )(BC(n), x, BC(incx)) ;} // NOLINT(readability-identifier-length) conventional BLAS name -#define ixamax(T) template auto iamax(S n, T const* x, S incx){return BLAS(i##T##amax)(BC(n), x, BC(incx)) - 1;} // NOLINT(readability-identifier-length) conventional BLAS name - -//xasum(s, s) xasum(d, d) xasum (c, sc) xasum(z, dz) - -namespace core { -// xnrm2(s, s, s) xnrm2(d, d, d) xnrm2(s, c, sc) xnrm2(d, z, dz) - -template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_s{} and std::is_assignable{} , int> =0> void asum(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(sasum) (n, (s const*)static_cast(x), incx); std::memcpy((s*)static_cast(r), &rr, sizeof(s));} // NOLINT(google-readability-casting,readability-identifier-length) -template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_d{} and std::is_assignable{} , int> =0> void asum(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(dasum) (n, (d const*)static_cast(x), incx); std::memcpy((s*)static_cast(r), &rr, sizeof(d));} // NOLINT(google-readability-casting,readability-identifier-length) - -template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_s{} and std::is_assignable{}, int> =0> void asum(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(scasum)(n, (c const*)static_cast(x), incx); std::memcpy((s*)static_cast(r), &rr, sizeof(s));} // NOLINT(google-readability-casting,readability-identifier-length) -template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_d{} and std::is_assignable{}, int> =0> void asum(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(dzasum)(n, (z const*)static_cast(x), incx); std::memcpy((s*)static_cast(r), &rr, sizeof(d));} // NOLINT(google-readability-casting,readability-identifier-length) - - -template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_s{} and std::is_assignable{} , int> =0> void nrm2(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(snrm2) (n, (s const*)static_cast(x), incx); std::memcpy((s*)static_cast(r), &rr, sizeof(s));} // NOLINT(google-readability-casting,readability-identifier-length) -template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_d{} and std::is_assignable{} , int> =0> void nrm2(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(dnrm2) (n, (d const*)static_cast(x), incx); std::memcpy((s*)static_cast(r), &rr, sizeof(d));} // NOLINT(google-readability-casting,readability-identifier-length) - -template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_s{} and std::is_assignable{}, int> =0> void nrm2(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(scnrm2)(n, (c const*)static_cast(x), incx); std::memcpy((s*)static_cast(r), &rr, sizeof(s));} // NOLINT(google-readability-casting,readability-identifier-length) -template::element_type, class RP, class R = typename std::pointer_traits::element_type, enable_if_t{} and is_d{} and std::is_assignable{}, int> =0> void nrm2(ssize_t n, XP x, ptrdiff_t incx, RP r) {auto rr = BLAS(dznrm2)(n, (z const*)static_cast(x), incx); std::memcpy((s*)static_cast(r), &rr, sizeof(d));} // NOLINT(google-readability-casting,readability-identifier-length) - -// template v nrm2 (S n, typename add_const_ptr>::type x, S incx, d* r){*r = BLAS(dznrm2 )(BC(n), x, BC(incx));} - ixamax(s) ixamax(d) ixamax(c) ixamax(z) -} // end namespace core - -#undef xnrm2 -#undef xasum -#undef ixamax - -/////////////////////////////////////////////////////////////////////////////// -// LEVEL2 -//#define xgemv(T) template v gemv(C trans, S m, S n, T const& a, T const* A, S lda, T const* X, S incx, T beta, T* Y, S incy ){BLAS(T##gemv)(trans, BC(m), BC(n), a, A, BC(lda), X, BC(incx), beta, Y, BC(incy) );} -//#define xger(T) template< class S> v ger ( S m, S n, T const& a, T const* X, S incx, T const* Y, S incy, T* A, S lda){BLAS(T##ger )( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} -// template< class S> v ger ( S m, S n, c const& a, c const* X, S incx, c const* Y, S incy, c* A, S lda){BLAS(cgeru )( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} -// template< class S> v ger ( S m, S n, z const& a, z const* X, S incx, z const* Y, S incy, z* A, S lda){BLAS(zgeru )( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} -//#define xgeru(T) template< class S> v geru( S m, S n, T const& a, T const* X, S incx, T const* Y, S incy, T* A, S lda){BLAS(T##geru)( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} -//#define xgerc(T) template< class S> v gerc( S m, S n, T const& a, T const* X, S incx, T const* Y, S incy, T* A, S lda){BLAS(T##gerc)( BC(m), BC(n), a, X, BC(incx), Y, BC(incy), A, BC(lda));} - -namespace core { - -//xgemv(s) xgemv(d) xgemv(c) xgemv(z) -//xger(s) xger(d) -// xgeru(c) xgeru(z) -// xgerc(c) xgerc(z) - -using std::enable_if_t; -using std::is_assignable; - -#if 0 -template::element_type, class BBP, class BB = typename pointer_traits::element_type, class BETA, class CCP, class CC = typename pointer_traits::element_type, \ -enable_if_t< \ - is_##T{} and is_##T{} and is_##T{} and /*is_assignable{} and */ \ - is_convertible_v and is_convertible_v and is_convertible_v \ -, int> =0> \ -v gemm(char transA, char transB, ssize_t m, ssize_t n, ssize_t k, ALPHA const* alpha, AAP aa, ssize_t lda, BBP bb, ssize_t ldb, BETA const* beta, CCP cc, ssize_t ldc) { /*NOLINT(bugprone-easily-swappable-parameters)*/ \ - -#endif - -template{} and is_s{} and is_s{} and is_assignable{}, int> =0> void gemv(char trans, size_t m, size_t n, A const* a, M* ma, size_t lda, X* x, size_t incx, B const* b, Y* y, size_t incy) {BLAS(sgemv)(trans, m, n, *a, reinterpret_cast(ma), lda, reinterpret_cast(x), incx, *b, reinterpret_cast(y), incy);} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) -template{} and is_d{} and is_d{} and is_assignable{}, int> =0> void gemv(char trans, size_t m, size_t n, A const* a, M* ma, size_t lda, X* x, size_t incx, B const* b, Y* y, size_t incy) {BLAS(dgemv)(trans, m, n, *a, reinterpret_cast(ma), lda, reinterpret_cast(x), incx, *b, reinterpret_cast(y), incy);} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) -template{} and is_c{} and is_c{} and is_assignable{}, int> =0> void gemv(char trans, size_t m, size_t n, A const* a, M* ma, size_t lda, X* x, size_t incx, B const* b, Y* y, size_t incy) {BLAS(cgemv)(trans, m, n, *a, reinterpret_cast(ma), lda, reinterpret_cast(x), incx, *b, reinterpret_cast(y), incy);} // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) -template{} and is_z{} and is_z{} and is_assignable()*std::declval()*std::declval()+std::declval()*std::declval())>{}, int> =0> void gemv(char trans, size_t m, size_t n, A const* a, M* ma, size_t lda, X* x, size_t incx, B const* b, Y* y, size_t incy) { // NOLINT(google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) - BLAS(zgemv)(trans, m, n, *a, reinterpret_cast(ma), lda, reinterpret_cast(x), incx, *b, reinterpret_cast(y), incy); // NOLINT(fuchsia-default-arguments-calls,google-readability-casting,readability-identifier-length,cppcoreguidelines-pro-type-reinterpret-cast) -} - -} // end namespace core - -template -struct blas2 { -// template -// static v trsv(char ulA, char transA, char di, S m, T const* A, S lda, T* X, S incx) = delete; -}; - -template<> struct blas2 {template static v trsv(As... args) {BLAS(strsv)(args...);}}; -template<> struct blas2 {template static v trsv(As... args) {BLAS(dtrsv)(args...);}}; -template<> struct blas2 {template static v trsv(As... args) {BLAS(ctrsv)(args...);}}; -template<> struct blas2 {template static auto trsv(As... args) -> decltype(BLAS(ztrsv)(args...)) {BLAS(ztrsv)(args...);}}; - -namespace core { - template - v trsv(C ulA, C transA, C diA, S n, TconstP A, S lda, TP X, S incx) { // NOLINT(readability-identifier-length) conventional BLAS naming - blas2::element_type>>::trsv(ulA, transA, diA, n, A, lda, X, incx); - } -} // end namespace core - -//#undef xgemv -#undef xger -#undef xgeru -#undef xgerc - -/////////////////////////////////////////////////////////////////////////////// -// LEVEL 3 - -#if 0 -#define xsyrk(T) \ -template v syrk( UL ul, C transA, S n, S k, T alpha, T const* A, S lda, T beta, T* CC, S ldc){ \ - MULTI_MARK_SCOPE("cpu_syrk"); BLAS(T##syrk)( ul, transA, BC(n), BC(k), alpha, A, BC(lda), beta, CC, BC(ldc));} -#endif - -namespace core { - -using std::is_convertible_v; -using std::pointer_traits; -using std::enable_if_t; -using std::max; - -#define xsyrk(T) \ -template::element_type, class BETA, class CCP, class CC = typename pointer_traits::element_type, \ -enable_if_t< \ - is_##T{} and is_##T{} and is_assignable{} and \ - is_convertible_v and is_convertible_v \ -, int> =0> \ -v syrk( UL uplo, C transA, S n, S k, ALPHA const* alpha, AAP aa, S lda, BETA const* beta, CCP cc, S ldc) /*NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length)*/ \ -/*=delete;*/ \ -{ \ - if(transA == 'N' or transA == 'n') {MULTI_ASSERT1( lda >= max(1L, n) );} \ - if(transA != 'N' and transA != 'n') {MULTI_ASSERT1( lda >= max(1L, k) );} \ - MULTI_ASSERT1( ldc >= max(1L, n) ); \ - MULTI_MARK_SCOPE("cpu_herk"); \ - BLAS(T##syrk)( uplo, transA, BC(n), BC(k), *reinterpret_cast(alpha), aa, BC(lda), *reinterpret_cast(beta), cc, BC(ldc)); /*NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)*/ \ -} \ - -#define xherk(T) \ -template::element_type, class BETA, class CCP, class CC = typename pointer_traits::element_type, class Real = typename T::value_type, \ -enable_if_t< \ - is_##T{} and is_##T{} and is_assignable{} and \ - is_convertible_v and is_convertible_v \ -, int> =0> \ -v herk( UL uplo, C transA, S n, S k, ALPHA const* alpha, AAP aa, S lda, BETA const* beta, CCP cc, S ldc) /*NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length)*/ \ -/*=delete;*/ \ -{ \ - if(transA == 'N' or transA == 'n') {MULTI_ASSERT1( lda >= max(1L, n) );} /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ - if(transA != 'N' and transA != 'n') {MULTI_ASSERT1( lda >= max(1L, k) );} \ - MULTI_ASSERT1( ldc >= max(1L, n) ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ - MULTI_MARK_SCOPE("cpu_herk"); \ - BLAS(T##herk)( uplo, transA, BC(n), BC(k), *reinterpret_cast(alpha), aa, BC(lda), *reinterpret_cast(beta), cc, BC(ldc)); /*NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)*/ \ -} \ - -#define xgemm(T) \ -template::element_type, class BBP, class BB = typename pointer_traits::element_type, class BETA, class CCP, class CC = typename pointer_traits::element_type, \ -enable_if_t< \ - is_##T{} and is_##T{} and is_##T{} and /*is_assignable{} and */ \ - is_convertible_v and is_convertible_v and is_convertible_v \ -, int> =0> \ -v gemm(char transA, char transB, ssize_t m, ssize_t n, ssize_t k, ALPHA const* alpha, AAP aa, ssize_t lda, BBP bb, ssize_t ldb, BETA const* beta, CCP cc, ssize_t ldc) { /*NOLINT(bugprone-easily-swappable-parameters)*/ \ - MULTI_MARK_SCOPE("cpu_gemm"); \ - using std::max; \ - if(transA == 'N') {MULTI_ASSERT1(lda >= max(1L, m));} \ - if(transA != 'N') {MULTI_ASSERT1(lda >= max(1L, k));} \ - if(transB == 'N') {MULTI_ASSERT1(ldb >= max(1L, k));} \ - if(transB != 'N') {MULTI_ASSERT1(ldb >= max(1L, n));} \ - MULTI_ASSERT1( aa != cc ); \ - MULTI_ASSERT1( bb != cc ); \ - if(not( ldc >= max(1L, m) )) {throw std::logic_error("failed 'ldc >= max(1L, m)' with ldc = "+ std::to_string(ldc) +" and m = "+ std::to_string(m));} \ - if(*beta != 0.) {MULTI_ASSERT1((is_assignable()*std::declval()*std::declval() + std::declval()*std::declval())> {}));} \ - BLAS(T##gemm)(transA, transB, BC(m), BC(n), BC(k), *reinterpret_cast(alpha), reinterpret_cast(static_cast(aa)), BC(lda), reinterpret_cast(static_cast(bb)), BC(ldb), *reinterpret_cast(beta), (T*)(static_cast(cc)) /*NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)*/ /*TODO(correaa) check constness*/, BC(ldc)); \ -} \ - -// NOLINTNEXTLINE(readability-identifier-length) conventional BLAS name -xgemm(s) xgemm(d) xgemm(c) xgemm(z) // NOLINT(readability-function-cognitive-complexity) : 36 of 25 -#undef xgemm - -#define xtrsm(T) \ -template::element_type, class BBP, class BB = typename pointer_traits::element_type, \ -enable_if_t< \ - is_##T{} and is_##T{} and is_assignable{} and is_assignable{} and \ - is_convertible_v and is_convertible_v \ -,int> =0> \ -v trsm(char side, char uplo, char transA, char diag, ssize_t m, ssize_t n, ALPHA alpha, AAP aa, ssize_t lda, BBP bb, ssize_t ldb) { /*NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length)*/ \ - MULTI_MARK_SCOPE("cpu_trsm"); \ - assert( side == 'L' or side == 'R' ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ - assert( uplo == 'U' or uplo == 'L' ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ - assert( transA == 'N' or transA == 'T' or transA == 'C' ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ - assert( diag == 'U' or diag == 'N' ); /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ - MULTI_ASSERT1( m >= 0 and n >= 0 ); \ - using std::max; \ - if(side == 'L') {MULTI_ASSERT1( lda >= max(ssize_t{1}, m) );} /* NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)*/ \ - if(side == 'R') {MULTI_ASSERT1( lda >= max(ssize_t{1}, n) );} \ - MULTI_ASSERT1( ldb >= max(ssize_t{1}, m) ); \ - BLAS(T##trsm)(side, uplo, transA, diag, BC(m), BC(n), alpha, reinterpret_cast(static_cast(aa)), BC(lda), reinterpret_cast(static_cast(bb)), BC(ldb)); /*NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,bugprone-macro-parentheses)*/ \ -} \ - -xtrsm(s) xtrsm(d) xtrsm(c) xtrsm(z) // NOLINT(readability-function-cognitive-complexity) : 29 of 25 -#undef xtrsm - -xsyrk(s) xsyrk(d) xsyrk(c) xsyrk(z) -#undef xsyrk - xherk(c) xherk(z) - -} // end namespace core - -#undef xsyrk -#undef xherk -#undef xtrsm - -#undef BC - -struct context { // stateless (and thread safe) - template - static auto scal(As... args) - ->decltype(core::scal(args...)) { - return core::scal(args...); } - - template - static auto copy(As... args) - ->decltype(core::copy(args...)) { - return core::copy(args...); } - - template - static auto swap(As... args) - ->decltype(core::swap(args...)) { - return core::swap(args...); } - - template - static auto axpy(As... args) - ->decltype(core::axpy(args...)) { - return core::axpy(args...); } - - template - static auto dot(As... args) - ->decltype(core::dot(args...)) { - return core::dot(args...); } - - template - static auto dotc(As... args) - ->decltype(core::dotc(args...)) { - return core::dotc(args...); } - - template - static auto dotu(As... args) - ->decltype(core::dotu(args...)) { - return core::dotu(args...); } - - template - static auto gemm(As&&... args) - ->decltype(core::gemm(std::forward(args)...)) { - return core::gemm(std::forward(args)...); } - - template - static auto gemv(As&&... args) -// ->decltype(core::gemv(std::forward(args)...)) { - { return core::gemv(std::forward(args)...); } - - template - static auto asum(As... args) - ->decltype(core::asum(args...)) { - return core::asum(args...); } - - template - static auto nrm2(As... args) - ->decltype(core::nrm2(args...)) { - return core::nrm2(args...); } - - template - static auto trsm(As&&... args) // TODO(correaa) remove && - ->decltype(core::trsm(std::forward(args)...)) { - return core::trsm(std::forward(args)...); } - - template - static auto herk(As&&... args) - ->decltype(core::herk(std::forward(args)...)) { - return core::herk(std::forward(args)...); } -}; - -template struct is_context : std::false_type {}; - -template<> struct is_context : std::true_type {}; -template<> struct is_context : std::true_type {}; -template<> struct is_context : std::true_type {}; -template<> struct is_context : std::true_type {}; - -template<> struct is_context : std::true_type {}; - -namespace core { - -// template -// auto copy(Context&& /*unused*/, As... args) -// ->decltype(core::copy(args...)) { -// return core::copy(args...); } - -} // end namespace core - -template::element_type*>{}, int> =0> -auto default_context_of(TPtr const& /*unused*/) -> blas::context* { - static blas::context dc; - return &dc; -} - -} // end namespace boost::multi::blas - -#endif diff --git a/include/multi/adaptors/blas/cuda.hpp_ b/include/multi/adaptors/blas/cuda.hpp_ deleted file mode 100644 index 06feb6147..000000000 --- a/include/multi/adaptors/blas/cuda.hpp_ +++ /dev/null @@ -1,545 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -$CXXX $CXXFLAGS -include"boost/log/trivial.hpp" -D'MULTI_MARK_SCOPE(MsG)=BOOST_LOG_TRIVIAL(trace)< - -#include "../cuda/cublas/error.hpp" - -#include - -#define DECLRETURN(ExpR) ->decltype(ExpR){return ExpR;} -#define JUSTRETURN(ExpR) {return ExpR;} - -#include - -/////////////////// - -#include - -#define CUBLAS_CALL(CodE) \ - MULTI_MARK_SCOPE("multi::cublas::"#CodE); \ - auto s = static_cast(CodE); \ - cudaDeviceSynchronize(); /*TODO make this more specific to mananged ptr and specific handle*/ \ - if(s != boost::multi::cuda::cublas::error::success) throw std::system_error{boost::multi::cuda::cublas::make_error_code(s), "cannot call cublas function "#CodE }; - -cublasStatus_t cublasZdot (cublasHandle_t handle, int n, - const double2 *x, int incx, - const double2 *y, int incy, - double2 *result) = delete; - -namespace boost{ -namespace multi{ - -namespace cublas{ - using Complex = cuComplex; - using DoubleComplex = cuDoubleComplex; - namespace { - template struct complex_t; - template<> struct complex_t{using type = Complex;}; - template<> struct complex_t{using type = DoubleComplex;}; - } - template using complex = typename complex_t::type; - -// 2.2.7. cublasPointerMode_t https://docs.nvidia.com/cuda/cublas/index.html#cublaspointermode_t - enum class pointer_mode : std::underlying_type::type{ - host = CUBLAS_POINTER_MODE_HOST, - device = CUBLAS_POINTER_MODE_DEVICE - }; - template enum pointer_mode scalar_kind(memory::cuda::ptr){return pointer_mode::device;} - template enum pointer_mode scalar_kind(T*){return pointer_mode::host;} -} - -using v = void; -using S = float; -using D = double; -using C = cublas::complex; -using Z = cublas::complex; - -template struct cublas1{}; -template struct cublas2{}; -template struct cublas3{}; - -#define DEFINE_CUBLAS1(UppeR, LowR) \ - template<> struct cublas1{ \ - template static auto iamax(As...as){return cublasI##LowR##amax(as...);} \ - /*amin */ \ - template static auto asum (As...as){return cublas##UppeR##asum (as...);} \ - /*axpy */ \ - template static auto copy (As...as){return cublas##UppeR##copy (as...);} \ - template static auto dot (As...as){return cublas##UppeR##dot (as...);} \ - template static auto dotu (As...as){return cublas##UppeR##dotu (as...);} \ - template static auto dotc (As...as){return cublas##UppeR##dotc (as...);} \ - template static auto nrm2 (As...as){return cublas##UppeR##nrm2 (as...);} \ - /*rot */ \ - /*rotg */ \ - /*rotmg*/ \ - template static auto scal (As...as){return cublas##UppeR##scal (as...);} \ - /*swap */ \ - } - -DEFINE_CUBLAS1(S, s); -DEFINE_CUBLAS1(D, d); - -#define DEFINE_CUBLAS1_COMPLEX(UppeR, LowR, ReaLUppeR, ReaLLowR) \ - template<> struct cublas1{ \ - template static auto iamax(As...as){return cublasI##LowR##amax(as...);} \ - /*amin */ \ - template static auto asum (As...as){return cublas##ReaLUppeR##LowR##asum (as...);} \ - /*axpy */ \ - template static auto copy (As...as){return cublas##UppeR##copy (as...);} \ - template static auto dot (As...as){return cublas##UppeR##dotu (as...);} \ - template static auto dotu (As...as){return cublas##UppeR##dotu (as...);} \ - template static auto dotc (As...as){return cublas##UppeR##dotc (as...);} \ - template static auto nrm2 (As...as){return cublas##UppeR##nrm2 (as...);} \ - /*rot */ \ - /*rotg */ \ - /*rotmg*/ \ - template static auto scal (As...as){return cublas##UppeR##scal (as...);} \ - /*swap */ \ - } - -DEFINE_CUBLAS1_COMPLEX(C, c, S, s); -DEFINE_CUBLAS1_COMPLEX(Z, z, D, d); - -template struct nrm2_result;//{using type = T;}; -template<> struct nrm2_result{using type = S;}; -template<> struct nrm2_result{using type = D;}; -template<> struct nrm2_result{using type = S;}; -template<> struct nrm2_result{using type = D;}; - -template<> struct cublas1{ -// 2.5.1. cublasIamax() https://docs.nvidia.com/cuda/cublas/index.html#cublasi-lt-t-gt-amax - template static cublasStatus_t iamax(cublasHandle_t handle, int n, const T* x, int incx, int *result ){return cublas1::iamax(handle, n, x, incx, result);} -// 2.5.3. cublasasum() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-asum - template static cublasStatus_t asum (cublasHandle_t handle, int n, T1 const* x, int incx, T2* result ){return cublas1::asum(handle, n, x, incx, result);} -// 2.5.5. cublascopy() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-copy - template static cublasStatus_t copy (cublasHandle_t handle, int n, const T* x, int incx, T* y, int incy){return cublas1::copy(handle, n, x, incx, y, incy);} -// 2.5.6. cublasdot() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-dot - template static auto dot(cublasHandle_t handle, int n, const T* x, int incx, const T* y, int incy, T* result) - ->decltype(cublas1::dot(handle, n, x, incx, y, incy, result)){MULTI_MARK_SCOPE("function dot"); - return cublas1::dot(handle, n, x, incx, y, incy, result);} - template static auto dotu(cublasHandle_t handle, int n, const T* x, int incx, const T* y, int incy, T* result) - ->decltype(cublas1::dotu(handle, n, x, incx, y, incy, result)){MULTI_MARK_SCOPE("function dotu"); - return cublas1::dotu(handle, n, x, incx, y, incy, result);} - template static auto dotc(cublasHandle_t handle, int n, const T* x, int incx, const T* y, int incy, T* result) - ->decltype(cublas1::dotc(handle, n, x, incx, y, incy, result)){MULTI_MARK_SCOPE("function dotc"); - return cublas1::dotc(handle, n, x, incx, y, incy, result);} -// 2.5.7. cublasnrm2() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-nrm2 - template static auto nrm2(cublasHandle_t handle, int n, - const T *x, int incx, typename nrm2_result::type *result){return cublas1::nrm2(handle, n, x, incx, result);} -// 2.5.12. cublasscal() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-scale - template static cublasStatus_t scal(cublasHandle_t handle, int n, - const T *alpha, - T *x, int incx){return cublas1::scal(handle, n, alpha, x, incx);} -}; - -template<> struct cublas2{ -// 2.6.16. cublastrsv() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-trsv - template static cublasStatus_t trsv(cublasHandle_t handle, cublasFillMode_t uplo, - cublasOperation_t trans, cublasDiagType_t diag, - int n, const T *A, int lda, - T *x, int incx){return cublas2::trsv(handle, uplo, trans, diag, n, A, lda, x, incx);} -}; - -template<> struct cublas2{template static auto trsv(A...a){return cublasStrsv(a...);}}; -template<> struct cublas2{template static auto trsv(A...a){return cublasDtrsv(a...);}}; -template<> struct cublas2{template static auto trsv(A...a){return cublasCtrsv(a...);}}; -template<> struct cublas2{template static auto trsv(A...a){return cublasZtrsv(a...);}}; - -template<> struct cublas3{ - template static auto gemm (As...as){CUBLAS_CALL(cublasSgemm(as...));} - template static auto syrk (As...as){CUBLAS_CALL(cublasSsyrk(as...));} -// template static auto herk (As...as){return CUBLAS_CALL(cublasSherk)(as...);} - template static auto trsm (As...as){CUBLAS_CALL(cublasStrsm(as...));} -}; -template<> struct cublas3{ - template static auto gemm (As...as){ CUBLAS_CALL(cublasDgemm(as...));} - template static auto syrk (As...as){ CUBLAS_CALL(cublasDsyrk(as...));} -// template static auto herk (As...as){return cublas_call(cublasDherk)(as...);} - template static auto trsm (As...as){ CUBLAS_CALL(cublasDtrsm(as...));} -}; -template<> struct cublas3{ - template static auto gemm (As...as){ CUBLAS_CALL(cublasCgemm(as...));} - template static auto syrk (As...as){ CUBLAS_CALL(cublasCsyrk(as...));} - template static auto herk (As...as){ CUBLAS_CALL(cublasCherk(as...));} - template static auto trsm (As...as){ CUBLAS_CALL(cublasCtrsm(as...));} -}; -template<> struct cublas3{ - template static auto gemm (As...as){ CUBLAS_CALL(cublasZgemm(as...));} - template static auto syrk (As...as){ CUBLAS_CALL(cublasZsyrk(as...));} - template static auto herk (As...as){ CUBLAS_CALL(cublasZherk(as...));} - template static auto trsm (As...as){ CUBLAS_CALL(cublasZtrsm(as...));} -}; - -template struct herk_scalar; -template<> struct herk_scalar{using type = S;}; -template<> struct herk_scalar{using type = D;}; - -template struct asum_scalar; -template<> struct asum_scalar{using type = S;}; -template<> struct asum_scalar{using type = D;}; - -template using herk_scalar_t = typename herk_scalar::type; - -template<> struct cublas3{ -// 2.7.1. cublasgemm() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-gemm - template static auto gemm(cublasHandle_t handle, - cublasOperation_t transa, cublasOperation_t transb, - int m, int n, int k, - const T *alpha, - const T *A, int lda, - const T *B, int ldb, - const T *beta, - T *C, int ldc){MULTI_MARK_SCOPE("cublas3 gemm"); return cublas3::gemm(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc);} -// 2.7.6. cublassyrk() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-syrk - template static auto syrk(cublasHandle_t handle, - cublasFillMode_t uplo, cublasOperation_t trans, - int n, int k, - const T *alpha, - const T *A, int lda, - const T *beta, - T *C, int ldc){return cublas3::syrk(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc);} -// 2.7.13. cublasherk() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-herk - template static auto herk(cublasHandle_t handle, - cublasFillMode_t uplo, cublasOperation_t trans, - int n, int k, - const herk_scalar_t *alpha, - const T2 *A, int lda, - const herk_scalar_t *beta, - T3 *C, int ldc){return cublas3::herk(handle, uplo, trans, n, k, alpha, A, lda, beta, C, ldc);} -// 2.7.10. cublastrsm() https://docs.nvidia.com/cuda/cublas/index.html#cublas-lt-t-gt-trsm - template static auto trsm(cublasHandle_t handle, - cublasSideMode_t side, cublasFillMode_t uplo, - cublasOperation_t trans, cublasDiagType_t diag, - int m, int n, - std::add_const_t *alpha, - std::add_const_t *A, int lda, - T *B, int ldb){return cublas3::trsm(handle, side, uplo, trans, diag, m, n, alpha, A, lda, B, ldb);} -}; - -namespace cublas{ - -template{}, int> =0> decltype(auto) translate(T t){return t;} -template>{}, int> =0> T& translate(T& t){return t;} - -auto translate(std::complex const * t){return reinterpret_cast const*>(t);} -auto translate(std::complex * t){return reinterpret_cast *>(t);} -auto translate(std::complex const* t){return reinterpret_cast const*>(t);} -auto translate(std::complex * t){return reinterpret_cast *>(t);} - -auto translate(thrust::complex const* t){return reinterpret_cast const*>(t);} -auto translate(thrust::complex * t){return reinterpret_cast *>(t);} - -template auto translate(memory::cuda::ptr p) DECLRETURN(translate(raw_pointer_cast(p))) -template auto translate(memory::cuda::managed::ptr p) DECLRETURN(translate(raw_pointer_cast(p))) - -//auto translate(context& c){return c;} - -template{},int> = 0> -auto translate(T n){ - assert(n <= +static_cast(std::numeric_limits::max())); - assert(n > -static_cast(std::numeric_limits::max())); - return static_cast(n); -} - -auto translate(char O)->cublasOperation_t{ - switch(O){case 'N': return CUBLAS_OP_N; case 'T': return CUBLAS_OP_T; case 'C': return CUBLAS_OP_C;} assert(0); - return CUBLAS_OP_N; -} - -//struct context : std::unique_ptr, decltype(&cublasDestroy)>{ -// context() : std::unique_ptr, decltype(&cublasDestroy)>( -// []{MULTI_MARK_SCOPE("multi::cublas::create context"); cublasHandle_t h; cublasCreate(&h); return h;}(), &cublasDestroy -// ){} -// int version() const{ -// int ret; cublasGetVersion(get(), &ret); return ret; -// } -// context(context&& other) noexcept = default; -// ~context() noexcept = default; -//// 2.4.7. cublasGetPointerMode() -// auto get_pointer_mode() const{ -// cublasPointerMode_t ret; cublasGetPointerMode(get(), &ret); -// return static_cast(ret); -// } -//// 2.4.8. cublasSetPointerMode() https://docs.nvidia.com/cuda/cublas/index.html#cublassetpointermode -// context& set_pointer_mode(enum pointer_mode m){ -// cublasSetPointerMode(get(), static_cast(m)); return *this; -// } -// //set_stream https://docs.nvidia.com/cuda/cublas/index.html#cublassetstream -// //get_stream https://docs.nvidia.com/cuda/cublas/index.html#cublasgetstream -// //get_pointer_mode https://docs.nvidia.com/cuda/cublas/index.html#cublasgetpointermode -// //set_pointer_mode https://docs.nvidia.com/cuda/cublas/index.html#cublasgetpointermode -// template auto iamax(As...as) const DECLRETURN(cublas1<>::iamax(get(), translate(as)...)) -// template auto asum (As...as) const DECLRETURN(cublas1<>::asum (get(), translate(as)...)) -// template auto scal (As...as) const DECLRETURN(cublas1<>::scal (get(), translate(as)...)) -// template auto dot (As...as) const DECLRETURN(cublas1<>::dot (get(), translate(as)...)) -// template auto dotu (As...as) const DECLRETURN(cublas1<>::dotu (get(), translate(as)...)) -// template auto dotc (As...as) const DECLRETURN(cublas1<>::dotc (get(), translate(as)...)) -// template -// auto nrm2(S n, Ptr p, S incx, memory::cuda::ptr result) // no const because the method is not thread safe -// ->decltype(cublas1<>::nrm2 (get(), translate(n), translate(p), translate(incx), translate(result))){set_pointer_mode(pointer_mode::device); -// auto r=cublas1<>::nrm2 (get(), translate(n), translate(p), translate(incx), translate(result)); set_pointer_mode(pointer_mode::host); -// return r; -// } -// template -// auto nrm2(S n, Ptr p, S incx, T* result) const{ -// return cublas1<>::nrm2 (get(), translate(n), translate(p), translate(incx), translate(result)); -// } -// template auto copy (As...as) const DECLRETURN(cublas1<>::copy (get(), translate(as)...)) -// template auto trsv (As...as) const{return cublas2<>::trsv(get(), translate(as)...);} - -// template auto gemm(As... as) DECLRETURN(cublas3<>::gemm(get(), translate(as)...)) - -// template auto syrk (As...as) const{return cublas3<>::syrk(get(), translate(as)...);} -// template auto herk (As...as) const{return cublas3<>::herk(get(), translate(as)...);} -// template auto trsm (As...as) const{return cublas3<>::trsm(get(), translate(as)...);} -//}; - -//context* get_default_context(){ -// thread_local context instance; -// return &instance; -//} - -} - -}} - -namespace boost{ -namespace multi{ - -namespace blas{ - -template<> struct is_context : std::true_type{}; -template<> struct is_context : std::true_type{}; -template<> struct is_context : std::true_type{}; - -template boost::multi::cublas::context* default_context_of(memory::cuda:: ptr const&){return boost::multi::cublas::get_default_context();} -template boost::multi::cublas::context* default_context_of(memory::cuda::managed::ptr const&){return boost::multi::cublas::get_default_context();} - -//template boost::multi::cublas::context default_context_of(memory::cuda::managed::ptr){return {};} - -//} - -//namespace memory{namespace cuda{ -// using boost::multi::blas::default_context_of; // to please nvcc 'default_context_of' should be declared prior to the call site or in namespace 'boost::multi::memory::cuda' -//}} -} - -}} - -namespace boost{ -namespace multi{ - -namespace memory{ -namespace cuda{ - -template -auto iamax(As... as) -->decltype(cublas::context{}.iamax(as..., std::declval()), int()){ - int r; cublas::context{}.iamax(as..., &r); return r-1;} - -template//, typename T = typename std::decay_t::value_type> -auto asum(S n, cuda::ptr x, S incx){ - decltype(std::abs(ComplexTconst{})) r; - cublas::context{}.asum(n, raw_pointer_cast(x), incx, &r); - return r; -} - -template auto copy(As... as) DECLRETURN(cublas::context{}.copy(as...)) -template auto scal(As... as) DECLRETURN(cublas::context{}.scal(as...)) -//template auto dot (As... as) DECLRETURN(cublas::context{}.dot (as...)) -template auto dotu(As... as) DECLRETURN(cublas::context{}.dotu(as...)) -template auto dotc(As... as) DECLRETURN(cublas::context{}.dotc(as...)) -template auto nrm2(As... as) DECLRETURN(cublas::context{}.nrm2(as...)) - -template -auto trsv(char ul, char transA, char a_diag, S n, memory::cuda::ptr A, S lda, memory::cuda::ptr X, S ldc){ - cublasFillMode_t uplo = [ul](){ - switch(ul){ - case 'U': return CUBLAS_FILL_MODE_UPPER; - case 'L': return CUBLAS_FILL_MODE_LOWER; - } assert(0); return CUBLAS_FILL_MODE_UPPER; - }(); - cublasOperation_t cutransA = [transA](){ - switch(transA){ - case 'N': return CUBLAS_OP_N; - case 'T': return CUBLAS_OP_T; - case 'C': return CUBLAS_OP_C; - } assert(0); return CUBLAS_OP_N; - }(); - auto cudiag = a_diag=='N'?CUBLAS_DIAG_NON_UNIT:CUBLAS_DIAG_UNIT; - return cublas::context{}.trsv(uplo, cutransA, cudiag, n, A, lda, X, ldc); -} - -template -auto gemm(As... as) -->decltype(cublas::context{}.gemm(as...)){ - return cublas::context{}.gemm(as...);} - -template -void syrk(UL ul, C transA, S n, S k, Real alpha, multi::memory::cuda::ptr A, S lda, Real beta, multi::memory::cuda::ptr CC, S ldc){ - cublasFillMode_t uplo = [ul](){ - switch(ul){ - case 'U': return CUBLAS_FILL_MODE_UPPER; - case 'L': return CUBLAS_FILL_MODE_LOWER; - } assert(0); return CUBLAS_FILL_MODE_UPPER; - }(); - cublasOperation_t cutransA = [transA](){ - switch(transA){ - case 'N': return CUBLAS_OP_N; - case 'T': return CUBLAS_OP_T; - case 'C': return CUBLAS_OP_C; - } assert(0); return CUBLAS_OP_N; - }(); - return cublas::context{}.syrk(uplo, cutransA, n, k, &alpha, static_cast(A), lda, &beta, static_cast(CC), ldc); -} - -template -auto herk(UL ul, C transA, S n, S k, Real alpha, memory::cuda::ptr A, S lda, Real beta, memory::cuda::ptr CC, S ldc){ - cublasFillMode_t uplo = [ul](){ - switch(ul){ - case 'U': return CUBLAS_FILL_MODE_UPPER; - case 'L': return CUBLAS_FILL_MODE_LOWER; - } assert(0); return CUBLAS_FILL_MODE_UPPER; - }(); - cublasOperation_t cutransA = [transA](){ - switch(transA){ - case 'N': return CUBLAS_OP_N; - case 'T': return CUBLAS_OP_T; - case 'C': return CUBLAS_OP_C; - } assert(0); return CUBLAS_OP_N; - }(); - return cublas::context{}.herk(uplo, cutransA, n, k, &alpha, raw_pointer_cast(A), lda, &beta, raw_pointer_cast(CC), ldc); -} - -template -auto trsm(Side /*cublasSideMode_t*/ side, /*cublasFillMode_t*/ Fill uplo, /*cublasOperation_t*/ Trans trans, /*cublasDiagType_t*/ Diag diag, - Size m, Size n, T alpha, cuda::ptr A, Size lda, cuda::ptr B, Size ldb) -->decltype(cublas::context{}.trsm( - side=='L'?CUBLAS_SIDE_LEFT:CUBLAS_SIDE_RIGHT, uplo=='L'?CUBLAS_FILL_MODE_LOWER:CUBLAS_FILL_MODE_UPPER, CUBLAS_OP_N, diag=='N'?CUBLAS_DIAG_NON_UNIT:CUBLAS_DIAG_UNIT, m, n, &alpha, raw_pointer_cast(A), lda, raw_pointer_cast(B), ldb)) -{ - cublasOperation_t trans_cu = [&]{ - switch(trans){ - case 'N': return CUBLAS_OP_N; - case 'T': return CUBLAS_OP_T; - case 'C': return CUBLAS_OP_C; - } __builtin_unreachable(); - }(); -// T alpha_{alpha}; - return cublas::context{}.trsm( - side=='L'?CUBLAS_SIDE_LEFT:CUBLAS_SIDE_RIGHT, uplo=='L'?CUBLAS_FILL_MODE_LOWER:CUBLAS_FILL_MODE_UPPER, trans_cu, diag=='N'?CUBLAS_DIAG_NON_UNIT:CUBLAS_DIAG_UNIT, m, n, &alpha, raw_pointer_cast(A), lda, raw_pointer_cast(B), ldb); -} - -}}}} - -namespace boost{namespace multi{namespace memory{namespace cuda{namespace managed{ - -using cuda::iamax; -using cuda::asum; -using cuda::copy; -using cuda::scal; -//using cuda::dot; -using cuda::dotu; -using cuda::dotc; -using cuda::nrm2; - -template -auto trsv(char ul, char transA, char a_diag, S n, multi::memory::cuda::managed::ptr A, S lda, cuda::managed::ptr X, S ldc){ - cuda::trsv(ul, transA, a_diag, n, cuda::ptr(A), lda, cuda::ptr(X), ldc); -} - -using cuda::gemm; -using cuda::syrk; -using cuda::herk; - -template -auto trsm(Side /*cublasSideMode_t*/ side, /*cublasFillMode_t*/ Fill uplo, /*cublasOperation_t*/ Trans trans, /*cublasDiagType_t*/ Diag diag, - Size m, Size n, T alpha, cuda::managed::ptr A, Size lda, cuda::managed::ptr B, Size ldb){ - return trsm(side, uplo, trans, diag, m, n, alpha, cuda::ptr(A), lda, cuda::ptr(B), ldb); -} - -}}}}} - -/////////////////////////////////////////////////////////////////////////////// - -#if not __INCLUDE_LEVEL__ // _TEST_MULTI_ADAPTORS_BLAS_CUDA - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuBLAS" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../array.hpp" -#include "../../utility.hpp" - -#include "../../adaptors/cuda.hpp" -#include "../../adaptors/blas.hpp" -#include "../../adaptors/blas/cuda.hpp" - -#include - -namespace multi = boost::multi; - -#if 0 -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_version){ - multi::cublas::context c; - BOOST_REQUIRE( c.version() >= 10100 ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_iamax){ - using complex = std::complex; - complex const I{0,1}; - { - multi::array const A = {1. + 2.*I, 2., 3. + 3.*I, 4.}; - using multi::blas::iamax; - BOOST_REQUIRE( iamax(A) == 2 ); - } - { - multi::cuda::array const A = {1. + 2.*I, 2., 3. + 3.*I, 4.}; - using multi::blas::iamax; - BOOST_REQUIRE( iamax(A) == 2 ); - } - { - multi::cuda::managed::array const A = {1. + 2.*I, 2., 3. + 3.*I, 4.}; - using multi::blas::iamax; - BOOST_REQUIRE( iamax(A) == 2 ); - } -} -#endif - -template void what(T&&) = delete; - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_dot){ - using complex = std::complex; - complex const I{0,1}; - multi::array const A = {1. + 2.*I, 2., 3. + 3.*I, 4.}; - multi::array const B = {2. + 3.*I, 4., 5. + 6.*I, 7.}; - namespace blas = multi::blas; - { - multi::cuda::array const A_gpu = A, B_gpu = B; - using blas::dot; - BOOST_REQUIRE( dot(blas::C(A_gpu), B_gpu) == dot(blas::C(A), B) ); - } - { - multi::cuda::managed::array const A_mng = A, B_mng = B; - using blas::dot; - BOOST_REQUIRE( dot(blas::C(A_mng), A_mng) == dot(blas::C(A), A) ); - } -} - - -#endif -#endif - diff --git a/include/multi/adaptors/blas/cuda/tests/gemm.cpp b/include/multi/adaptors/blas/cuda/tests/gemm.cpp deleted file mode 100644 index 5bdaf7b63..000000000 --- a/include/multi/adaptors/blas/cuda/tests/gemm.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS -/usr/local/cuda-11.1/bin/nvcc -x cu -std=c++17 -use_fast_math -lpthread -D_REENTRANT -DBOOST_PP_VARIADICS -Xcudafe "--diag_suppress=implicit_return_from_non_void_function" --extended-lambda --expt-relaxed-constexpr $0 -o $0x `pkg-config --cflags --libs cudart-11.0 cublas-11.0 blas` -lboost_unit_test_framework -DBOOST_LOG_DYN_LINK -lboost_log -lboost_thread -lboost_system -lboost_log_setup -lpthread -lboost_timer&&$0x&&rm $0x; exit -#endif -// © Alfredo A. Correa 2020-2021 - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuBLAS gemm" -#define BOOST_TEST_DYN_LINK -#include -#include - -//#include"boost/log/trivial.hpp" -//#define MULTI_MARK_SCOPE(MsG) BOOST_LOG_TRIVIAL(trace)< - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_gemm_complex_3x2_3x2){ - using complex = std::complex; complex const I{0, 1}; - namespace blas = multi::blas; - multi::array const a = { - {1. + 2.*I, 5. + 2.*I}, - {9. - 1.*I, 9. + 1.*I}, - {1. + 1.*I, 2. + 2.*I} - }; - multi::array const b = { - { 11. - 2.*I, 5. + 2.*I}, - { 7. - 3.*I, 2. + 1.*I}, - { 8. - 1.*I, 1. + 1.*I} - }; - { - { - multi::array c({2, 2}); - c = blas::gemm(1., blas::H(a), b); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 125.-84.*I ); - } - } - { - multi::cuda::array const a_gpu = a; - multi::cuda::array const b_gpu = b; - { - multi::cuda::array c_gpu({2, 2}); - c_gpu = blas::gemm(1., blas::H(a_gpu), b_gpu); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); - } - { - auto c_gpu =+ blas::gemm(1.0, blas::H(a_gpu), b_gpu); - BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); - } - } - { - multi::cuda::managed::array const a_gpu = a; - multi::cuda::managed::array const b_gpu = b; - { - multi::cuda::managed::array c_gpu({2, 2}); - blas::gemm(1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); - } - { - auto c_gpu =+ blas::gemm(1.0, blas::H(a_gpu), b_gpu); - BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); - } - } -} - -//BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_gemm_complex_3x2_3x2_with_context){ -// using complex = std::complex; complex const I{0, 1}; -// namespace blas = multi::blas; -// multi::array const a = { -// {1. + 2.*I, 5. + 2.*I}, -// {9. - 1.*I, 9. + 1.*I}, -// {1. + 1.*I, 2. + 2.*I} -// }; -// multi::array const b = { -// { 11. - 2.*I, 5. + 2.*I}, -// { 7. - 3.*I, 2. + 1.*I}, -// { 8. - 1.*I, 1. + 1.*I} -// }; -// { -// { -// multi::blas::context ctx; -// multi::array c({2, 2}); -// blas::gemm(ctx, 1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ -// BOOST_REQUIRE( c[1][0] == 125.-84.*I ); -// } -// } -// { -// multi::cublas::context ctx; -// multi::cuda::array const a_gpu = a; -// multi::cuda::array const b_gpu = b; -// { -// multi::cuda::array c_gpu({2, 2}); -// blas::gemm(ctx, 1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆ -// BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); -// } -// { -// auto c_gpu =+ blas::gemm(&ctx, blas::H(a_gpu), b_gpu); -// BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); -// } -// } -// { -// multi::cublas::context ctx; -// multi::cuda::managed::array const a_gpu = a; -// multi::cuda::managed::array const b_gpu = b; -// { -// multi::cuda::managed::array c_gpu({2, 2}); -// blas::gemm(ctx, 1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆ -// BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); -// } -// { -// auto c_gpu =+ blas::gemm(&ctx, blas::H(a_gpu), b_gpu); -// BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I ); -// } -// } -//} - -#if 0 -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_gemm_context_timing){ - using complex = std::complex;//complex const I{0, 1}; - - multi::array A({1000, 1000}); - multi::array B( {1000, 1000}); - multi::array C({size(A), size(~B)}); - A[99][99] = B[11][22] = C[33][44] = 1.0; - std::cerr<< "memory " << (A.num_elements()+ B.num_elements() + C.num_elements())*sizeof(complex)/1e6 <<" MB"<{0., 10.}, g=std::mt19937{}]() mutable{return complex{d(g), d(g)};}; - std::generate(A.elements().begin(), A.elements().end(), rand); - std::generate(B.elements().begin(), B.elements().end(), rand); - } - namespace blas = multi::blas; - { - boost::timer::auto_cpu_timer t; // 2.398206s - for(auto i = 0; i != 10; ++i){ - blas::context ctx; - blas::gemm(ctx, 1, A, B, 0, C); - } - } - using device_array = multi::cuda::array; - { - device_array A_gpu = A, B_gpu = B, C_gpu({size(A), size(~B)}); - - boost::timer::auto_cpu_timer t; // 0.707426s - for(auto i = 0; i != 10; ++i){ - multi::cublas::context ctx; - blas::gemm(ctx, 1, A_gpu, B_gpu, 0, C_gpu); - } - } - { - device_array A_gpu = A, B_gpu = B, C_gpu({size(A), size(~B)}); - - boost::timer::auto_cpu_timer t; // 0.613534s - multi::cublas::context ctx; - for(auto i = 0; i != 10; ++i) blas::gemm(ctx, 1, A_gpu, B_gpu, 0, C_gpu); - } -} -#endif - diff --git a/include/multi/adaptors/blas/cuda/tests/iamax.cpp b/include/multi/adaptors/blas/cuda/tests/iamax.cpp deleted file mode 100644 index 52f49c288..000000000 --- a/include/multi/adaptors/blas/cuda/tests/iamax.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS -$CXXX $CXXFLAGS $0 -o $0x `pkg-config --libs blas` -Wno-deprecated-declarations `pkg-config --cflags --libs cudart-11.0 cublas-11.0 blas` -lboost_unit_test_framework&&$0x&&rm $0x; exit -#endif -// © Alfredo A. Correa 2019-2020 - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuBLAS iamax" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../../../adaptors/blas.hpp" -#include "../../../../adaptors/cuda.hpp" -#include "../../../../adaptors/blas/cuda.hpp" - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_iamax){ - using complex = std::complex; complex const I{0, 1}; - { - multi::array const A = {1. + 2.*I, 2., 3. + 3.*I, 4.}; - using multi::blas::iamax; - BOOST_REQUIRE( iamax(A) == 2 ); - } - { - multi::cuda::array const A = {1. + 2.*I, 2., 3. + 3.*I, 4.}; - using multi::blas::iamax; - BOOST_REQUIRE( iamax(A) == 2 ); - } - { - multi::cuda::managed::array const A = {1. + 2.*I, 2., 3. + 3.*I, 4.}; - using multi::blas::iamax; - BOOST_REQUIRE( iamax(A) == 2 ); - } -} - diff --git a/include/multi/adaptors/blas/filling.hpp b/include/multi/adaptors/blas/filling.hpp deleted file mode 100644 index 29b21eb6d..000000000 --- a/include/multi/adaptors/blas/filling.hpp +++ /dev/null @@ -1,31 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_FILLING_HPP -#define MULTI_ADAPTORS_BLAS_FILLING_HPP - -#include - -#include -#include - -namespace boost::multi::blas { - -enum class filling : char { - lower = 'U', - upper = 'L' -}; - -inline auto flip(filling side) -> filling { - switch(side) { - case filling::lower: return filling::upper; - case filling::upper: return filling::lower; - } __builtin_unreachable(); // LCOV_EXCL_LINE -} - -inline auto operator-(filling side) -> filling {return flip(side);} -inline auto operator+(filling side) -> filling {return side;} - -} // end namespace boost::multi::blas - -#endif diff --git a/include/multi/adaptors/blas/gemm.hpp b/include/multi/adaptors/blas/gemm.hpp deleted file mode 100644 index c4621aa76..000000000 --- a/include/multi/adaptors/blas/gemm.hpp +++ /dev/null @@ -1,313 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_GEMM_HPP -#define MULTI_ADAPTORS_BLAS_GEMM_HPP - -#include "../blas/core.hpp" -#include "../blas/gemv.hpp" -#include "../blas/numeric.hpp" -#include "../blas/operations.hpp" - -namespace boost::multi::blas { - -using core::gemm; - -template -auto xbase_aux(It const& it, std::true_type const& /*true */) -->decltype(underlying(base(it))) { - return underlying(base(it)); } - -template -auto xbase_aux(It const& it, std::false_type const& /*false*/) -->decltype(base(it)) { - return base(it); } - -template -auto xbase(It const& it) -->decltype(xbase_aux(it, std::integral_constant{}>{})) { - return xbase_aux(it, std::integral_constant{}>{}); } - -template -auto gemm_n(Context&& ctxt, typename It2DA::element alpha, It2DA a_first, Size a_count, It2DB b_first, typename It2DA::element beta, It2DC c_first) // NOLINT(readability-function-cognitive-complexity) : 125 -//->decltype(std::forward(ctxt).gemm('N', 'N', b_first->size(), a_count, a_first->size(), &alpha, xbase(b_first), b_first->size() , xbase(a_first), a_first->size(), &beta, c_first.base(), c_first->size() ), It2DC{}) -try { - assert( b_first->size() == c_first->size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - assert( a_first.stride()==1 or a_first->stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - assert( b_first.stride()==1 or b_first->stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - assert( c_first.stride()==1 or c_first->stride()==1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - - if(a_count != 0){ - #define CTXT std::forward(ctxt) - if constexpr (!is_conjugated{} and !is_conjugated{}){ - if (a_first->stride()==1 and b_first->stride()==1 and c_first->stride()==1) { - if ( a_count==1 and b_first->size()==1 ) {CTXT->gemm('N', 'N', b_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first->size() , base(a_first), a_first->size() , &beta, base(c_first), c_first->size() );} - else if( a_count==1 ) {CTXT->gemm('N', 'N', b_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first. stride(), base(a_first), a_first->size() , &beta, base(c_first), c_first->size() );} - else {CTXT->gemm('N', 'N', b_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first. stride(), base(a_first), a_first. stride(), &beta, base(c_first), c_first. stride());} - }else if(a_first->stride()==1 and b_first->stride()==1 and c_first. stride()==1) { - if (a_count==1) {CTXT->gemm('T', 'T', a_count, b_first->size(), a_first->size(), &alpha, base(a_first), a_first. stride(), base(b_first), b_first->size() , &beta, base(c_first), a_first->size() );} - else {CTXT->gemm('T', 'T', a_count, b_first->size(), a_first->size(), &alpha, base(a_first), a_first. stride(), base(b_first), b_first. stride(), &beta, base(c_first), c_first->stride());} - }else if(a_first. stride()==1 and b_first->stride()==1 and c_first->stride()==1) { - if (a_count==1) {CTXT->gemm('N', 'T', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first. stride(), base(a_first), a_first->stride(), &beta, base(c_first), a_count );} - else {CTXT->gemm('N', 'T', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first. stride(), base(a_first), a_first->stride(), &beta, base(c_first), c_first.stride());} - }else if(a_first. stride()==1 and b_first->stride()==1 and c_first. stride()==1) { - if (a_count==1) {CTXT->gemm('N', 'T', a_count, b_first->size(), a_first->size(), &alpha, base(a_first), a_first->stride(), base(b_first), a_first->size() , &beta, base(c_first), b_first->size() );} - else {CTXT->gemm('N', 'T', a_count, b_first->size(), a_first->size(), &alpha, base(a_first), a_first->stride(), base(b_first), b_first. stride(), &beta, base(c_first), c_first->stride());} - }else if(a_first->stride()==1 and b_first.stride()==1 and c_first. stride()==1) { - if (a_count==1 and b_first->size()==1 ) {CTXT->gemm('N', 'N', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first->size() , base(a_first), a_first->size() , &beta, base(c_first), c_first->stride());} - else if(a_count==1) {CTXT->gemm('N', 'T', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first->stride(), base(a_first), a_first->size() , &beta, base(c_first), c_first->stride());} - else if(a_first->size() == 1 and b_first->size() == 1) - {CTXT->gemm('N', 'N', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first->stride(), base(a_first), a_first. stride(), &beta, base(c_first), c_first->stride());} - else {CTXT->gemm('N', 'T', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first->stride(), base(a_first), a_first. stride(), &beta, base(c_first), c_first->stride());} - }else if(a_first->stride()==1 and b_first. stride()==1 and c_first->stride()==1) { - if (a_count==1) {CTXT->gemm('T', 'N', a_count, c_first->size(), a_first->size(), &alpha, base(b_first), b_first->stride(), base(a_first), a_first->size() , &beta, base(c_first), c_first. stride());} - else {CTXT->gemm('T', 'N', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first->stride(), base(a_first), a_first. stride(), &beta, base(c_first), c_first. stride());} - }else if(a_first. stride()==1 and b_first.stride( )==1 and c_first. stride()==1) { - if (b_first->size()==1) {CTXT->gemm('N', 'N', a_count, b_first->size(), a_first->size(), &alpha, base(a_first), a_first->stride(), base(b_first), b_first->stride(), &beta, base(c_first), a_count );} - else {CTXT->gemm('N', 'N', a_count, b_first->size(), a_first->size(), &alpha, base(a_first), a_first->stride(), base(b_first), b_first->stride(), &beta, base(c_first), c_first->stride());} - }else if(a_first. stride()==1 and b_first.stride( )==1 and c_first->stride()==1) { - {CTXT->gemm('T', 'T', b_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first->stride(), base(a_first), a_first->stride(), &beta, base(c_first), c_first. stride());} - } else {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - - }else if constexpr(!is_conjugated{} and is_conjugated{}){ - if (a_first->stride()==1 and b_first->stride()==1 and c_first->stride()==1){ - // TODO(correaa) : check why these two branches are identical - /* if(b_first->size()==1)*/{CTXT->gemm('C', 'N', c_first->size(), a_count, a_first->size(), &alpha, underlying(base(b_first)), b_first->stride(), base(a_first), a_first->size() , &beta, base(c_first), c_first.stride());} - /* else {CTXT->gemm('C', 'N', c_first->size(), a_count, a_first->size(), &alpha, underlying(base(b_first)), b_first->stride(), base(a_first), a_first->size() , &beta, base(c_first), c_first.stride());}*/ - }else if(a_first->stride()==1 and b_first. stride()==1 and c_first->stride()==1){ - if (a_count==1) {CTXT->gemm('C', 'N', a_count, c_first->size(), a_first->size(), &alpha, underlying(base(b_first)), b_first->stride(), base(a_first), a_first->size() , &beta, base(c_first), c_first.stride());} - else {CTXT->gemm('C', 'N', c_first->size(), a_count, a_first->size(), &alpha, underlying(base(b_first)), b_first->stride(), base(a_first), a_first.stride(), &beta, base(c_first), c_first.stride());} - }else if(a_first->stride()==1 and b_first. stride()==1 and c_first. stride()==1){ - {CTXT->gemm('C', 'N', c_first->size(), a_count, a_first->size(), &alpha, underlying(base(b_first)), b_first->stride(), base(a_first), a_first. stride(), &beta, base(c_first), c_first->stride());} - }else if(a_first. stride()==1 and b_first. stride()==1 and c_first. stride()==1){ - {CTXT->gemm('C', 'T', c_first->size(), a_count, a_first->size(), &alpha, underlying(base(b_first)), b_first->stride(), base(a_first), a_first->stride(), &beta, base(c_first), c_first->stride());} - }else if(a_first. stride()==1 and b_first. stride()==1 and c_first->stride()==1){ - {CTXT->gemm('C', 'T', a_count, c_first->size(), a_first->size(), &alpha, underlying(base(b_first)), b_first->stride(), base(a_first), a_first->stride(), &beta, base(c_first), c_first. stride());} - }else{assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - }else if constexpr( is_conjugated{} and !is_conjugated{}){ - if (a_first. stride()==1 and b_first->stride()==1 and c_first->stride()==1){ - if (a_count==1) {CTXT->gemm('N', 'C', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first. stride(), underlying(base(a_first)), a_first->stride(), &beta, base(c_first), a_first->size() );} - else {CTXT->gemm('N', 'C', c_first->size(), a_count, a_first->size(), &alpha, base(b_first), b_first. stride(), underlying(base(a_first)), a_first->stride(), &beta, base(c_first), c_first.stride());} - }else {throw std::logic_error{"not BLAS-implemented"};} - }else if constexpr( is_conjugated{} and is_conjugated{}){ - if (a_first. stride()==1 and b_first. stride()==1 and c_first->stride()==1){ - {CTXT->gemm('C', 'C', a_count, c_first->size(), a_first->size(), &alpha, underlying(base(b_first)), b_first->stride(), underlying(base(a_first)), a_first->stride(), &beta, base(c_first), c_first. stride());} - }else {throw std::logic_error{"not BLAS-implemented"};} - } - #undef CTXT - } - return c_first + a_count; -} catch(std::logic_error& e) { - using std::to_string; - throw std::logic_error{ - "couldn't do "+std::string(__PRETTY_FUNCTION__)+" of layout a_count="+std::to_string(a_count) // NOLINT(fuchsia-default-arguments-calls,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - +" a_strides="+ to_string(a_first.stride()) +","+ to_string(a_first->stride()) +" a->size="+ to_string(a_first->size()) - +" b_strides="+ to_string(b_first.stride()) +","+ to_string(b_first->stride()) +" b->size="+ to_string(b_first->size()) - +" c_strides="+ to_string(c_first.stride()) +","+ to_string(c_first->stride()) +" c->size="+ to_string(c_first->size()) - +" because " + e.what() - }; -} - -template // TODO(correaa) automatic deduction of context -auto gemm_n(typename It2DA::element alpha, It2DA a_first, Size a_count, It2DB b_first, typename It2DA::element beta, It2DC c_first) -->decltype(gemm_n(Context{}, alpha, a_first, a_count, b_first, beta, c_first)) { - return gemm_n(Context{}, alpha, a_first, a_count, b_first, beta, c_first); } - -template -auto gemm(Context&& ctx, typename A::element alpha, A const& a, B const& b, typename A::element beta, C&& c) -> C&& { // NOLINT(readability-identifier-length) BLAS naming - assert( size( a) == size( c) ); - if(not a.is_empty()) {assert( size(~a) == size( b) );} - if constexpr(is_conjugated{}) {blas::gemm (std::forward(ctx), conj(alpha), conj(a), conj(b) , conj(beta), conj(c) );} - else {blas::gemm_n(std::forward(ctx), alpha , begin(a), size(a), begin(b), beta , begin(c));} - return std::forward(c); -} - -template -auto gemm(typename A::element alpha, A const& a, B const& b, typename A::element beta, C&& c) -> C&& { // NOLINT(readability-identifier-length) BLAS naming - if constexpr(is_conjugated{}) { - auto ctxt = blas::default_context_of(underlying(a.base())); - return gemm(ctxt, alpha, a, b, beta, std::forward(c)); - } else { - auto ctxt = blas::default_context_of(a.base()); - return gemm(ctxt, alpha, a, b, beta, std::forward(c)); - } -} - -template -class gemm_range; - -template -class gemm_reference { // TODO(correaa) implement this in terms of gemv_range - Ext exts_; - - public: - explicit gemm_reference(Ext exts) : exts_{std::move(exts)} {} - auto extensions() const {return exts_;} - friend auto extensions(gemm_reference const& self) {return self.extensions();} -}; - -template -class gemm_iterator { - ContextPtr ctxtp_; - Scalar s_; - ItA a_it_; - ItB b_begin_; - gemm_iterator(ContextPtr ctxtp, Scalar s, ItA a_it, ItB b_begin) : ctxtp_{ctxtp}, s_{s}, a_it_{std::move(a_it)}, b_begin_{std::move(b_begin)} {} // NOLINT(readability-identifier-length) BLAS naming - template - friend class gemm_range; - - public: - gemm_iterator(gemm_iterator const&) = default; - gemm_iterator(gemm_iterator&&) noexcept = default; - ~gemm_iterator() = default; - auto operator=(gemm_iterator&&) -> gemm_iterator& = delete; - auto operator=(gemm_iterator const&) -> gemm_iterator& = delete; - - using difference_type = typename std::iterator_traits::difference_type; - using value_type = typename std::iterator_traits::value_type; - using pointer = void*; - using reference = gemm_referenceextensions())>; - using iterator_category = std::random_access_iterator_tag; // using iterator_category = std::input_iterator_tag; - - static_assert( std::is_base_of::iterator_category>{} ); - - auto operator+=(difference_type n) -> gemm_iterator& {a_it_ += n; return *this;} - auto operator-=(difference_type n) -> gemm_iterator& {a_it_ -= n; return *this;} - - auto operator++() -> gemm_iterator& {return operator+=(1);} // required by random access concept requires even if not used explicitly - auto operator--() -> gemm_iterator& {return operator-=(1);} - - auto operator+(difference_type n) const {gemm_iterator ret{*this}; ret+=n; return ret;} - - friend auto operator-(gemm_iterator const& a, gemm_iterator const& b) -> difference_type { // NOLINT(readability-identifier-length) BLAS naming - assert(a.b_begin_ == b.b_begin_); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - return a.a_it_ - b.a_it_; - } - friend auto operator==(gemm_iterator const& self, gemm_iterator const& other) -> bool {return self.a_it_ == other.a_it_;} - friend auto operator!=(gemm_iterator const& self, gemm_iterator const& other) -> bool {return self.a_it_ != other.a_it_;} - - template - friend auto copy_n(gemm_iterator const& first, difference_type count, ItOut d_first) - ->decltype(blas::gemm_n(std::declval(), std::declval() , std::declval(), count, std::declval(), 0.0, d_first)) try { // std::complex NOLINT(fuchsia-default-arguments-calls) - return blas::gemm_n(first.ctxtp_ , static_cast(first.s_), first.a_it_ , count, first.b_begin_ , 0.0, d_first); // NOLINT(fuchsia-default-arguments-calls) - } catch(std::exception const& e) { - using namespace std::string_literals; - throw std::logic_error( - "in "s + __PRETTY_FUNCTION__ +"\nCouldn't decay product of arrays of size "+ std::to_string(count) +"x"+ std::to_string(first.a_it_->size()) + " and " + // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - std::to_string(first.a_it_->size())+ "x" +std::to_string(first.b_begin_->size()) + " into " + std::to_string(count) +"x" + std::to_string(first.b_begin_->size()) + - "\nbecause\n" + e.what() - ); - } - - template - friend auto copy(gemm_iterator const& first, gemm_iterator const& last, ItOut d_first) {assert(first.s_ == last.s_); - return copy_n(first, last - first, d_first); - } - - template - friend auto uninitialized_copy_n(gemm_iterator const& first, difference_type count, ItOut d_first) { - return copy_n(first, count, d_first); - } - - template - friend auto uninitialized_copy(gemm_iterator const& first, gemm_iterator const& last, ItOut d_first) { - assert( first.s_ == last.s_ ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - return uninitialized_copy_n(first, last - first, d_first); - } - - auto operator*() const {return reference{b_begin_->extensions()};} -}; - -template -class gemm_range { - ContextPtr ctxtp_; - Scalar s_; - ItA a_begin_; - ItA a_end_; - ItB b_begin_; - - public: - gemm_range(gemm_range const&) = delete; - gemm_range(gemm_range&&) = delete; - auto operator=(gemm_range const&) -> gemm_range& = delete; - auto operator=(gemm_range&&) -> gemm_range& = delete; - ~gemm_range() = default; - - gemm_range(ContextPtr ctxtp, Scalar s, ItA a_first, ItA a_last, ItB b_first) // NOLINT(bugprone-easily-swappable-parameters,readability-identifier-length) BLAS naming - : ctxtp_{ctxtp} - , s_{s}, a_begin_{std::move(a_first)}, a_end_{std::move(a_last)} - , b_begin_{std::move(b_first)} - {} - - using iterator = gemm_iterator; - using decay_type = DecayType; - using size_type = typename decay_type::size_type; - - auto begin() const& -> iterator {return {ctxtp_, s_, a_begin_, b_begin_};} - auto end() const& -> iterator {return {ctxtp_, s_, a_end_ , b_begin_};} - friend auto begin(gemm_range const& self) {return self.begin();} - friend auto end (gemm_range const& self) {return self.end ();} - - auto size() const -> size_type {return a_end_ - a_begin_;} - auto extensions() const -> typename decay_type::extensions_type {return size()*b_begin_->extensions();} - friend auto extensions(gemm_range const& self) {return self.extensions();} -// operator decay_type() const{return decay_type(*this);} // do not use curly { } - auto operator+() const -> decay_type {return *this;} // TODO(correaa) : investigate why return decay_type{*this} doesn't work - template - friend auto operator+=(Arr&& a, gemm_range const& self) -> Arr&& { // NOLINT(readability-identifier-length) BLAS naming - blas::gemm_n(self.ctxtp_, self.s_, self.a_begin_, self.a_end_ - self.a_begin_, self.b_begin_, 1., a.begin()); - return std::forward(a); - } - friend auto operator*(Scalar factor, gemm_range const& self) { - return gemm_range{self.ctxtp_, factor*self.s_, self.a_begin_, self.a_end_, self.b_begin_}; - } -}; - -template())>{}> > -auto gemm(ContextPtr ctxtp, Scalar s, A2D const& a, B2D const& b) // NOLINT(readability-identifier-length) BLAS naming -->gemm_range -{ - return {ctxtp, s, begin(a), end(a), begin(b)}; -} - -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -template -auto gemm(Scalar s, A2D const& a, B2D const& b) { // NOLINT(readability-identifier-length) conventional BLAS naming - if constexpr(is_conjugated{}) { - auto ctxtp = blas::default_context_of(underlying(a.base())); - return blas::gemm(ctxtp, s, a, b); - } else { - auto ctxtp = blas::default_context_of(a.base()); - return blas::gemm(ctxtp, s, a, b); - } -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #pragma diagnostic pop - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -namespace operators { - template =0> - auto operator*(A2D const& A, B2D const& B) // NOLINT(readability-identifier-length) conventional BLAS names - ->decltype(blas::gemm(1.0, A, B)) { - return blas::gemm(1.0, A, B); } -} // end namespace operators - -} // end namespace boost::multi::blas -#endif diff --git a/include/multi/adaptors/blas/ger.hpp b/include/multi/adaptors/blas/ger.hpp deleted file mode 100644 index 719f5c511..000000000 --- a/include/multi/adaptors/blas/ger.hpp +++ /dev/null @@ -1,234 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -// Copyright 2019-2021 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_GER_HPP -#define MULTI_ADAPTORS_BLAS_GER_HPP - -#include "../blas/core.hpp" - -namespace boost::multi::blas { - -using core::ger; - -template -auto ger_n(T alpha, It1 x_first, Size1 x_n, It2 y_first, Size2 y_n, Out A_first) -> Out { - assert( A_first->size() == x_n ); - assert( A_first->stride() == 1 ); - ger(x_n, y_n, alpha, base(x_first), stride(x_first), base(y_first), stride(y_first), base(A_first), stride(A_first)); - return A_first + y_n; -} - -template -auto ger(T alpha, It1 x_first, It1 x_last, It2 y_first, It2 y_last, Out A_first) -> Out { - assert( stride(x_first) == stride(x_last) ); - assert( stride(y_first) == stride(y_last) ); - return ger_n(alpha, x_first, std::distance(x_first, x_last), y_first, std::distance(y_first, y_last), A_first); -} - -template -auto ger(T alpha, X1D const& x, Y1D const& y, A2D&& A) -> A2D&& { - if(stride(A) == 1) { - auto e = ger(alpha, begin(y), end(y), begin(x), end(x), begin(rotated(A))); - assert( end(rotated(A)) == e ); - } else { - assert( size(A) == size(y) ); - auto e = ger(alpha, begin(x), end(x), begin(y), end(y), begin(A)); - assert( end(A) == e ); - } - return std::forward(A); -} - -template -auto gerc_n(T alpha, It1 x_first, Size1 x_n, It2 y_first, Size2 y_n, Out A_first) -> Out { - assert( A_first->size() == x_n ); - assert( A_first->stride() == 1 ); - gerc(x_n, y_n, alpha, base(x_first), stride(x_first), base(y_first), stride(y_first), base(A_first), stride(A_first)); - return A_first + y_n; -} - -template -auto gerc(T alpha, It1 x_first, It1 x_last, It2 y_first, It2 y_last, Out A_first) -> Out { - assert( stride(x_first) == stride(x_last) ); - assert( stride(y_first) == stride(y_last) ); - return gerc_n(alpha, x_first, std::distance(x_first, x_last), y_first, std::distance(y_first, y_last), A_first); -} - -template -auto gerc(T alpha, X1D const& x, Y1D const& y, A2D&& A) -> A2D { - if(stride(A) == 1) { - auto e = gerc(alpha, begin(y), end(y), begin(x), end(x), begin(rotated(A))); - assert( end(rotated(A)) == e ); - } else { - assert( size(A) == size(y) ); - auto e = gerc(alpha, begin(x), end(x), begin(y), end(y), begin(A)); - assert( end(A) == e ); - } - return A; -} - -template -auto geru_n(T alpha, It1 x_first, Size1 x_n, It2 y_first, Size2 y_n, Out A_first) -> Out { - assert( A_first->size() == x_n ); - assert( A_first->stride() == 1 ); - geru(x_n, y_n, alpha, base(x_first), stride(x_first), base(y_first), stride(y_first), base(A_first), stride(A_first)); - return A_first + y_n; -} - -template -auto geru(T alpha, It1 x_first, It1 x_last, It2 y_first, It2 y_last, Out A_first) -> Out { - assert( stride(x_first) == stride(x_last) ); - assert( stride(y_first) == stride(y_last) ); - return geru_n(alpha, x_first, std::distance(x_first, x_last), y_first, std::distance(y_first, y_last), A_first); -} - -template -auto geru(T alpha, X1D const& x, Y1D const& y, A2D&& A) -> A2D { - if(stride(A) == 1) { - auto e = geru(alpha, begin(y), end(y), begin(x), end(x), begin(rotated(A))); - assert( end(rotated(A)) == e ); - } else { - assert( size(A) == size(y) ); - auto e = geru(alpha, begin(x), end(x), begin(y), end(y), begin(A)); - assert( end(A) == e ); - } - return A; -} - -} // end namespace boost::multi::blas - -//#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ - -//#define BOOST_TEST_MODULE "C++ Unit Tests for Multi blas ger" -//#define BOOST_TEST_DYN_LINK -//#include - -//#include "../../array.hpp" -//#include "../../utility.hpp" - -//#include -//#include -//#include -//#include -//#include - -//using std::cout; -//namespace multi = boost::multi; - -//BOOST_AUTO_TEST_CASE(multi_blas_ger){ -// namespace blas = multi::blas; -// { -// multi::array A = { -// {0., 0. ,0.}, -// {0., 0., 0.} -// }; -// multi::array const x = { 0., 0., 1.}; -// multi::array const y = { 0., 1.}; -// blas::ger(1., x, y, A); // A = a*A + (y^T)(x) -// for(int i = 0; i != size(A); ++i){ -// for(int j = 0; j != size(A[i]); ++j) -// std::cout << A[i][j] << ' '; -// std::cout << std::endl; -// } -// std::cout << std::endl; -// // a = {{2., 3.}, {1., 4.}, {1., 0.}}; GER[1, {1., 2., 5.}, {-2., 1.}, a]; Print[a] : {{0., 4.}, {-3., 6.}, {-9., 5.}} -// // assert( A[1][1] == 6. ); -// } -// { -// multi::array A = { -// {0., 0.}, -// {0., 0.}, -// {0., 0.} -// }; -// multi::array const x = {0., 0., 1.}; -// multi::array const y = {0., 1.}; -// blas::ger(1., x, y, rotated(A)); // A^T = a*A^T + (y^T)(x) and A = a*A + (x^T)y -// // a = {{2., 3.}, {1., 4.}, {1., 0.}}; GER[1, {1., 2., 5.}, {-2., 1.}, a]; Print[a] : {{0., 4.}, {-3., 6.}, {-9., 5.}} -// for(int i = 0; i != size(A); ++i){ -// for(int j = 0; j != size(A[i]); ++j) -// std::cout << A[i][j] << ' '; -// std::cout << std::endl; -// } -//// std::cout << A[1][2] << std::endl; -//// assert( A[1][2] == 1. ); -// } -// { -//// multi::array A = { -//// {2., 3., 6., 8.}, -//// {4., 1., 6., 8.}, -//// {0., 1., 6., 8.} -//// }; -//// assert( A[1][2] == 6. ); -//// multi::array const x = { 0., 1., 0.}; -//// multi::array const y = { 0., 0., 1., 0.}; -// -// // multi::blas::ger(0., x, y, rotated(A)); // - -// // a = {{2., 3.}, {1., 4.}, {1., 0.}}; GER[1, {1., 2., 5.}, {-2., 1.}, a]; Print[a] : {{0., 4.}, {-3., 6.}, {-9., 5.}} -// // assert( A[1][1] == 4. ); -// } -// { -// multi::array A = { -// {2., 3., 6., 8.}, -// {4., 1., 6., 8.}, -// {0., 1., 6., 8.} -// }; -// multi::array const x = { 1., 2., 5.}; -// multi::array const y = {-2., 1., 1., 1.}; -// blas::ger(1., x, y, A); // -// // a = {{2., 3.}, {1., 4.}, {1., 0.}}; GER[1, {1., 2., 5.}, {-2., 1.}, a]; Print[a] : {{0., 4.}, {-3., 6.}, {-9., 5.}} -// // assert( A[1][1] == 4. ); -// } -// { -// multi::array a = { -// {2., 1., 1.}, -// {3., 4., 0.} -// }; -// multi::array const x = { 1., 2., 5.}; -// multi::array const y = {-2., 1.}; -// blas::ger(1., x, y, rotated(a)); -// // a = {{2., 3.}, {1., 4.}, {1., 0.}}; GER[1, {1., 2., 5.}, {-2., 1.}, a]; Print[a] : {{0., 4.}, {-3., 6.}, {-9., 5.}} -// assert( a[1][1] == 6. ); -// } -//#if 0 -// { -// multi::array, 2> a = { -// {2., 3.}, -// {1., 4.}, -// {1.,0.} -// }; -// multi::array, 1> const x = { 1., 2., 5.}; -// multi::array, 1> const y = {-2., 1.}; -// multi::blas::gerc(1., x, y, a); -// // a = {{2., 3.}, {1., 4.}, {1., 0.}}; GER[1, {1., 2., 5.}, {-2., 1.}, a]; Print[a] : {{0., 4.}, {-3., 6.}, {-9., 5.}} -// assert( a[1][1] == 6. ); -// } -// { -// multi::array, 2> a = {{2. + 1.*I, 3. + 4.*I}, {1.+3.*I, 4. + 2.*I}, {1. + 7.*I, 0.}}; -// multi::array, 1> const x = { 1. + 1.*I, 2. + I*9., 5. + 4.*I}; -// multi::array, 1> const y = {-2. + 8.*I, 1. + 1.*I}; -// multi::blas::geru(1. + 2.*I, x, y, a); // a = alpha*outer(x, y) + a -//// a = {{2. + 1.*I, 3. + 4.*I}, {1. + 3.*I, 4. + 2.*I}, {1. + 7.*I, 0.}}; GER[1 + 2.*I, {1. + 1.*I, 2. + I*9., 5. + 4.*I}, {-2. + 8.*I, 1. + 1.*I}, a]; Print[a]; -//// {{-20.-13. I,-1.+6. I},{-71.-151. I,-25.-1. I},{-105.-45. I,-17.+11. I}} -// std::cout << "a11 " << a[1][1] << std::endl; -// assert( a[1][1] == -25. - 1.*I ); -// } -// { -// multi::array, 2> a = { -// {2. + 1.*I, 1. + 3.*I, 1. + 7.*I}, -// {3. + 4.*I, 4. + 2.*I, 0. + 0.*I} -// }; -// std::cout << "a = " << size(a) << std::endl; -// multi::array, 1> const x = { 1. + 1.*I, 2. + I*9., 5. + 4.*I}; -// multi::array, 1> const y = {-2. + 8.*I, 1. + 1.*I}; -// multi::blas::geru(1. + 2.*I, x, y, rotated(a)); // a = alpha*outer(x, y) + a -//// a = {{2. + 1.*I, 3. + 4.*I}, {1. + 3.*I, 4. + 2.*I}, {1. + 7.*I, 0.}}; GER[1 + 2.*I, {1. + 1.*I, 2. + I*9., 5. + 4.*I}, {-2. + 8.*I, 1. + 1.*I}, a]; Print[a]; -//// {{-20.-13. I,-1.+6. I},{-71.-151. I,-25.-1. I},{-105.-45. I,-17.+11. I}} -// std::cout << "here a11 " << a[1][1] << std::endl; -// assert( a[1][1] == -25. - 1.*I ); -// } -//#endif - -//} - -//#endif -#endif diff --git a/include/multi/adaptors/blas/herk.hpp b/include/multi/adaptors/blas/herk.hpp deleted file mode 100644 index f58568cc2..000000000 --- a/include/multi/adaptors/blas/herk.hpp +++ /dev/null @@ -1,117 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_HERK_HPP -#define MULTI_ADAPTORS_BLAS_HERK_HPP - -#include "../blas/copy.hpp" -#include "../blas/core.hpp" -#include "../blas/filling.hpp" -#include "../blas/operations.hpp" -#include "../blas/side.hpp" -#include "../blas/syrk.hpp" // fallback to real case - -namespace boost::multi::blas { - -template{}, int> =0> -auto base_aux(A&& array) -->decltype(base(array)) { - return base(array); } - -template{}, int> =0> -auto base_aux(A&& array) -->decltype(underlying(base(array))) { - return underlying(base(array)); } - -using core::herk; - -template{}, int> =0> -auto herk(filling c_side, AA alpha, A2D const& a, BB beta, C2D&& c) -> C2D&& { // NOLINT(readability-function-cognitive-complexity,readability-identifier-length) 74, BLAS naming - assert( a.size() == c.size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - assert( c.size() == rotated(c).size() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - if(c.is_empty()) {return std::forward(c);} - if constexpr(is_conjugated{}) { - herk(flip(c_side), alpha, a, beta, hermitized(c)); - } else { - auto base_a = base_aux(a); - auto base_c = base_aux(c); // static_assert( not is_conjugated{}, "!" ); - if constexpr(is_conjugated{}) { - // auto& ctxt = *blas::default_context_of(underlying(a.base())); - // if you get an error here might be due to lack of inclusion of a header file with the backend appropriate for your type of iterator - if (stride(a)==1 and stride(c)!=1) {herk(c_side==filling::upper?'L':'U', 'N', size(c), size(rotated(a)), &alpha, base_a, stride(rotated(a)), &beta, base_c, stride(c));} - else if(stride(a)==1 and stride(c)==1) { - if(size(a)==1) {herk(c_side==filling::upper?'L':'U', 'N', size(c), size(rotated(a)), &alpha, base_a, stride(rotated(a)), &beta, base_c, stride(c));} - else {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - } - else if(stride(a)!=1 and stride(c)==1) {herk(c_side==filling::upper?'U':'L', 'C', size(c), size(rotated(a)), &alpha, base_a, stride( a ), &beta, base_c, stride(rotated(c)));} - else if(stride(a)!=1 and stride(c)!=1) {herk(c_side==filling::upper?'L':'U', 'C', size(c), size(rotated(a)), &alpha, base_a, stride( a ), &beta, base_c, stride( c ));} - else {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - } else { - // auto& ctxt = *blas::default_context_of( a.base() ); - if (stride(a)!=1 and stride(c)!=1) {herk(c_side==filling::upper?'L':'U', 'C', size(c), size(rotated(a)), &alpha, base_a, stride( a ), &beta, base_c, stride(c));} - else if(stride(a)!=1 and stride(c)==1) { - if(size(a)==1) {herk(c_side==filling::upper?'L':'U', 'N', size(c), size(rotated(a)), &alpha, base_a, stride(rotated(a)), &beta, base_c, stride(rotated(c)));} - else {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - } - else if(stride(a)==1 and stride(c)!=1) {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - else if(stride(a)==1 and stride(c)==1) {herk(c_side==filling::upper?'U':'L', 'N', size(c), size(rotated(a)), &alpha, base_a, stride(rotated(a)), &beta, base_c, stride(rotated(c)));} - // else {assert(0);} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - } - } - return std::forward(c); -} - -template{}, int> =0> -auto herk(filling c_side, AA alpha, A2D const& a, BB beta, C2D&& c) // NOLINT(readability-identifier-length) BLAS naming -->decltype(syrk(c_side, alpha, a, beta, std::forward(c))) { - return syrk(c_side, alpha, a, beta, std::forward(c)); } - -template -auto herk(filling c_side, AA alpha, A2D const& a, C2D&& c) // NOLINT(readability-identifier-length) BLAS naming -->decltype(herk(c_side, alpha, a, 0., std::forward(c))) { - return herk(c_side, alpha, a, 0., std::forward(c)); } - -template -auto herk(AA alpha, A2D const& a, C2D&& c) // NOLINT(readability-identifier-length) BLAS naming -->decltype(herk(filling::lower, alpha, a, herk(filling::upper, alpha, a, std::forward(c)))) { - return herk(filling::lower, alpha, a, herk(filling::upper, alpha, a, std::forward(c))); } - -template -auto herk(A2D const& a, C2D&& c) // NOLINT(readability-identifier-length) BLAS naming -->decltype(herk(1., a, std::forward(c))) { - return herk(1., a, std::forward(c)); } - -template -[[nodiscard]] // ("when argument is read-only") -auto herk(AA alpha, A2D const& a) { // NOLINT(readability-identifier-length) BLAS naming - return herk(alpha, a, Ret({size(a), size(a)}));//Ret({size(a), size(a)}));//, get_allocator(a))); -} - -template struct numeric_limits : std::numeric_limits {}; -template struct numeric_limits> : std::numeric_limits> { - static auto quiet_NaN() -> std::complex {auto nana = numeric_limits::quiet_NaN(); return {nana, nana};} // NOLINT(readability-identifier-naming) conventional std name -}; - -template -[[nodiscard]] // ("because argument is read-only")]] -auto herk(filling cs, AA alpha, A2D const& a) // NOLINT(readability-identifier-length) BLAS naming -->std::decay_t< -decltype( herk(cs, alpha, a, Ret({size(a), size(a)}, 0., get_allocator(a))))> { - return herk(cs, alpha, a, Ret({size(a), size(a)}, -#ifdef NDEBUG - numeric_limits::quiet_NaN(), -#endif - get_allocator(a) - )); -} - -template auto herk(filling s, A2D const& a) // NOLINT(readability-identifier-length) BLAS naming -->decltype(herk(s, 1., a)) { - return herk(s, 1., a); } - -template auto herk(A2D const& array) { - return herk(1., array); -} - -} // end namespace boost::multi::blas -#endif diff --git a/include/multi/adaptors/blas/iamax.hpp b/include/multi/adaptors/blas/iamax.hpp deleted file mode 100644 index fe4d40a07..000000000 --- a/include/multi/adaptors/blas/iamax.hpp +++ /dev/null @@ -1,71 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -// Copyright 2019-2021 Alfredo A. Correa -#ifndef MULTI_ADAPTORS_BLAS_IAMAX_HPP -#define MULTI_ADAPTORS_BLAS_IAMAX_HPP - -#include "../blas/core.hpp" - -namespace boost::multi::blas { - -template -auto iamax_n(It first, Size n) { - using core::iamax; - return iamax(n, base(first), stride(first)); - // if you get an error here make sure that you are including (and linking) the appropriate BLAS backend for your memory type -} - -template -auto iamax(It first, It last) -->decltype(iamax_n(first, std::distance(first, last))) { - return iamax_n(first, std::distance(first, last)); } - -template -auto iamax(X1D const& x) // NOLINT(readability-identifier-length) x conventional blas name -->decltype(iamax(begin(x), end(x))) {assert( not offset(x) ); - return iamax(begin(x), end(x)); } - -template -auto amax(X1D const& x) { // NOLINT(readability-identifier-length) x conventional blas name - return begin(x) + iamax(x); -} - -} // end namespace boost::multi::blas - -//#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ - -//#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS iamax" -//#define BOOST_TEST_DYN_LINK -//#include - -//#include "../../array.hpp" -//#include "../../utility.hpp" - -//#include -//#include - -//using std::cout; -//namespace multi = boost::multi; -//namespace blas = multi::blas; - -//BOOST_AUTO_TEST_CASE(multi_adaptors_blas_iamax_real){ -// multi::array const A = {1., 2., 3., 4.}; - -// auto i = blas::iamax(A); -// BOOST_REQUIRE( i == 3 ); -// BOOST_REQUIRE( A[blas::iamax(A)] == 4. ); - -// BOOST_REQUIRE( *blas::amax(A) == 4. ); -//} - -//using complex = std::complex; - -//BOOST_AUTO_TEST_CASE(multi_adaptors_blas_iamax_complex){ -// multi::array const A = {1., 2., 3., 4.}; -// auto i = blas::iamax(A); -// BOOST_REQUIRE( i == 3 ); -// BOOST_REQUIRE( A[blas::iamax(A)] == 4. ); -// BOOST_REQUIRE( *blas::amax(A) == 4. ); -//} - -//#endif -#endif diff --git a/include/multi/adaptors/blas/nrm2.hpp b/include/multi/adaptors/blas/nrm2.hpp deleted file mode 100644 index cc6c6d575..000000000 --- a/include/multi/adaptors/blas/nrm2.hpp +++ /dev/null @@ -1,227 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_NRM2_HPP -#define MULTI_ADAPTORS_BLAS_NRM2_HPP - -#include - -#include - -#include // std::norm - -namespace boost::multi::blas { - -using core::nrm2; - -using multi::base; -using std::norm; // nvcc11 needs using std::FUNCTION and the FUNCTION (and it works in clang, gcc, culang, icc) - -template -auto nrm2_n(It const& x, Size n, A0D res) // NOLINT(readability-identifier-length) conventional BLAS naming -//->decltype(blas::default_context_of(x.base())->nrm2(n, x.base(), x.stride(), res), std::next(res)) { // NOLINT(fuchsia-default-arguments-calls) -{ return blas::default_context_of(x.base())->nrm2(n, x.base(), x.stride(), res), std::next(res); } // NOLINT(fuchsia-default-arguments-calls) - -template -auto nrm2(A1D const& x, A0D&& res) // NOLINT(readability-identifier-length) conventional BLAS naming -//->decltype(nrm2_n(x.begin(), x.size(), &res)) { -{ return nrm2_n(x.begin(), x.size(), &res); } - -template -class nrm2_ptr { - ItX x_first_; - Size count_; - - protected: - nrm2_ptr(ItX x_first, Size count) : x_first_{x_first}, count_{count} {} - - public: - explicit operator bool() const {return true;} - - template - friend constexpr auto copy_n(nrm2_ptr first, Size2 count, ItOut d_first) { -// ->decltype(blas::nrm2_n(std::declval(), Size2{} , d_first), d_first + count) { - assert(count == 1); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - return blas::nrm2_n(first.x_first_ , first.count_, d_first), d_first + count; } - - template - friend constexpr auto uninitialized_copy_n(nrm2_ptr first, Size2 count, ItOut d_first) - ->decltype(blas::nrm2_n(std::declval(), Size2{} , d_first), d_first + count) {assert(count == 1); - return blas::nrm2_n(first.x_first_ , first.count_, d_first), d_first + count; } -}; - -template> -struct nrm2_ref : private Ptr { - using decay_type = decltype(norm(std::declval())); - explicit nrm2_ref(X const& x) : Ptr{begin(x), size(x)} {} // NOLINT(readability-identifier-length) BLAS naming - - constexpr auto operator&() const& -> Ptr const& {return *this;} // NOLINT(google-runtime-operator) reference type - - auto decay() const -> decay_type {decay_type ret; copy_n(operator&(), 1, &ret); return ret;} // NOLINT(fuchsia-default-arguments-calls) complex - operator decay_type() const {return decay();} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions,hicpp-explicit-conversion) to allow terse syntax -// #if not defined(__CUDACC__) or not defined(__INTEL_COMPILER) -// friend auto operator*(decay_type const& lhs, dot_ref const& self) {return lhs*self.decay();} -// #endif - auto operator+() const -> decay_type {return decay();} - - auto operator==(nrm2_ref const& other) const -> bool {return decay() == other.decay();} - auto operator!=(nrm2_ref const& other) const -> bool {return decay() != other.decay();} - - // template - // auto operator==(Other const& other) const - // ->decltype(decay()==other) { - // return decay()==other; } - // template - // auto operator!=(Other const& other) const - // ->decltype(decay()!=other) { - // return decay()!=other; } -}; - -template -[[nodiscard]] -auto nrm2(X const& x) { // NOLINT(readability-identifier-length) BLAS naming - return nrm2_ref{x}; -} - -namespace operators { - using std::norm; - template()))>//decltype(norm(std::declval()))> - [[nodiscard]] auto operator^(A1D const& array, int n) - ->decltype(std::pow(Real{blas::nrm2(array)}, n)) { - return std::pow(Real{blas::nrm2(array)}, n); } - - template - [[nodiscard]] auto abs(A1D const& array) { - return blas::nrm2(array); - } - - template - [[nodiscard]] auto norm(A1D const& array) { - auto const sqrt = +blas::nrm2(array); - return sqrt*sqrt; - } - -} // end namespace operators - -} // end namespace boost::multi::blas - -//#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ - -//#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS nrm2" -//#define BOOST_TEST_DYN_LINK -//#include - - -//#include "../../array.hpp" -//#include "../../complex.hpp" - -////#include - -//#include - -//namespace multi = boost::multi; - -//BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_real){ -// namespace blas = multi::blas; -// multi::array const cA = { -// {1., 2., 3., 4.}, -// {5., 6., 7., 8.}, -// {9., 10., 11., 12.} -// }; - -// double n; -// BOOST_REQUIRE( blas::nrm2(rotated(cA)[1], n) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); -// BOOST_REQUIRE( n == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); -// BOOST_REQUIRE( blas::nrm2(rotated(cA)[1]) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); - -// double n2 = blas::nrm2(rotated(cA)[1]); -// BOOST_REQUIRE( n == n2 ); - -// multi::array R(4); -// blas::nrm2( rotated(cA)[1], R[2]); -// BOOST_REQUIRE( R[2] == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); - -// multi::array R0; -// blas::nrm2( rotated(cA)[1], R0); -// BOOST_REQUIRE( R0 == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); - -// BOOST_REQUIRE( blas::nrm2(rotated(cA)[1]) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); - -//} - -//BOOST_AUTO_TEST_CASE(multi_adaptor_blas_nrm2_operators){ -// multi::array X = {1.1,2.1,3.1, 4.1}; -// double n; multi::blas::nrm2(X, n); -// BOOST_REQUIRE( n == multi::blas::nrm2(X) ); - -//} - -//BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_complex_real_case){ -// using complex = std::complex; -// multi::array const cA = { -// {1., 2., 3., 4.}, -// {5., 6., 7., 8.}, -// {9., 10., 11., 12.} -// }; - -// using multi::blas::nrm2; -// double n; -// BOOST_REQUIRE( nrm2(rotated(cA)[1], n) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); -// BOOST_REQUIRE( nrm2(rotated(cA)[1]) == n ); -//} - -//#if 0 -//BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_complex_real_case_thrust){ -// using complex = thrust::complex; -// multi::array const cA = { -// {1., 2., 3., 4.}, -// {5., 6., 7., 8.}, -// {9., 10., 11., 12.} -// }; - -// using multi::blas::nrm2; -// double n; -// BOOST_REQUIRE( nrm2(rotated(cA)[1], n) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); -// BOOST_REQUIRE( nrm2(rotated(cA)[1]) == n ); -//} - -//BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_complex_real_case_types){ -// boost::mpl::for_each, -// thrust::complex//, -// // boost::multi::complex // TODO make this work -// >>([](auto cplx){ -// multi::array const cA = { -// {1., 2., 3., 4.}, -// {5., 6., 7., 8.}, -// {9., 10., 11., 12.} -// }; - -// using multi::blas::nrm2; -// double n; -// BOOST_REQUIRE( nrm2(rotated(cA)[1], n) == std::sqrt( 2.*2. + 6.*6 + 10.*10.) ); -// BOOST_REQUIRE( nrm2(rotated(cA)[1]) == n ); -// }); -//} -//#endif - -//BOOST_AUTO_TEST_CASE(multi_adaptor_multi_nrm2_complex){ -// using complex = std::complex; complex const I{0,1}; -// multi::array const cA = { -// {1., 2. + 1.*I, 3., 4.}, -// {5., 6. + 4.*I, 7., 8.}, -// {9., 10. - 3.*I, 11., 12.} -// }; - -// using multi::blas::nrm2; -// double n; -// BOOST_REQUIRE( nrm2(rotated(cA)[1], n) == std::sqrt( norm(cA[0][1]) + norm(cA[1][1]) + norm(cA[2][1]) ) ); -// BOOST_REQUIRE( nrm2(rotated(cA)[1]) == std::sqrt( norm(cA[0][1]) + norm(cA[1][1]) + norm(cA[2][1]) ) ); - -// using namespace multi::blas::operators; -// BOOST_TEST_REQUIRE( (rotated(cA)[1]^-1) == 1/std::sqrt(norm(cA[0][1]) + norm(cA[1][1]) + norm(cA[2][1])) , boost::test_tools::tolerance(1e-15) ); -// BOOST_TEST_REQUIRE( (rotated(cA)[1]^2) == norm(cA[0][1]) + norm(cA[1][1]) + norm(cA[2][1]) , boost::test_tools::tolerance(1e-15) ); -//} - -//#endif -#endif diff --git a/include/multi/adaptors/blas/numeric.hpp b/include/multi/adaptors/blas/numeric.hpp deleted file mode 100644 index f85b3b71e..000000000 --- a/include/multi/adaptors/blas/numeric.hpp +++ /dev/null @@ -1,299 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_NUMERIC_HPP -#define MULTI_ADAPTORS_BLAS_NUMERIC_HPP -#pragma once - -#include "../../adaptors/complex.hpp" -#include "../../array_ref.hpp" -#include "../../complex.hpp" - -#include "../blas/complex_traits.hpp" - -#include "../../memory/pointer_traits.hpp" - -#include "numeric/is_complex.hpp" - -#include - -namespace boost { -namespace multi::blas { - -template struct complex_dummy {T real; T imag;}; - -template< - class A, typename Complex = typename std::decay_t::element, typename T=typename multi::blas::complex_traits::real_type, - class=std::enable_if_t::value> -> -auto real(A&& array) -->decltype(std::forward(array).template reinterpret_array_cast>().template member_cast(&complex_dummy::real)){ - return std::forward(array).template reinterpret_array_cast>().template member_cast(&complex_dummy::real);} - -template< - class A, class Complex = typename std::decay_t::element_type, typename T=typename complex_traits::real_type, - class=std::enable_if_t::value> -> -auto imag(A&& array) -->decltype(std::forward(array).template reinterpret_array_cast>().template member_cast(&complex_dummy::imag)){ - return std::forward(array).template reinterpret_array_cast>().template member_cast(&complex_dummy::imag);} - -template::element, typename RealElem = typename ComplexElem::value_type, - class=std::enable_if_t::value> -> -auto real_doubled(ComplexArr&& array) { // produces a real view of complex array with the last dimension duplicated and with interleaved real imaginary parts - return std::forward(array).template reinterpret_array_cast(2).rotated().flatted().unrotated(); -} - -template class involuted; - -template::reference, F> > class involuter; - -template -class involuted { - Ref r_; // [[no_unique_address]] // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - Involution f_; - -public: - using decay_type =std::decay_t()(std::declval()))>; - - constexpr explicit involuted(Ref ref, Involution fun) : r_{std::forward(ref)}, f_{fun}{} - constexpr explicit involuted(Ref ref) : r_{std::forward(ref)}, f_{}{} - - auto operator=(involuted const& other) -> involuted& = delete; - - ~involuted() = default; - involuted(involuted const&) = delete; - involuted(involuted&&) noexcept = default; // for C++14 - auto operator=(involuted&& other) noexcept -> involuted&{ - r_ = std::move(other.r_); - return *this; - } - - constexpr auto decay() const& -> decay_type{return f_(r_);} - - constexpr explicit operator decay_type() &{return f_(r_);} - constexpr explicit operator decay_type() const&{return f_(r_);} - constexpr /*plct*/ operator decay_type() &&{return f_(r_);} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - constexpr auto operator*(decay_type const& other) const{return f_(r_)*other;} - constexpr auto operator&()&& -> decltype(auto){ // NOLINT(google-runtime-operator) : reference-like object - return involuter()), Involution>{&r_, f_}; - } - - template() = (std::declval())(std::declval()))> - constexpr auto operator=(DecayType&& other)& -> involuted&{ - r_=f_(std::forward(other)); - return *this; - } - - template() = (std::declval())(std::declval()))> - constexpr auto operator=(DecayType&& other)&& -> involuted&{ - r_=f_(std::forward(other)); - return *this; - } - - template - friend constexpr auto operator==(involuted const& self, DecayType const& other) - ->decltype( std::declval()==other){ - return self.operator decay_type()==other;} - template - friend constexpr auto operator!=(involuted const& self, DecayType const& other) - ->decltype(std::declval()!=other){ - return self.operator decay_type()!=other;} - - friend constexpr auto operator==(decay_type const& other, involuted const& self) -> bool { - return other == self.operator decay_type();} - - friend constexpr auto operator!=(decay_type const& other, involuted const& self) -> bool { - return other != self.operator decay_type();} - - template{}, int> =0> - friend constexpr auto operator==(DecayType const& other, involuted const& self) { - return other == self.operator decay_type(); - } - template{}, int> =0> - friend constexpr auto operator!=(DecayType const& other, involuted const& self) { - return other != self.operator decay_type();\ - } -// auto imag() const{return static_cast(*this).imag();} - template friend constexpr auto operator<<(Sink&& sink, involuted const& self) -> Sink& { - return sink<< self.operator decay_type(); - } - constexpr auto conj() const& {return adl_conj(operator decay_type());} - - template - friend constexpr auto imag(involuted const& self) { - //->decltype(imag(std::declval())) { - return self.operator decay_type().imag(); } - - // ->decltype(adl_imag(std::declval())) { - // return adl_imag(self.operator decay_type()); } -}; - -#if defined(__cpp_deduction_guides) -template involuted(T&&, F) -> involuted; -//template involuted(T&, F)->involuted; -//template involuted(T const&, F)->involuted; -#endif - -template -class involuter; - -template -auto default_allocator_of(involuter const& iv) { - return default_allocator_of(iv.it_); -} - -template -class involuter { - It it_; - F f_; // [[no_unique_address]] - template friend class involuter; - - public: - using difference_type = typename std::iterator_traits::difference_type; - using value_type = typename std::iterator_traits::value_type; - using pointer = involuter;//svoid; // typename std::iterator_traits::pointer - using reference = Reference; - using iterator_category = typename std::iterator_traits::iterator_category; - using element_type = typename std::pointer_traits::element_type; - template using rebind = involuter::template rebind, F>; - - involuter() = default; -// ~involuter() = default; - - HD constexpr explicit involuter(It it) : it_{std::move(it)}, f_{} {} - HD constexpr explicit involuter(It it, F fun) : it_{std::move(it)}, f_{std::move(fun)} {} - -// involuter(involuter const& other) = default; - - template(typename Other::underlying_type{}))* = nullptr> - // cppcheck-suppress noExplicitConstructor - HD constexpr/*implct*/involuter(Other const& other) : it_{other.it_}, f_{other.f_}{} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : inherit implicit conversion of underlying type - template(typename Other::underlying_type{}))* = nullptr> - HD constexpr explicit involuter(Other const& other) : it_{other.it_}, f_{other.f_}{} - - constexpr auto operator*() const {return reference{*it_, f_};} - constexpr auto operator[](difference_type n) const {return reference{*(it_ + n), f_};} - - auto operator==(involuter const& other) const -> bool {return it_ == other.it_;} - auto operator!=(involuter const& other) const -> bool {return it_ != other.it_;} - - constexpr auto operator+=(difference_type n) -> involuter& {it_ += n; return *this;} - constexpr auto operator-=(difference_type n) -> involuter& {it_ -= n; return *this;} - - constexpr auto operator+(difference_type n) const {return involuter{it_ + n, f_};} - constexpr auto operator-(difference_type n) const {return involuter{it_ - n, f_};} - - auto operator-(involuter const& other) const{return it_ - other.it_;} - - explicit operator bool() const{return it_;} - using underlying_type = It; - friend /*constexpr*/ auto underlying(involuter const& self) -> underlying_type{return self.it_;} - constexpr explicit operator It() const {return underlying(*this);} -// friend auto get_allocator(involuter const& self){return get_allocator(self.it_);} - friend auto default_allocator_of(involuter const& inv){ - using multi::default_allocator_of; - return default_allocator_of(inv.it_); - } - using default_allocator_type = typename multi::pointer_traits::default_allocator_type; - friend auto get_allocator(involuter const& inv){ - using boost::multi::get_allocator; - return get_allocator(inv.it_); - } -}; - -template using negated = involuted>; -template using negater = involuter>; - -struct conjugate { - template - constexpr auto operator()(Complex const& zee) const { - // using std::conj; /*for doubles?*/ - return conj(zee); - // return multi::adl_conj(std::forward(zee)); // this is needed by icc - } - #ifdef __NVCC__ - template - constexpr auto operator()(::thrust::tagged_reference zee) const { - return conj(static_cast(zee)); - } - #endif - #if defined(__HIP_PLATFORM_AMD__) - template - constexpr auto operator()(::thrust::tagged_reference zee) const { - return conj(static_cast(zee)); - } - #endif -}; - -template using conjugated = involuted; - -template using conjugater = involuter; - -template auto make_conjugater(It it){return conjugater{it};} -template auto make_conjugater(conjugater it) -> It {return underlying(it);} - -template auto imag(involuted const& inv) {return inv.decay().imag();} -template auto real(involuted const& inv) {return inv.decay().real();} - -template auto has_imag_fun_aux(T const& value) -> decltype((void)imag(value), std::true_type {}); - inline auto has_imag_fun_aux(... ) -> decltype( std::false_type{}); -template struct has_imag_fun : decltype(has_imag_fun_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - - -template auto has_imag_mem_aux(T const& value) -> decltype((void)value.imag(), std::true_type {}); - inline auto has_imag_mem_aux(... ) -> decltype( std::false_type{}); -template struct has_imag_mem : decltype(has_imag_mem_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - -template struct has_imag : std::integral_constant{} or has_imag_mem{})>{}; - -template -struct is_complex_array : has_imag().base())>>::element_type>> {}; -// template static auto _(T const& t) -> has_imag; -// constexpr explicit operator bool() &{return decltype(_(*base(std::declval()))){};} -// constexpr explicit operator bool() &&{return decltype(_(*base(std::declval()))){};} -// constexpr operator bool() const&{return decltype(_(*base(std::declval()))){};} -// static constexpr bool value = decltype(_(*base(std::declval()))){}; -// template constexpr auto operator()(AA&& /*unused*/){return _(*base(std::declval()));} -//}; - -template struct is_complex : has_imag {}; - -template - auto is_conjugated_aux(conjugater const& /*self*/) -> std::true_type ; -inline auto is_conjugated_aux(... ) -> std::false_type; - -template struct is_conjugated : decltype(is_conjugated_aux((std::declval()).base())) { // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - template constexpr auto operator()(AA&& /*unused*/) {return is_conjugated_aux((std::declval()).base());} -}; - -template, typename Elem=typename D::element_type, typename Ptr=typename D::element_ptr, - std::enable_if_t{}, int> =0> -auto conj(A&& array) -> A&& { - return std::forward(array); -} - -template, typename Elem=typename D::element_type, typename Ptr=typename D::element_ptr, - std::enable_if_t{} and is_complex_array{}, int> =0> -auto conj(A&& array) -> decltype(auto) { - return std::forward(array).template static_array_cast>(); -} - -template, typename Elem=typename D::element_type, typename Ptr=typename D::element_ptr::underlying_type, - std::enable_if_t< is_conjugated{}, int> =0> -auto conj(A&& array) -->decltype(std::forward(array).template static_array_cast()) { - return std::forward(array).template static_array_cast(); } - -} // end namespace multi::blas - -template -auto default_allocator_of(multi::blas::involuter it) { - return multi::default_allocator_of(it.underlying()); -} - -} // end namespace boost - -#endif diff --git a/include/multi/adaptors/blas/operations.hpp b/include/multi/adaptors/blas/operations.hpp deleted file mode 100644 index ae4335e86..000000000 --- a/include/multi/adaptors/blas/operations.hpp +++ /dev/null @@ -1,92 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_OPERATIONS_HPP -#define MULTI_ADAPTORS_BLAS_OPERATIONS_HPP - -#include - -namespace boost::multi::blas { - -template auto transposed(M const& array) -> decltype(auto){return rotated(array);} - -template, typename E=typename D::element_type> -auto conjugated_transposed(A&& array) -> decltype(auto) { - return transposed(blas::conj(std::forward(array))); -} - -template auto identity(A&& array) -> decltype(auto) {return std::forward(array);} - -// template, typename E=typename D::element_type> -// auto conjugated(A&& array) -> decltype(auto) { -// return blas::conj(std::forward(array)); -// } - -template -auto hermitized(A&& array, std::true_type /*true */) -> decltype(auto) { - return conjugated_transposed(std::forward(array)); -} - -template -auto hermitized(A&& array, std::false_type /*false*/) -> decltype(auto) { - return transposed(std::forward(array)); -} - -template -auto hermitized(A&& array) -> decltype(auto) {return conjugated_transposed(std::forward(array));} - -template -auto transposed(A&& array) -> decltype(auto) {return rotated(std::forward(array));} - -namespace operators { - -[[maybe_unused]] constexpr static struct { - - template::rank::value == 2, int> =0> - [[nodiscard]] auto operator()(A&& array) const -> decltype(auto) { return hermitized(std::forward(array)); } - - template::rank::value == 1, int> =0> - [[deprecated("use blas::C instead of blas::H for conjugated vectors to avoid confusions")]] - [[nodiscard]] auto operator()(A&& array) const -> decltype(auto) { return blas::conj(std::forward(array)); } - -} H; // NOLINT(readability-identifier-length) conventional name in BLAS - -template -auto operator^(A&& array, Op op) -->decltype(op(std::forward(array))) { - return op(std::forward(array)); } - -} // end namespace operators - -using operators::H; - -template::rank::value == 1, int> =0> -auto C(A&& array) -> decltype(auto) {return blas::conj(std::forward(array));} // NOLINT(readability-identifier-naming,readability-identifier-length) : conventional one-letter operation BLAS - -template::rank::value == 2, int> =0> -[[deprecated("use blas::H instead of blas::C for conjugated transposed matrices to avoid confusion, use blas::J for only-conjugation of matrices")]] -auto C(A&& array) -> decltype(auto) {return hermitized(std::forward(array));} // NOLINT(readability-identifier-naming,readability-identifier-length) : conventional one-letter operation BLAS - -template::rank::value == 2, int> =0> -auto J(A&& array) -> decltype(auto) {return blas::conj(std::forward(array));} // NOLINT(readability-identifier-naming,readability-identifier-length) : conventional one-letter operation BLAS - -namespace operators { - - template - auto operator*(A&& array) - ->decltype(blas::conj(std::forward(array))) { - return blas::conj(std::forward(array)); } - - template - auto operator~(A&& array) - ->decltype(blas::transposed(std::forward(array))) { - return blas::transposed(std::forward(array)); } - -} // end namespace operators - -template auto T(A&& array) -> decltype(auto) {return transposed(std::forward(array));} // NOLINT(readability-identifier-naming) : conventional one-letter operation BLAS -template auto N(A&& array) -> decltype(auto) {return identity (std::forward(array));} // NOLINT(readability-identifier-naming) : conventional one-letter operation BLAS - -} // end namespace boost::multi::blas - -#endif diff --git a/include/multi/adaptors/blas/side.hpp b/include/multi/adaptors/blas/side.hpp deleted file mode 100644 index 6c9deb865..000000000 --- a/include/multi/adaptors/blas/side.hpp +++ /dev/null @@ -1,25 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_SIDE_HPP -#define MULTI_ADAPTORS_BLAS_SIDE_HPP - -namespace boost::multi::blas { - -enum side : char { - left = 'L', - right = 'R'//, -// pre_multiply = 'R', -// post_multiply = 'L' -}; - -inline auto swap(side sid) -> side { - switch(sid) { - case side::left : return side::right; - case side::right: return side::left ; - } __builtin_unreachable(); // LCOV_EXCL_LINE -} - -} // end namespace boost::multi::blas - -#endif diff --git a/include/multi/adaptors/blas/swap.hpp b/include/multi/adaptors/blas/swap.hpp deleted file mode 100644 index bc968316b..000000000 --- a/include/multi/adaptors/blas/swap.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_SWAP_HPP -#define MULTI_ADAPTORS_BLAS_SWAP_HPP -#pragma once - -#include - -namespace boost::multi::blas { - -using core::swap; - -template -auto swap_n(It1 first, Size count, It2 first2) -> It2 { - blas::default_context_of(base(first))->swap(count, base(first), stride(first), base(first2), stride(first2)); - return first2 + count; -} - -template -auto swap(It1 first, It2 last, It2 first2) -> It2 { - assert(stride(first) == stride(last)); - return swap_n(first, last - first, first2); -} - -template -auto swap(X1D&& x, Y1D&& y) -> Y1D&& { // NOLINT(readability-identifier-length) x, y conventional blas names - assert( size(x) == size(y) ); -// assert( offset(x) == 0 and offset(y) == 0 ); - swap( begin(x), end(x), begin(y) ); - return std::forward(y); -} - -template -auto swap(X1D const& x, Y1D const& y) = delete; // NOLINT(readability-identifier-length) x, y conventional blas names - -template -auto operator^(X1D&& x, Y1D&& y) { // NOLINT(readability-identifier-length) BLAS naming - blas::swap(x, y); - return std::tie(x, y); -} - -namespace operators { - using blas::operator^; -} // end namespace operators - -} // end namespace boost::multi::blas -#endif diff --git a/include/multi/adaptors/blas/syrk.hpp b/include/multi/adaptors/blas/syrk.hpp deleted file mode 100644 index e5b325225..000000000 --- a/include/multi/adaptors/blas/syrk.hpp +++ /dev/null @@ -1,432 +0,0 @@ -#ifndef MULTI_ADAPTORS_BLAS_SYRK_HPP // -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -#define MULTI_ADAPTORS_BLAS_SYRK_HPP -// Copyright 2019-2021 Alfredo A. Correa - -#include "../blas/core.hpp" -#include "../blas/filling.hpp" -#include "../blas/numeric.hpp" - -namespace boost::multi::blas { - -using core::syrk; - -template -auto syrk(filling c_side, AA alpha, A2D const& a, BB beta, C2D&& c) { // NOLINT(readability-identifier-length) BLAS naming -//->decltype(syrk('\0', '\0', size(c), size(a), alpha, base(a), stride(rotated(a)), beta, base(c), stride(c)), std::forward(c)){ - assert( size(c) == size(rotated(c)) ); - if(stride(a)==1) { - if(stride(c)==1) {syrk(flip(c_side)==filling::upper?'L':'U', 'N', size(c), size(a ), &alpha, base(a), stride(rotated(a)), &beta, base(c), stride(rotated(c)));} - else {syrk(c_side ==filling::upper?'L':'U', 'N', size(c), size(rotated(a)), &alpha, base(a), stride(rotated(a)), &beta, base(c), stride( c ));} - } else { - if(stride(c)==1) {syrk(flip(c_side)==filling::upper?'L':'U', 'T', size(c), size(rotated(a)), &alpha, base(a), stride(a), &beta, base(c), stride(rotated(c)));} - else {syrk(c_side ==filling::upper?'L':'U', 'T', size(c), size(rotated(a)), &alpha, base(a), stride(a), &beta, base(c), stride( c ));} - } - return std::forward(c); -} - -template -auto syrk(filling c_side, AA alpha, A2D const& a, C2D&& c) // NOLINT(readability-identifier-length) BLAS naming -->decltype(syrk(c_side, alpha, a, 0., std::forward(c))) { - return syrk(c_side, alpha, a, 0., std::forward(c)); } - -template -auto syrk(AA alpha, A2D const& a, C2D&& c) // NOLINT(readability-identifier-length) BLAS naming -->decltype(syrk(filling::upper, alpha, a, syrk(filling::lower, alpha, a, std::forward(c)))) { - return syrk(filling::upper, alpha, a, syrk(filling::lower, alpha, a, std::forward(c))); } - -template -[[nodiscard]] // ("because input argument is const") -// this decay in the return type is important -// NOLINTNEXTLINE(readability-identifier-length) BLAS naming -auto syrk(AA alpha, A2D const& a) -> std::decay_\ -t { - return syrk(alpha, a, Ret({size(a), size(a)}, get_allocator(a))); } - -template -[[nodiscard]] -auto syrk(A2D const& A) // NOLINT(readability-identifier-length) BLAS naming -->decltype(syrk(1., A)) { - return syrk(1., A); } - -} // end namespace boost::multi::blas - -//#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ - -//#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuBLAS syrk" -//#include - -//#include "../blas/gemm.hpp" - -//#include "../../array.hpp" -//#include "../../utility.hpp" - -//#include - -//#include -//#include -//#include -//#include -//#include - -////#include - -//using std::cout; -//using std::cerr; - -//namespace multi = boost::multi; - -//template decltype(auto) print(M const& C){ -// using boost::multi::size; -// for(int i = 0; i != size(C); ++i){ -// for(int j = 0; j != size(C[i]); ++j) -// std::cout << C[i][j] << ' '; -// std::cout << std::endl; -// } -// return std::cout << std::endl; -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_real){ -// multi::array const a = { -// { 1., 3., 4.}, -// { 9., 7., 1.} -// }; -// { -// multi::array c({3, 3}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// using blas::transposed; -// syrk(filling::lower, 1., transposed(a), 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[2][1] == 19. ); -// BOOST_REQUIRE( c[1][2] == 9999. ); -// } -// { -// multi::array c({3, 3}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// using blas::transposed; -// syrk(filling::upper, 1., transposed(a), 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[1][2] == 19. ); -// BOOST_REQUIRE( c[2][1] == 9999. ); -// } -// { -// multi::array c({2, 2}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// using blas::syrk; -// syrk(filling::lower, 1., a, 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[1][0] == 34. ); -// BOOST_REQUIRE( c[0][1] == 9999. ); -// } -// { -// multi::array c({2, 2}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// syrk(filling::upper, 1., a, 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, a⸆a, `c` in lower triangular -// BOOST_REQUIRE( c[0][1] == 34. ); -// BOOST_REQUIRE( c[1][0] == 9999. ); -// } -// { -// multi::array c({2, 2}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// syrk(filling::upper, 1., a, 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, a⸆a, `c` in lower triangular -// BOOST_REQUIRE( c[0][1] == 34. ); -// BOOST_REQUIRE( c[1][0] == 9999. ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_real_special_case){ -// multi::array const a = { -// { 1., 3., 4.}, -// }; -// { -// multi::array c({1, 1}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// syrk(filling::lower, 1., a, 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// //BOOST_REQUIRE( c[1][0] == 34. ); -// //BOOST_REQUIRE( c[0][1] == 9999. ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_complex_real_case){ -// using complex = std::complex; -// multi::array const a = { -// { 1., 3., 4.}, -// { 9., 7., 1.} -// }; -// { -// multi::array c({3, 3}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// using blas::transposed; -// syrk(filling::lower, 1., transposed(a), 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[2][1] == 19. ); -// BOOST_REQUIRE( c[1][2] == 9999. ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_complex){ -// using complex = std::complex; -// constexpr auto const I = complex{0., 1.}; -// multi::array const a = { -// { 1. + 3.*I, 3.- 2.*I, 4.+ 1.*I}, -// { 9. + 1.*I, 7.- 8.*I, 1.- 3.*I} -// }; -// { -// multi::array c({3, 3}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// using blas::transposed; -// syrk(filling::lower, 1., transposed(a), 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[2][1] == complex(-3., -34.) ); -// BOOST_REQUIRE( c[1][2] == 9999. ); -// } -// { -// multi::array c({2, 2}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// syrk(filling::lower, 1., a, 0., c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[1][0] == complex(18., -21.) ); -// BOOST_REQUIRE( c[0][1] == 9999. ); -// } -// { -// multi::array c({2, 2}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// syrk(filling::upper, 1., a, 0., c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in upper triangular -// BOOST_REQUIRE( c[0][1] == complex(18., -21.) ); -// BOOST_REQUIRE( c[1][0] == 9999. ); -// } -//} - - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_automatic_operation_complex){ -// using complex = std::complex; -// constexpr auto const I = complex{0., 1.}; -// multi::array const a = { -// { 1. + 3.*I, 3.- 2.*I, 4.+ 1.*I}, -// { 9. + 1.*I, 7.- 8.*I, 1.- 3.*I} -// }; -// { -// multi::array c({2, 2}, 9999.); -// using multi::blas::filling; -// syrk(filling::lower, 1., a, 0., c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[1][0]==complex(18., -21.) ); -// BOOST_REQUIRE( c[0][1]==9999. ); -// } -// { -// multi::array c({3, 3}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// using blas::transposed; -// syrk(filling::lower, 1., transposed(a), 0., c); // c⸆=c=a⸆a=(aa⸆)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[2][1]==complex(-3.,-34.) ); -// BOOST_REQUIRE( c[1][2]==9999. ); -// } -// { -// multi::array c({3, 3}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// using blas::transposed; -// syrk(filling::lower, 1., rotated(a), 0., c); // c⸆=c=a⸆a=(aa⸆)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[2][1]==complex(-3.,-34.) ); -// BOOST_REQUIRE( c[1][2]==9999. ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_automatic_operation_real){ -// multi::array const a = { -// { 1., 3., 4.}, -// { 9., 7., 1.} -// }; -// { -// multi::array c({2, 2}, 9999.); -// using multi::blas::filling; -// syrk(filling::lower, 1., a, 0., c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[1][0] == 34. ); -// BOOST_REQUIRE( c[0][1] == 9999. ); -// } -// { -// multi::array c({2, 2}, 9999.); -// using multi::blas::filling; -// syrk(filling::upper, 1., a, 0., c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in upper triangular -// BOOST_REQUIRE( c[0][1] == 34. ); -// BOOST_REQUIRE( c[1][0] == 9999. ); -// } -// { -// multi::array c({3, 3}, 9999.); -// using multi::blas::filling; -// syrk(filling::lower, 1., rotated(a), 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[2][1] == 19. ); -// BOOST_REQUIRE( c[1][2] == 9999. ); -// } -// { -// multi::array c({3, 3}, 9999.); -// namespace blas = multi::blas; -// using blas::transposed; -// using blas::filling; -// syrk(filling::lower, 1., transposed(a), 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[2][1] == 19. ); -// BOOST_REQUIRE( c[1][2] == 9999. ); -// } -// { -// multi::array c({3, 3}, 9999.); -// namespace blas = multi::blas; -// using blas::transposed; -// using blas::filling; -// syrk(filling::upper, 1., transposed(a), 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in upper triangular -// BOOST_REQUIRE( c[1][2] == 19. ); -// BOOST_REQUIRE( c[2][1] == 9999. ); -// } -// { -// multi::array c({2, 2}, 9999.); -// using multi::blas::filling; -// using multi::blas::transposed; -// syrk(filling::upper, 1., a, 0., transposed(c)); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in upper triangular -// BOOST_REQUIRE( c[0][1] == 9999. ); -// BOOST_REQUIRE( c[1][0] == 34. ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_automatic_implicit_zero){ -// multi::array const a = { -// { 1., 3., 4.}, -// { 9., 7., 1.} -// }; -// { -// multi::array c({2, 2}, 9999.); -// using multi::blas::filling; -// syrk(filling::lower, 1., a, c); // c⸆=c=aa⸆=(aa⸆)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[1][0] == 34. ); -// BOOST_REQUIRE( c[0][1] == 9999. ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_automatic_symmetrization){ -// multi::array const a = { -// { 1., 3., 4.}, -// { 9., 7., 1.} -// }; -// { -// multi::array c({2, 2}, 9999.); -// using multi::blas::syrk; -// using multi::blas::gemm; -// using multi::blas::T; -// syrk(1., a, c); // c⸆=c=aa⸆=(aa⸆)⸆ -// BOOST_REQUIRE( c[1][0] == 34. ); -// BOOST_REQUIRE( c[0][1] == 34. ); -// BOOST_REQUIRE( syrk(a) == gemm(a, T(a)) ); -// } -// { -// using multi::blas::syrk; -// multi::array c = syrk(1., a); // c⸆=c=aa⸆=(aa⸆)⸆ -// BOOST_REQUIRE( c[1][0] == 34. ); -// BOOST_REQUIRE( c[0][1] == 34. ); -// } -// { -// using multi::blas::syrk; -// multi::array c = syrk(a); // c⸆=c=aa⸆=(aa⸆)⸆ -// BOOST_REQUIRE( c[1][0] == 34. ); -// BOOST_REQUIRE( c[0][1] == 34. ); -// } -// { -// using multi::blas::transposed; -// using multi::blas::syrk; -// multi::array c = syrk(transposed(a)); // c⸆=c=a⸆a=(a⸆a)⸆ -// BOOST_REQUIRE( c[2][1] == 19. ); -// BOOST_REQUIRE( c[1][2] == 19. ); -// } -//} - -//#if 0 - - - -//} - - - - - - - -//} - - - - - - - -//#if 0 -// { - -// { -// multi::array C({2, 2}, 9999.); -// syrk(1., rotated(A), rotated(C)); // C^T=C=A*A^T=(A*A^T)^T -// assert( C[1][0] == complex(18., -21.) ); -// } -// { -// multi::array C({2, 2}, 9999.); -// syrk(rotated(A), rotated(C)); // C^T=C=A*A^T=(A*A^T)^T -// assert( C[1][0] == complex(18., -21.) ); -// } -// { -// complex C[2][2]; -// using multi::rotated; -// syrk(rotated(A), rotated(C)); // C^T=C=A*A^T=(A*A^T)^T -// assert( C[1][0] == complex(18., -21.) ); -// } -// { -// auto C = syrk(1., A); // C = C^T = A^T*A, C is a value type matrix (with C-ordering, information is everywhere) -// assert( C[1][2]==complex(-3.,-34.) ); -// } -// { -//// what(rotated(syrk(A))); -// multi::array C = rotated(syrk(A)); // C = C^T = A^T*A, C is a value type matrix (with C-ordering, information is in upper triangular part) -// print(C) <<"---\n"; -// } -// -// } -//#if 0 -// { -// multi::array const A = { -// { 1. + 3.*I, 3.- 2.*I, 4.+ 1.*I}, -// { 9. + 1.*I, 7.- 8.*I, 1.- 3.*I} -// }; -// auto C = rotated(syrk(A)).decay(); // C = C^T = A^T*A, C is a value type matrix (with C-ordering, information is in upper triangular part) -// print(C) <<"---\n"; -//// print(C) <<"---\n"; -// } -// return 0; -// { -// multi::array const A = { -// { 1. + 3.*I, 3.- 2.*I, 4.+ 1.*I}, -// { 9. + 1.*I, 7.- 8.*I, 1.- 3.*I} -// }; -// auto C = syrk(rotated(A)); // C = C^T = A^T*A, C is a value type matrix (with C-ordering) -// print(C) <<"---\n"; -// } -//#endif -//#endif -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_syrk_herk_fallback){ -// multi::array const a = { -// { 1., 3., 4.}, -// { 9., 7., 1.} -// }; -// { -// multi::array c({2, 2}, 9999.); -// namespace blas = multi::blas; -// using blas::filling; -// syrk(filling::lower, 1., a, 0., c); // c⸆=c=a⸆a=(a⸆a)⸆, `c` in lower triangular -// BOOST_REQUIRE( c[1][0] == 34. ); -// BOOST_REQUIRE( c[0][1] == 9999. ); -// } -//} -//#endif - -//#endif -#endif diff --git a/include/multi/adaptors/blas/test/axpy.cpp b/include/multi/adaptors/blas/test/axpy.cpp deleted file mode 100644 index 94cf8fbb8..000000000 --- a/include/multi/adaptors/blas/test/axpy.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS axpy" -#include - -#include -#include -#include - -#include - -#include - -namespace multi = boost::multi; -namespace blas = multi::blas; - -using complex = multi::complex; - -BOOST_AUTO_TEST_CASE(multi_blas_axpy_real) { - multi::array arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0}, - }; - auto const AC = arr; - multi::array const b = arr[2]; // NOLINT(readability-identifier-length) BLAS naming - - blas::axpy(2.0, b, arr[1]); // daxpy - BOOST_REQUIRE( arr[1][2] == 2.0*b[2] + AC[1][2] ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_axpy_double) { - multi::array const const_arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0}, - }; - multi::array arr = const_arr; - multi::array const b = const_arr[2]; // NOLINT(readability-identifier-length) conventional name in BLAS - - blas::axpy(2.0, b, arr[1]); // A[1] = 2*b + A[1], A[1]+= a*A[1] - BOOST_REQUIRE( arr[1][2] == 2.0*b[2] + const_arr[1][2] ); - - auto const I = complex{0, 1}; // NOLINT(readability-identifier-length) imaginary unit - - multi::array AC = {1.0 + 2.0 * I, 3.0 + 4.0 * I, 4.0 - 8.0 * I}; - multi::array BC(extensions(AC), complex{0.0, 0.0}); - - blas::axpy(+1.0, blas::real(AC), blas::real(BC)); - blas::axpy(-1.0, blas::imag(AC), blas::imag(BC)); - - // BOOST_REQUIRE( BC[2] == std::conj(AC[2]) ); - BOOST_REQUIRE( BC[2] == conj(AC[2]) ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_axpy_complex) { - multi::array arr = { - {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0}}, - {{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}}, - {{9.0, 0.0}, {10.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}}, - }; - auto const const_arr = arr; - - multi::array const x = arr[2]; // NOLINT(readability-identifier-length) BLAS naming - blas::axpy(complex{2.0, 0.0}, x, arr[1]); // zaxpy (2. is promoted to 2+I*0 internally and automatically) - BOOST_REQUIRE( arr[1][2] == 2.0*x[2] + const_arr[1][2] ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_axpy_complex_as_operator_plus_equal) { - using complex = std::complex; - - multi::array arr = { - {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0}}, - {{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}}, - {{9.0, 0.0}, {10.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}}, - }; - auto const carr = arr; - multi::array const y = arr[2]; // NOLINT(readability-identifier-length) BLAS naming - arr[1] += blas::axpy(2.0, y); // zaxpy (2. is promoted to 2+I*0 internally and automatically) - BOOST_REQUIRE( arr[1][2] == 2.0*y[2] + carr[1][2] ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_axpy_complex_as_operator_minus_equal) { - multi::array arr = { - {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0}}, - {{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}}, - {{9.0, 0.0}, {10.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}}, - }; - auto const AC = arr; - multi::array const x = arr[2]; // NOLINT(readability-identifier-length) BLAS naming - arr[1] -= blas::axpy(complex{2.0, 0.0}, x); // zaxpy (2. is promoted to 2+I*0 internally and automatically) - BOOST_REQUIRE( arr[1][2] == -2.0*x[2] + AC[1][2] ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_axpy_complex_context) { - multi::array arr = { - {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {4.0, 0.0}}, - {{5.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}}, - {{9.0, 0.0}, {10.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}}, - }; - auto const arr_copy = arr; - multi::array const arr2 = arr[2]; - blas::context ctxt{}; - blas::axpy(&ctxt, complex{2.0, 0.0}, arr2, arr[1]); // zaxpy (2. is promoted to 2+I*0 internally and automatically) - BOOST_REQUIRE( arr[1][2] == 2.0*arr2[2] + arr_copy[1][2] ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_axpy_operator_minus) { - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array x = { - {10.0, 0.0}, - {11.0, 0.0}, - {12.0, 0.0}, - {13.0, 0.0}, - }; - multi::array const y = x; // NOLINT(readability-identifier-length) BLAS naming - - using blas::operators::operator-; - - BOOST_REQUIRE( (x - y)[0] == complex{} ); - BOOST_REQUIRE( (y - x)[0] == complex{} ); - - using blas::operators::operator+; - - BOOST_REQUIRE( (x - (y+y))[0] == -x[0] ); - BOOST_REQUIRE( ((x+x) - y)[0] == +x[0] ); - - multi::array arr = { - {{1.0, 0.0}, {2.0, 0.0}}, - {{3.0, 0.0}, {4.0, 0.0}}, - }; - multi::array const arr2 = { - {1.0, 0.0}, - {2.0, 0.0}, - }; - BOOST_REQUIRE( (arr[0] - arr2)[0] == complex{} ); - BOOST_REQUIRE( (arr[0] - arr2)[1] == complex{} ); - - multi::array X = { /* NOLINT(readability-identifier-length) BLAS naming */ - {10.0, 0.0}, - {11.0, 0.0}, - {12.0, 0.0}, - {13.0, 0.0}, - }; - multi::array const Y = { /* NOLINT(readability-identifier-length) BLAS naming*/ - {10.0, 0.0}, - {11.0, 0.0}, - {12.0, 0.0}, - {13.0, 0.0}, - }; - - using blas::operators::operator-=; - X -= Y; - BOOST_REQUIRE( X[0] == complex{} ); -} diff --git a/include/multi/adaptors/blas/test/copy.cpp b/include/multi/adaptors/blas/test/copy.cpp deleted file mode 100644 index d74d69977..000000000 --- a/include/multi/adaptors/blas/test/copy.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS copy" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include - -#include "../../../array.hpp" - -#include "../../blas/copy.hpp" - -#include - -namespace multi = boost::multi; -namespace blas = multi::blas; - -BOOST_AUTO_TEST_CASE(multi_blas_copy_n) { - multi::array const x = {1.0, 2.0, 3.0, 4.0}; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = {5.0, 6.0, 7.0, 8.0}; // NOLINT(readability-identifier-length) BLAS naming - blas::copy_n(x.begin(), x.size(), y.begin()); - BOOST_REQUIRE( y == x ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_copy) { - multi::array const x = {1.0, 2.0, 3.0, 4.0}; // NOLINT(readability-identifier-length) BLAS naming - { - multi::array y = {5.0, 6.0, 7.0, 8.0}; // NOLINT(readability-identifier-length) BLAS naming - blas::copy(x, y); // segmentation fault in clang-11 - BOOST_REQUIRE( y == x ); - } - { - multi::array y = {5.0, 6.0, 7.0, 8.0}; // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( size(y) == size(x) ); - y() = blas::copy(x); - BOOST_REQUIRE( y == x ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_copy_real) { - namespace blas = multi::blas; - multi::array arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0} - }; - BOOST_REQUIRE( arr[0][2] == 3.0 ); - BOOST_REQUIRE( arr[2][2] == 11.0 ); - - blas::copy(arr[0], arr[2]); - BOOST_REQUIRE( arr[0][2] == 3.0 ); - BOOST_REQUIRE( arr[2][2] == 3.0 ); - -// multi::blas::copy(begin(A[1]), end(A[1]), begin(A[2])); // dcopy - blas::copy( arr[1]({0, size(arr[1])}), arr[2]({0, size(arr[1])}) ); - BOOST_REQUIRE( arr[1][3] == 8.0 ); - BOOST_REQUIRE( arr[2][3] == 8.0 ); - - multi::array AR3 = blas::copy(rotated(arr)[3]); // dcopy - BOOST_REQUIRE( AR3[1] == arr[1][3] ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_copy_row) { - multi::array const arr = { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - {7.0, 8.0, 9.0} - }; - multi::array y(multi::extensions_t<1>{multi::iextension{3}}); // NOLINT(readability-identifier-length) BLAS naming - blas::copy(rotated(arr)[0], y); - BOOST_REQUIRE( y == rotated(arr)[0] ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_copy_complex) { - using complex = std::complex; auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - multi::array arr = { - {1.0 + 3.0*I, 2.0 + 4.0*I, 3.0 + 5.0*I, 4.0 + 6.0*I}, - {5.0 + 0.0*I, 6.0 + 0.0*I, 7.0 + 0.0*I, 8.0 + 0.0*I}, - {9.0 + 0.0*I, 10.0 + 0.0*I, 11.0 + 0.0*I, 12.0 + 0.0*I} - }; - blas::copy(arr[0], arr[2]); - BOOST_REQUIRE( arr[0][2] == 3.0 + 5.0*I ); -} diff --git a/include/multi/adaptors/blas/test/dot.cpp b/include/multi/adaptors/blas/test/dot.cpp deleted file mode 100644 index 661c7bb3c..000000000 --- a/include/multi/adaptors/blas/test/dot.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS dot" -#include - -#include -#include - -#include -#include -#include - -namespace multi = boost::multi; -namespace blas = multi::blas; - -BOOST_AUTO_TEST_CASE(blas_dot_context) { - multi::array const x = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming - multi::array const y = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming - blas::context const ctxt; - { - auto res = +blas::dot(&ctxt, x, y); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( res == std::inner_product(begin(x), end(x), begin(y), 0.F) ); - } - { - float res = +blas::dot(&ctxt, x, y); // NOLINT(readability-identifier-length) BLAS naming - BOOST_TEST_REQUIRE( res == std::inner_product(begin(x), end(x), begin(y), 0.F) ); - } -} - -BOOST_AUTO_TEST_CASE(blas_dot_no_context) { - multi::array const x = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming - multi::array const y = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming - auto res = +blas::dot(x, y); - BOOST_REQUIRE( res == std::inner_product(begin(x), end(x), begin(y), 0.F) ); -} - -BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param) { - multi::array const x = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming - multi::array const y = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming - float res = NAN; - blas::dot(x, y, multi::array_ref(res)); - BOOST_REQUIRE( res == std::inner_product(begin(x), end(x), begin(y), 0.0F) ); -} - -BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_complex) { // if you get a segfaut here, your system may require -DRETURN_BY_STACK - using complex = std::complex; - multi::array const x = { {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - multi::array const y = { {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - complex res{0.0, 0.0}; - blas::dot(x, y, res); - // an isolated error here might mean that the dot and nrm2 interface for the BLAS library is not detected properly - BOOST_REQUIRE_EQUAL( real(res) , real(std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return alpha*std::conj(omega);})) ); - BOOST_REQUIRE_EQUAL( imag(res) , imag(std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return alpha*std::conj(omega);})) ); -} - -BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_complex_C) { - using complex = std::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - multi::array const x = {1.0 + 0.0*I, 2.0 + 0.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming - multi::array const y = {1.0 + 0.0*I, 2.0 + 2.0*I, 3.0 + 0.0*I}; // NOLINT(readability-identifier-length) BLAS naming - complex res{0.0, 0.0}; - blas::dot(blas::C(x), y, res); - BOOST_REQUIRE( res == std::inner_product(begin(x), end(x), begin(y), complex{0.0, 0.0}, std::plus<>{}, [](auto const& alpha, auto const& omega) {return conj(alpha)*omega;}) ); -} - -#if defined(CUDA_FOUND) and CUDA_FOUND -#include -BOOST_AUTO_TEST_CASE(blas_dot_no_context_out_param_complex_C_thrust) { - using complex = thrust::complex; complex const I{0.0, 1.0}; - multi::array const A = {1.0 + 0.*I, 2.0 + 0.0*I, 3.0 + 0.0*I}; - multi::array const B = {1.0 + 0.*I, 2.0 + 2.0*I, 3.0 + 0.0*I}; - - complex C; - blas::dot(blas::C(A), B, C); - BOOST_REQUIRE( C == std::inner_product(begin(A), end(A), begin(B), complex{0.0, 0.0}, std::plus<>{}, [](auto& a, auto& b){return conj(a)*b;}) ); -} -#endif - -BOOST_AUTO_TEST_CASE(multi_blas_dot_strided) { - multi::array const CA = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0} - }; - double res = std::numeric_limits::quiet_NaN(); - blas::dot_n(begin(CA[1]), size(CA[1]), begin(CA[2]), &res); - BOOST_REQUIRE( res == std::inner_product(begin(CA[1]), begin(CA[2]), end(CA[1]), 0.0) ); - - double const res2 = blas::dot(CA[1], CA[2]); - BOOST_REQUIRE( res == res2 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_dot_strided_context) { - multi::array const CA = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0} - }; - double res = std::numeric_limits::quiet_NaN(); - blas::context ctxt; - blas::dot_n(&ctxt, begin(CA[1]), size(CA[1]), begin(CA[2]), &res); - BOOST_REQUIRE( res == std::inner_product(begin(CA[1]), begin(CA[2]), end(CA[1]), 0.0) ); - - double const res2 = blas::dot(CA[1], CA[2]); - BOOST_REQUIRE( res == res2 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_dot_1d_real) { - multi::array const x = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming - multi::array const y = {1.0F, 2.0F, 3.0F}; // NOLINT(readability-identifier-length) BLAS naming - - using blas::dot; - BOOST_REQUIRE( 14.0 == dot(x, y) ); - BOOST_REQUIRE( dot(x, y) == 14.0F ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_dot_impl_real) { - multi::array const cA = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0} - }; - { - double const res = blas::dot(cA[1], cA[2]); - BOOST_REQUIRE( res == std::inner_product(begin(cA[1]), begin(cA[2]), end(cA[1]), 0.0) ); - } - { - double res = NAN; - blas::dot(cA[1], cA[2], res); - BOOST_REQUIRE( res == std::inner_product(begin(cA[1]), begin(cA[2]), end(cA[1]), 0.0) ); - } - { - double res = NAN; - auto res2 = blas::dot(cA[1], cA[2], res); - BOOST_REQUIRE( res == res2 ); - } - { - double const res = blas::dot(cA[1], cA[2]); - BOOST_REQUIRE( res == std::inner_product(begin(cA[1]), begin(cA[2]), end(cA[1]), 0.0) ); - BOOST_REQUIRE( blas::dot(cA[1], cA[2]) == blas::dot(cA[2], cA[1]) ); - } -} - -BOOST_AUTO_TEST_CASE(inq_case) { - multi::array x(multi::extensions_t<1>{multi::iextension{10}}, +1.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array const y(multi::extensions_t<1>{multi::iextension{10}}, -1.0); // NOLINT(readability-identifier-length) BLAS naming - - using blas::dot; - using blas::hermitized; - using blas::conj; - - auto res = dot(x, y); - auto res2 = dot(hermitized(x), y); - - BOOST_REQUIRE(res == res2); - - auto res3 = dot(blas::conj(x), y); // conjugation doesn't do anything for real array - BOOST_REQUIRE(res3 == res); - - auto d_arr = dot(blas::C(x), y); - BOOST_REQUIRE(d_arr == res); - - static_assert( not std::is_same{} ); - - using blas::C; - double const d_doub = dot(C(x), y); - - BOOST_REQUIRE( d_doub == d_arr ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_dot_impl_complex) { - namespace blas = multi::blas; - - using complex = std::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + I, 2.0 + 3.0*I, 3.0 + 2.0*I, 4.0 - 9.0*I}, - {5.0 + 2.0*I, 6.0 + 6.0*I, 7.0 + 2.0*I, 8.0 - 3.0*I}, - {9.0 + 1.0*I, 10.0 + 9.0*I, 11.0 + 1.0*I, 12.0 + 2.0*I} - }; - { - complex c{0.0, 0.0}; blas::dot(A[1], A[2], c); // NOLINT(readability-identifier-length) BLAS naming - BOOST_TEST_REQUIRE( c == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}) ); - } - { - complex c = blas::dot(A[1], A[2]); // NOLINT(readability-identifier-length) BLAS naming - BOOST_TEST_REQUIRE( c == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}) ); - } - { - complex c = blas::dot(A[1], blas::C(A[2])); // NOLINT(readability-identifier-length) BLAS naming - BOOST_TEST_REQUIRE( c == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}, std::plus<>{}, [](auto alpha, auto omega) {return alpha*conj(omega);}) ); - } - { - complex c = blas::dot(blas::C(A[1]), A[2]); // NOLINT(readability-identifier-length) BLAS naming - BOOST_TEST_REQUIRE( c == inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}, std::plus<>{}, [](auto alpha, auto omega) {return conj(alpha)*omega;}) ); - } - { - complex c = blas::dot(blas::conj(A[1]), A[2]); // NOLINT(readability-identifier-length) BLAS naming - BOOST_TEST_REQUIRE( c == inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}, std::plus<>{}, [](auto alpha, auto omega) {return conj(alpha)*omega;}) ); - } - { - complex c = blas::dot(blas::C(A[1]), A[2]); // NOLINT(readability-identifier-length) BLAS naming - BOOST_TEST_REQUIRE( c == std::inner_product(begin(A[1]), end(A[1]), begin(A[2]), complex{0.0, 0.0}, std::plus<>{}, [](auto alpha, auto omega) {return conj(alpha)*omega;}) ); - } -} diff --git a/include/multi/adaptors/blas/test/gemm.cpp b/include/multi/adaptors/blas/test/gemm.cpp deleted file mode 100644 index 57e6d17bd..000000000 --- a/include/multi/adaptors/blas/test/gemm.cpp +++ /dev/null @@ -1,1921 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS gemm" -#include - -#include "../../../adaptors/blas/gemm.hpp" -#include - -#include - -namespace multi = boost::multi; -namespace blas = multi::blas; - -BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_T_sub) { - namespace blas = multi::blas; - - multi::array A({100, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array B({4, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm(1.0, A({0, 100}, {1, 2}), blas::T(B)({0, 1}, {0, 1}), 0.0, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(C[99][0] == 1.0); -} - -BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_H_sub) { - multi::array A({100, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array B({4, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm(1., A({0, 100}, {1, 2}), blas::H(B)({0, 1}, {0, 1}), 0.0, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(C[99][0] == 1.0); -} - -BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_H_sub_6) { - multi::array A({100, 4}, 2.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array B({4, 4}, 3.0); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm(1., A({0, 100}, {1, 2}), blas::H(B)({0, 1}, {0, 1}), 0.0, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(C[99][0] == 6.0); -} - -BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_H_copy) { - multi::array A({100, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array B({4, 4}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - - auto C = +blas::gemm(1., A({0, 100}, {1, 2}), blas::H(B)({2, 3}, {2, 3})); // c=ab, c⸆=b⸆a⸆ // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE(C[99][0] == 1.0); -} - -BOOST_AUTO_TEST_CASE(adaptor_blas_complex_100x1_1x1) { - using complex = std::complex; - multi::array const A({100, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({1, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, {0.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm({1.0, 0.0}, A, B, {0.0, 0.0}, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(C[99][0] == 1.0); -} - -BOOST_AUTO_TEST_CASE(adaptor_blas_complex_100x1_1x1_T) { - using complex = std::complex; - multi::array const A({100, 1}, complex{1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({1, 1}, complex{1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, complex{0.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm(complex{1.0, 0.0}, A, blas::T(B), complex{0.0, 0.0}, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( C[99][0] == 1.0 ); -} - -#if 1 -BOOST_AUTO_TEST_CASE(adaptor_blas_complex_100x1_1x1_H) { - using complex = std::complex; // complex const I{0, 1}; - multi::array const A({100, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({1, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, {0.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm({1.0, 0.0}, A, blas::H(B), {0.0, 0.0}, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( C[99][0] == 1.0 ); -} - -BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1) { - using complex = std::complex; - multi::array const A({100, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({1, 1}, {1.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, {0.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm({1.0, 0.0}, A, B, {0.0, 0.0}, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(C[99][0] == 1.0); -} - -BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_T) { - multi::array const A({100, 1}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({1, 1}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm(1.0, A, blas::T(B), 0.0, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(C[99][0] == 1.0); -} - -BOOST_AUTO_TEST_CASE(adaptor_blas_double_100x1_1x1_H) { - multi::array const A({100, 1}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({1, 1}, 1.0); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({100, 1}, 0.0); // NOLINT(readability-identifier-length) BLAS naming - - blas::gemm(1.0, A, blas::H(B), 0.0, C); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(C[99][0] == 1.0); -} - -BOOST_AUTO_TEST_CASE(multi_blas_gemm_square_real) { - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const a = { - {1.0, 3.0, 4.0}, - {9.0, 7.0, 1.0}, - {1.0, 2.0, 3.0}, - }; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const b = { - {11.0, 12.0, 4.0}, - { 7.0, 19.0, 1.0}, - {11.0, 12.0, 4.0}, - }; - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, a, b, 0.0, c); - BOOST_REQUIRE( c[2][1] == 86.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - BOOST_REQUIRE( size( a) == size( c) ); - BOOST_REQUIRE( size(~b) == size(~c) ); - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); - BOOST_REQUIRE( c[2][1] == 86.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, a, blas::T(b), 0.0, c); - BOOST_REQUIRE( c[2][1] == 48.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1., a.begin(), a.size(), blas::T(b).begin(), 0.0, c.begin()); - BOOST_REQUIRE( c[2][1] == 48.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, blas::T(a), b, 0.0, c); - BOOST_REQUIRE( c[2][1] == 103.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1.0, begin(blas::T(a)), size(blas::T(a)), begin(b), 0.0, begin(c)); - BOOST_REQUIRE( c[2][1] == 103.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, blas::T(a), blas::T(b), 0.0, c); - BOOST_REQUIRE( c[2][1] == 50.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1.0, begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0.0, begin(c)); - BOOST_REQUIRE( c[2][1] == 50.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, a, blas::T(b), 0.0, c); - BOOST_REQUIRE( c[2][1] == 48.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1.0, begin(a), size(a), begin(blas::T(b)), 0.0, begin(c)); - BOOST_REQUIRE( c[2][1] == 48.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, blas::T(a), b, 0.0, c); - BOOST_REQUIRE( c[2][1] == 103.0 ); - } - { - multi::array c({size(a), size(~b)}, 9999.0); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1.0, begin(blas::T(a)), size(blas::T(a)), begin(b), 0.0, begin(c)); - BOOST_REQUIRE( c[2][1] == 103.0 ); - } - { - multi::array c({size(a), size(rotated(b))}, 9999.0); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm(2.0, blas::H(a), blas::H(b), 0.0, c); - BOOST_REQUIRE( c[2][1] == 100.0 ); - } - { - multi::array c = blas::gemm(2.0, blas::H(a), blas::H(b)); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( c[2][1] == 100.0 ); - } - { - multi::array const c = blas::gemm(2.0, blas::H(a), blas::H(b)); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( c[2][1] == 100.0 ); - } - { - multi::array c({size(a), size(rotated(b))}, 9999.0); // NOLINT(readability-identifier-length) BLAS naming - c = blas::gemm(2.0, blas::H(a), blas::H(b)); - BOOST_REQUIRE( c[2][1] == 100.0 ); - } - { - multi::array c; // NOLINT(readability-identifier-length) BLAS naming - c = blas::gemm(2.0, blas::H(a), blas::H(b)); - BOOST_REQUIRE( c[2][1] == 100.0 ); - } - { - multi::array c({size(a), size(rotated(b))}, 9999.0); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm_n(2.0, begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0.0, begin(c)); - BOOST_REQUIRE( c[2][1] == 100.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_square) { - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const a = { - {1.0, 3.0}, - {9.0, 7.0}, - }; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const b = { - {11.0, 12.0}, - { 7.0, 19.0}, - }; - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 148.0 ); - } - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - - blas::context const ctxt; - blas::gemm_n(&ctxt, 1.0, begin(a), size(a), begin(b), 0.0, begin(c)); - BOOST_REQUIRE( c[1][0] == 148.0 ); - } - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, ~a, b, 0.0, c); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE(( c[1][1] == 169.0 and c[1][0] == 82.0 )); - } - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - - blas::context const ctxt; - blas::gemm_n(&ctxt, 1.0, begin(~a), size(~a), begin(b), 0.0, begin(c)); - BOOST_REQUIRE(( c[1][1] == 169 and c[1][0] == 82 )); - } - { - multi::array const c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - - blas::context const ctxt; - blas::gemm_n(&ctxt, 1.0, begin(~a), size(~a), begin(b), 0.0, begin(~c)); - BOOST_REQUIRE( (~c)[1][1] == 169 ); - BOOST_REQUIRE( (~c)[1][0] == 82 ); - } - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, a, ~b, 0.0, c); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( c[1][0] == 183.0 ); - } - { - // TODO(correaa) fix sfinae of const c - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - - blas::context const ctxt; - blas::gemm_n(&ctxt, 1.0, begin(a), size(a), begin(~b), 0.0, begin(c)); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( c[1][0] == 183.0 ); - } - { - // NOLINTNEXTLINE(misc-const-correctness) TODO(correaa) fix sfinae of const c - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, a, ~b, 0.0, ~c); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( (~c)[1][0] == 183.0 ); - } - { - // NOLINTNEXTLINE(misc-const-correctness) TODO(correaa) fix sfinae of const c - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1.0, begin(a), size(a), begin(~b), 0.0, begin(~c)); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( (~c)[1][0] == 183.0 ); - } - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, ~a, ~b, 0.0, c); // c=a⸆b⸆, c⸆=ba - BOOST_REQUIRE( c[1][0] == 117.0 ); - } - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1.0, begin(~a), size(~a), begin(~b), 0.0, begin(c)); // c=a⸆b⸆, c⸆=ba - BOOST_REQUIRE( c[1][0] == 117.0 ); - } - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, ~a, ~b, 0.0, ~c); // c⸆=a⸆b⸆, c=ba - BOOST_REQUIRE( c[0][1] == 117.0 ); - } - { - multi::array c({2, 2}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1.0, begin(~a), size(~a), begin(~b), 0.0, begin(~c)); // c⸆=a⸆b⸆, c=ba - BOOST_REQUIRE( c[0][1] == 117.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare) { - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const a = { - {1.0, 3.0, 1.0}, - {9.0, 7.0, 1.0}, - }; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const b = { - {11.0, 12.0, 1.0}, - { 7.0, 19.0, 1.0}, - { 1.0, 1.0, 1.0}, - }; - { - multi::array c({2, 3}); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 17 ); - } - { - multi::array c({2, 3}); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 17.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare_automatic) { - namespace blas = multi::blas; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const a = { - {1.0, 3.0, 1.0}, - {9.0, 7.0, 1.0}, - }; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const b = { - {11.0, 12.0, 4.0, 8.0}, - { 7.0, 19.0, 2.0, 7.0}, - { 5.0, 3.0, 3.0, 1.0}, - }; - { - multi::array c({size(a), size(~b)}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 53.0 ); - } - { - multi::array c({size(a), size(~b)}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 53.0 ); - } - { - multi::array c({2, 4}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm(0.1, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); - } - { - multi::array c({2, 4}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n(0.1, begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); - } - { - auto c = +blas::gemm(0.1, a, b); // c=ab, c⸆=b⸆a⸆ // NOLINT(readability-identifier-length) conventional BLAS naming - BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); - } - { - multi::array c = blas::gemm(0.1, a, b); // NOLINT(readability-identifier-length) conventional BLAS naming - BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_gemm_nh) { - using complex = std::complex; - complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit - - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const a = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - { - auto c = +blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c = blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( c[1][0] == 7.-10.*I ); - BOOST_REQUIRE( c[0][1] == 7.+10.*I ); - } - { - multi::array c = blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† // NOLINT(readability-identifier-length) conventional BLAS naming - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - c = blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - c() = blas::gemm(1.0, a, blas::H(a)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.*I ); - } - { - multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm({1.0, 0.0}, a, blas::H(a), {0.0, 0.0}, c); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm_n({1.0, 0.0}, begin(a), size(a), begin(blas::H(a)), {0.0, 0.0}, begin(c)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7. - 10.*I ); - BOOST_REQUIRE( c[0][1] == 7. + 10.*I ); - } -} - -#if defined(CUDA_FOUND) -#include -BOOST_AUTO_TEST_CASE(multi_blas_gemm_nh_thrust) { - using complex = thrust::complex; - complex const I{0.0, 1.0}; - multi::array const a = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I} - }; - { - auto c = +blas::gemm(1.0, a, blas::hermitized(a)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c = blas::gemm(1.0, a, blas::hermitized(a)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c = blas::gemm(1.0, a, blas::hermitized(a)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c({2, 2}); - c = blas::gemm(1.0, a, blas::hermitized(a)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., a, blas::hermitized(a), 0.0, c); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1.0, begin(a), size(a), begin(blas::H(a)), 0.0, begin(c)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[1][0] == 7.0 - 10.0*I ); - BOOST_REQUIRE( c[0][1] == 7.0 + 10.0*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_gemm_elongated) { - using complex = std::complex; - complex const I{0.0, 1.0}; - multi::array const a = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I} - }; - { - multi::array c({1, 1}); - blas::gemm(1.0, a, blas::H(a), 0.0, c); // c=aa†, c†=aa† - BOOST_REQUIRE( c[0][0] == 87.0 + 0.0*I ); - } - { - multi::array c({1, 1}); - blas::gemm_n(1.0, begin(a), size(a), begin(blas::H(a)), 0.0, begin(c)); // c=aa†, c†=aa† - BOOST_REQUIRE( c[0][0] == 87.0 + 0.0*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1_bisbis) { - using complex = std::complex; - complex const I{0.0, 1.0}; - multi::array const a = { - {1.0 + 2.0 * I}, - {9.0 - 1.0 * I}, - {1.0 + 1.0 * I}}; - multi::array const b = { - {11.0 - 2.0 * I, 7.0 - 3.0 * I, 8.0 - 1.0 * I} - }; - { - multi::array c({1, 1}); - - BOOST_REQUIRE( size(blas::H(a)) == 1 ); - BOOST_REQUIRE( size(blas::H(b)[0]) == 1 ); - - blas::gemm(1.0, blas::H(a), blas::H(b), 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 84.0 + 7.0*I ); - } - { - multi::array c({1, 1}); - blas::gemm_n(1.0, begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 84.0 + 7.0*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_empty) { - multi::array const a({0, 5}); - BOOST_REQUIRE( size( a) == 0 ); - BOOST_REQUIRE( size(~a) == 5 ); - BOOST_REQUIRE( a.is_empty() ); - - multi::array const b({5, 0}); - BOOST_REQUIRE( size( b) == 0 ); - BOOST_REQUIRE( size(~b) == 0 ); - BOOST_REQUIRE( b.is_empty() ); - { - multi::array c; - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - } - { - multi::array c; - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare2) { - multi::array const a = { - {1.0, 3.0}, - {9.0, 7.0}, - {1.0, 1.0}, - }; - multi::array const b = { - {11.0, 12.0}, - { 7.0, 19.0}, - }; - { - multi::array c({size(a), size(~b)}); - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[2][1] == 31.0 ); - } - { - multi::array c({size(a), size(~b)}); - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[2][1] == 31.0 ); - } - { - multi::array c({size(~b), size(a)}); - blas::gemm(1.0, a, b, 0.0, ~c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 31.0 ); - } - { - multi::array c({size(~b), size(a)}); - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(~c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 31.0 ); - } - { - auto ar = +~a; - multi::array c({3, 2}); - blas::gemm(1.0, ~ar, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[2][1] == 31.0 ); - } - { - auto ar = +~a; - multi::array c({3, 2}); - blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[2][1] == 31.0 ); - } - { - auto ar = +~a; - multi::array c({2, 3}); - blas::gemm(1.0, ~ar, b, 0.0, ~c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 31.0 ); - } - { - auto ar = +~a; - multi::array c({2, 3}); - blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(~c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 31.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x2_2x2) { - multi::array const a = { - {1.0, 3.0}, - {9.0, 4.0}, - }; - multi::array const b = { - {11.0, 12.0}, - { 7.0, 19.0}, - }; - { - multi::array c({2, 2}); - blas::gemm(1.0, ~a, b, 0.0, c); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[1][0] == 61.0 ); - - blas::gemm(1.0, ~a, b, 0.0, ~c); // c⸆=a⸆b, c=b⸆a - BOOST_REQUIRE( c[0][1] == 61.0 ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1.0, begin(~a), size(~a), begin(b), 0.0, begin(c)); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[1][0] == 61.0 ); - - blas::gemm_n(1.0, begin(~a), size(~a), begin(b), 0.0, begin(~c)); // c⸆=a⸆b, c=b⸆a - BOOST_REQUIRE( c[0][1] == 61.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x2) { - multi::array const a = { - {1.0, 3.0}, - {9.0, 4.0}, - {1.0, 5.0}, - }; - multi::array const b = { - {11.0, 12.0}, - { 7.0, 19.0}, - { 8.0, 1.0}, - }; - { - multi::array c({2, 2}); - blas::gemm(1.0, ~a, b, 0.0, c); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[1][0] == 101.0 ); - - blas::gemm(1., ~a, b, 0., ~c); // c⸆=a⸆b, c=b⸆a - BOOST_REQUIRE( c[0][1] == 101 ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1.0, begin(~a), size(~a), begin(b), 0.0, begin(c)); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[1][0] == 101.0 ); - - blas::gemm_n(1.0, begin(~a), size(~a), begin(b), 0.0, begin(~c)); // c⸆=a⸆b, c=b⸆a - BOOST_REQUIRE( c[0][1] == 101.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_3x2) { - multi::array const a = { - {1.0, 9.0, 1.0} - }; - BOOST_REQUIRE( stride(~a) == 1 ); - BOOST_REQUIRE( stride( a) == 3 ); - multi::array const b = { - {11.0, 12.0}, - { 7.0, 19.0}, - { 8.0, 1.0}, - }; - { - multi::array c({size(a), size(~b)}); - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 184.0 ); - } - { - multi::array c({size(a), size(~b)}); - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 184.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar)}); - blas::gemm(1.0, ~ar, b, 0.0, ~c); // c⸆=a⸆b, c=b⸆a - BOOST_REQUIRE( c[1][0] == 184.0 ); - } - { - auto ar = +~a; - BOOST_REQUIRE( size(~ar) == 1 ); - BOOST_REQUIRE( begin(~ar).stride() == 1 ); - BOOST_REQUIRE( begin(~ar)->stride() == 1 ); - BOOST_REQUIRE( begin( ar)->stride() == 1 ); - - multi::array c({size(~b), size(~ar)}); - BOOST_REQUIRE( begin( c).stride() == 1 ); - BOOST_REQUIRE( begin(~c).stride() == 1 ); - BOOST_REQUIRE( begin(c)->stride() == 1 ); - - BOOST_REQUIRE( begin(b) ); - blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(~c)); // c⸆=a⸆b, c=b⸆a - BOOST_REQUIRE( c[1][0] == 184.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complexreal_1x3_3x2) { - using complex = std::complex; - multi::array const a = { - {1.0, 9.0, 1.0} - }; - BOOST_REQUIRE( stride(~a) == 1 ); - BOOST_REQUIRE( stride( a) == 3 ); - multi::array const b = { - {11.0, 12.0}, - { 7.0, 19.0}, - { 8.0, 1.0}, - }; - { - multi::array c({size(a), size(~b)}); - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 184.0 ); - } - { - multi::array c({size(a), size(~b)}); - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 184.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar)}); - blas::gemm(1.0, ~ar, b, 0.0, ~c); // c⸆=a⸆b, c=b⸆a - BOOST_REQUIRE( c[1][0] == 184.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar)}); - blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(~c)); // c⸆=a⸆b, c=b⸆a - BOOST_REQUIRE( c[1][0] == 184.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_part_3x2) { - multi::array const a = { - {1.0, 9.0, 1.0}, - {3.0, 3.0, 3.0}, - }; - BOOST_REQUIRE( stride(~a) == 1 ); - BOOST_REQUIRE( stride( a) == 3 ); - multi::array const b = { - {11.0, 12.0}, - { 7.0, 19.0}, - { 8.0, 1.0}, - }; - { - multi::array c({size(a({0, 1})), size(~b)}); - blas::gemm(1.0, a({0, 1}), b, 0.0, c); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[0][1] == 184.0 ); - } - { - multi::array c({size(a({0, 1})), size(~b)}); - blas::gemm_n(1.0, begin(a({0, 1})), size(a({0, 1})), begin(b), 0.0, begin(c)); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[0][1] == 184.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); - blas::gemm(1.0, ~(ar(extension(ar), {0, 1})), b, 0.0, ~c); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[1][0] == 184.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); - blas::gemm_n(1.0, begin(~(ar(extension(ar), {0, 1}))), size(~(ar(extension(ar), {0, 1}))), begin(b), 0., begin(~c)); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[1][0] == 184.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complexreal_1x3_part_3x2) { - using complex = std::complex; - multi::array const a = { - {1.0, 9.0, 1.0}, - {3.0, 3.0, 3.0}, - }; - BOOST_REQUIRE( stride(~a) == 1 ); - BOOST_REQUIRE( stride( a) == 3 ); - multi::array const b = { - {11.0, 12.0}, - { 7.0, 19.0}, - { 8.0, 1.0} - }; - { - multi::array c({size(a({0, 1})), size(~b)}); - blas::gemm(1.0, a({0, 1}), b, 0.0, c); - BOOST_REQUIRE( c[0][1] == 184.0 ); - } - { - multi::array c({size(a({0, 1})), size(~b)}); - blas::gemm_n(1.0, begin(a({0, 1})), size(a({0, 1})), begin(b), 0.0, begin(c)); - BOOST_REQUIRE( c[0][1] == 184.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); - blas::gemm(1.0, ~(ar(extension(ar), {0, 1})), b, 0.0, ~c); - BOOST_REQUIRE( c[1][0] == 184.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); - blas::gemm_n(1.0, begin(~(ar(extension(ar), {0, 1}))), size(~(ar(extension(ar), {0, 1}))), begin(b), 0.0, begin(~c)); - BOOST_REQUIRE( c[1][0] == 184.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x1) { - multi::array const a = { - {1.0, 9.0, 1.0}, - {3.0, 3.0, 3.0}, - }; - BOOST_REQUIRE( stride(~a) == 1 ); - BOOST_REQUIRE( stride( a) == 3 ); - multi::array const b = { - {11.0}, - {7.0}, - {8.0}}; - { - multi::array c({size(a), size(~b)}); - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 82.0 ); - BOOST_REQUIRE( c[1][0] == 78.0 ); - } - { - multi::array c({size(a), size(~b)}); - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(c)); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[0][0] == 82.0 ); - BOOST_REQUIRE( c[1][0] == 78.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); - blas::gemm(1.0, ~(ar(extension(ar), {0, 1})), b, 0.0, ~c); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[0][0] == 82.0 ); - } - { - auto ar = +~a; - multi::array c({size(~b), size(~ar(extension(ar), {0, 1}))}); - blas::gemm_n(1., begin(~(ar(extension(ar), {0, 1}))), size(~(ar(extension(ar), {0, 1}))), begin(b), 0., begin(~c)); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE( c[0][0] == 82.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_2x3_3x1_bis) { - multi::array const a = { - {1.0, 9.0, 1.0}, - {3.0, 4.0, 5.0}, - }; - multi::array const b = { - {11.0}, - {7.0}, - {8.0}}; - - { - multi::array c({1, 2}); - blas::gemm(1.0, a, b, 0.0, ~c); // c⸆=ab, c=b⸆a⸆ - BOOST_REQUIRE( (~c)[0][0] == 82.0 ); - BOOST_REQUIRE( (~c)[1][0] == 101.0 ); - } - { - multi::array c({1, 2}); - blas::gemm_n(1.0, begin(a), size(a), begin(b), 0.0, begin(~c)); // c⸆=ab, c=b⸆a⸆ - BOOST_REQUIRE( (~c)[0][0] == 82.0 ); - BOOST_REQUIRE( (~c)[1][0] == 101.0 ); - } - { - multi::array c({2, 1}); - blas::gemm(1.0, a, b, 0.0, c); // c⸆=ab, c=b⸆a⸆ - BOOST_REQUIRE( (~c)[0][1] == 101.0 ); - BOOST_REQUIRE( c[1][0] == 101.0 ); - } - { - multi::array c({2, 1}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c⸆=ab, c=b⸆a⸆ - BOOST_REQUIRE( (~c)[0][1] == 101.0 ); - BOOST_REQUIRE( c[1][0] == 101.0 ); - } - { - multi::array c({1, 2}); - auto ar = +~a; - blas::gemm(1., ~ar, b, 0., ~c); // c⸆=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 101.0 ); - } - { - multi::array c({1, 2}); - auto ar = +~a; - blas::gemm_n(1., begin(~ar), size(~ar), begin(b), 0., begin(~c)); // c⸆=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 101.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_1x3_3x1) { - multi::array const a = { - {1.0, 9.0, 1.0}, - }; - multi::array const b = { - {11.0}, - {7.0}, - {8.0}}; - { - multi::array c({1, 1}); - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 82.0 ); - } - { - multi::array c({1, 1}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); - BOOST_REQUIRE( c[0][0] == 82.0 ); - } - { - multi::array c({1, 1}); - auto ar = +~a; - blas::gemm(1.0, ~ar, b, 0.0, c); - BOOST_REQUIRE( c[0][0] == 82.0 ); - } - { - multi::array c({1, 1}); - auto ar = +~a; - blas::gemm_n(1.0, begin(~ar), size(~ar), begin(b), 0.0, begin(c)); - BOOST_REQUIRE( c[0][0] == 82.0 ); - } - { - multi::array c({1, 1}); - auto br = +~b; - blas::gemm(1.0, a, ~br, 0.0, c); - BOOST_REQUIRE( c[0][0] == 82.0 ); - } - { - multi::array c({1, 1}); - BOOST_REQUIRE( begin(c). stride() == 1 ); - BOOST_REQUIRE( begin(c)->stride() == 1 ); - - auto br = +~b; - // BOOST_REQUIRE( begin(br). stride() == 1 ); - BOOST_REQUIRE( begin( br)->stride() == 1 ); - - BOOST_REQUIRE(begin(a)->stride() == 1); - BOOST_REQUIRE( begin(~br). stride() == 1 ); - // BOOST_REQUIRE( begin(~br)->stride() == 1 ); - BOOST_REQUIRE(begin(c)->stride() == 1); - BOOST_REQUIRE(begin(c).stride() == 1); - BOOST_REQUIRE(size(a) == 1); - - blas::gemm_n(1.0, begin(a), size(a), begin(~br), 0.0, begin(c)); - BOOST_REQUIRE( c[0][0] == 82.0 ); - } - { - multi::array c({1, 1}); - auto br = +~b; - blas::gemm(1.0, a, blas::H(br), 0.0, c); - BOOST_REQUIRE( c[0][0] == 82.0 ); - } - { - multi::array c({1, 1}); - auto br = +~b; - blas::gemm_n(1.0, begin(a), size(a), begin(blas::H(br)), 0.0, begin(c)); - BOOST_REQUIRE( c[0][0] == 82.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square) { - using complex = std::complex; - constexpr complex I{0.0, 1.0}; - multi::array const a = { - {1.0 + 3.0 * I, 3.0 + 2.0 * I}, - {9.0 + 1.0 * I, 7.0 + 1.0 * I}, - }; - multi::array const b = { - {11.0 + 2.0 * I, 12.0 + 4.0 * I}, - { 7.0 + 1.0 * I, 19.0 - 9.0 * I}, - }; - { - multi::array c({2, 2}); - blas::gemm(1.0, a, b, 0.0, c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 145.0 + 43.0*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 145. + 43.*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., ~a, b, 0., c); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE(( c[1][1] == 170.-8.*I and c[1][0] == 77.+42.*I )); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(~a), size(~a), begin(b), 0., begin(c)); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE(( c[1][1] == 170.-8.*I and c[1][0] == 77.+42.*I )); - } - { - multi::array c({2, 2}); - blas::gemm(1., a, ~b, 0., c); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( c[1][0] == 177.+69.*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(a), size(a), begin(~b), 0., begin(c)); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( c[1][0] == 177.+69.*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=a⸆b⸆, c⸆=ba - BOOST_REQUIRE( c[1][0] == 109. + 68.*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0., begin(c)); // c=a⸆b⸆, c⸆=ba - BOOST_REQUIRE( c[1][0] == 109. + 68.*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::T(a), blas::T(b), 0., blas::T(c)); // c⸆=a⸆b⸆, c=ba - BOOST_REQUIRE( c[0][1] == 109.+68.*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0., begin(blas::T(c))); // c⸆=a⸆b⸆, c=ba - BOOST_REQUIRE( c[0][1] == 109.+68.*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_1x3_3x1) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I, 9. - 1. * I, 1. + 1. * I}, - }; - multi::array const b = { - {11. - 2. * I}, - {7. - 3. * I}, - {8. - 1. * I}}; - { - multi::array c({1, 1}); - blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 84.-7.*I ); - } - { - multi::array c({1, 1}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 84.-7.*I ); - } - { - multi::array c({1, 1}); - auto ar = +~a; - blas::gemm(1., ~ar, b, 0., c); // c=ab, c⸆=ba - BOOST_REQUIRE( c[0][0] == 84.-7.*I ); - } - { - multi::array c({1, 1}); - auto ar = +~a; - blas::gemm_n(1., begin(~ar), size(~ar), begin(b), 0., begin(c)); // c=ab, c⸆=ba - BOOST_REQUIRE( c[0][0] == 84.-7.*I ); - } - { - multi::array c({1, 1}); - auto br = +~b; - blas::gemm(1., a, ~br, 0., c); - BOOST_REQUIRE( c[0][0] == 84.-7.*I ); - } - { - multi::array c({1, 1}); - auto br = +~b; - blas::context ctxt; - blas::gemm_n(ctxt, 1., begin(a), size(a), begin(~br), 0., begin(c)); - BOOST_REQUIRE( c[0][0] == 84.-7.*I ); - } - { - multi::array c({1, 1}); - auto br = +~b; - blas::gemm(1., a, blas::H(br), 0., ~c); - BOOST_REQUIRE( c[0][0] == 80. + 53.*I ); - } - { - multi::array c({1, 1}); - auto br = +~b; - blas::gemm_n(1., begin(a), size(a), begin(blas::H(br)), 0., begin(~c)); - BOOST_REQUIRE( c[0][0] == 80. + 53.*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_hermitized_square) { - using complex = std::complex; - constexpr complex I{0, 1}; - multi::array const a = { - {1. + 3. * I, 3. + 2. * I}, - {9. + 1. * I, 7. + 1. * I}, - }; - multi::array const b = { - {11. + 2. * I, 12. + 4. * I}, - { 7. + 1. * I, 19. - 9. * I}, - }; - { - multi::array c({2, 2}); - blas::gemm(1., a, b, 0., c); // c=ab, c†=b†a† - BOOST_REQUIRE( c[1][0] == 145. + 43.*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c†=b†a† - BOOST_REQUIRE( c[1][0] == 145. + 43.*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::H(a), blas::H(b), 0., c); // c=a†b†, c†=ba - BOOST_REQUIRE( c[1][0] == 109. - 68.*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0., begin(c)); // c=a†b†, c†=ba - BOOST_REQUIRE( c[1][0] == 109. - 68.*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::H(a), blas::H(b), 0., blas::H(c)); // c†=a†b†, c=ba - BOOST_REQUIRE( c[1][0] == 184. - 40.*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::H(a), b, 0., c); // c=a†b, c†=b†a - BOOST_REQUIRE( c[1][0] == 87. - 16.*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=a†b, c†=b†a - BOOST_REQUIRE( c[1][0] == 87. - 16.*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., a, blas::H(b), 0., c); // c=ab†, c†=ba† - BOOST_REQUIRE( c[1][0] == 189. - 23.*I ); - } - { - multi::array c({2, 2}); - c = blas::gemm(1., a, blas::H(b)); // c=ab†, c†=ba† - BOOST_REQUIRE( c[1][0] == 189. - 23.*I ); - } - { - multi::array c = blas::gemm(1., a, blas::H(b)); // c=ab†, c†=ba† - BOOST_REQUIRE( size(c) == 2 ); - BOOST_REQUIRE( c[1][0] == 189. - 23.*I ); - } - { - auto c = multi::array(blas::gemm(1., a, blas::H(b))); // c=ab†, c†=ba† - BOOST_REQUIRE( size(c) == 2 ); - BOOST_REQUIRE( c[1][0] == 189. - 23.*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab†, c†=ba† - BOOST_REQUIRE( c[1][0] == 189. - 23.*I ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::H(a), blas::H(b), 0., c); // c=a†b†, c†=ba - BOOST_REQUIRE( c[1][0] == 109. - 68.*I); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0., begin(c)); // c=a†b†, c†=ba - BOOST_REQUIRE( c[1][0] == 109. - 68.*I); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I}, - {9. - 1. * I}, - {1. + 1. * I}}; - multi::array const b = { - {11. - 2. * I}, - {7. - 3. * I}, - {8. - 1. * I}}; - { - multi::array c({1, 1}); - blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 80.-53.*I ); - } - { - multi::array c({1, 1}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 80.-53.*I ); - } - { - multi::array c({1, 1}); - blas::gemm(1., blas::H(a), b, 0., c); // c=a†b, c†=b†a - BOOST_REQUIRE( c[0][0] == 80.-53.*I ); - } - { - multi::array c({1, 1}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=a†b, c†=b†a - BOOST_REQUIRE( c[0][0] == 80.-53.*I ); - } - { - multi::array c({1, 1}); - auto ha = +blas::hermitized(a); - blas::gemm(1., ha, b, 0., c); - BOOST_REQUIRE( c[0][0] == 80.-53.*I ); - - blas::gemm(1., blas::H(b), a, 0., c); - BOOST_REQUIRE( c[0][0] == 80.+53.*I ); - } - { - multi::array c({1, 1}); - auto ha = +blas::hermitized(a); - blas::gemm_n(1., begin(ha), size(ha), begin(b), 0., begin(c)); - BOOST_REQUIRE( c[0][0] == 80.-53.*I ); - - blas::gemm_n(1., begin(blas::H(b)), size(blas::H(b)), begin(a), 0., begin(c)); - BOOST_REQUIRE( c[0][0] == 80.+53.*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_1x3_3x2) { - using complex = std::complex; - constexpr complex I{0, 1}; - multi::array const a = { - {1. + 2. * I, 9. - 1. * I, 1. + 1. * I} - }; - multi::array const b = { - {11. - 2. * I, 5. + 2. * I}, - { 7. - 3. * I, 2. + 1. * I}, - { 8. - 1. * I, 1. + 1. * I} - }; - { - multi::array c({1, 2}); - blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 20.+21.*I ); - } - { - multi::array c({1, 2}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 20.+21.*I ); - } - { - auto ar = +~a; - multi::array c({1, 2}); - blas::gemm(1., blas::H(ar), b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 28.+3.*I ); - } - { - auto ar = +~a; - multi::array c({1, 2}); - blas::gemm_n(1., begin(blas::H(ar)), size(blas::H(ar)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 28.+3.*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x2) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I}, - {9. - 1. * I}, - {1. + 1. * I}}; - multi::array const b = { - {11. - 2. * I, 5. + 2. * I}, - { 7. - 3. * I, 2. + 1. * I}, - { 8. - 1. * I, 1. + 1. * I} - }; - { - multi::array c({1, 2}); - blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 28.+3.*I ); - } - { - multi::array c({1, 2}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][1] == 28.+3.*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x2_3x2) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I, 5. + 2. * I}, - {9. - 1. * I, 9. + 1. * I}, - {1. + 1. * I, 2. + 2. * I} - }; - multi::array const b = { - {11. - 2. * I, 5. + 2. * I}, - { 7. - 3. * I, 2. + 1. * I}, - { 8. - 1. * I, 1. + 1. * I} - }; - { - multi::array c({2, 2}); - blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 125.-84.*I ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 125.-84.*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x2_3x1) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I, 5. + 2. * I}, - {9. - 1. * I, 9. + 1. * I}, - {1. + 1. * I, 2. + 2. * I} - }; - multi::array const b = { - {11. - 2. * I}, - {7. - 3. * I}, - {8. - 1. * I}}; - { - multi::array c({2, 1}); - blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 125.-84.*I ); - } - { - multi::array c({2, 1}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 125.-84.*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_3x1_3x1_bis) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I}, - {9. - 1. * I}, - {1. + 1. * I}}; - multi::array const b = { - {11. - 2. * I}, - {7. - 3. * I}, - {8. - 1. * I}}; - { - multi::array c({1, 1}); - blas::gemm(1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 80. - 53.*I ); - } - { - multi::array c({1, 1}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[0][0] == 80. - 53.*I ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_square_automatic) { - multi::array const a = { - {1., 3.}, - {9., 7.}, - }; - multi::array const b = { - {11., 12.}, - { 7., 19.}, - }; - { - multi::array c({2, 2}); - blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 148 and c[1][1] == 241 ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == 148 and c[1][1] == 241 ); - } - { - multi::array c({2, 2}); - blas::gemm(1., a, blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][1] == 196. ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::T(a), b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][1] == 169. ); - BOOST_REQUIRE( c[1][0] == 82. ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][1] == 154. ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square_automatic) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I, 3. - 3. * I}, - {9. + 1. * I, 7. + 4. * I}, - }; - multi::array const b = { - {11. + 1. * I, 12. + 1. * I}, - { 7. + 8. * I, 19. - 2. * I}, - }; - namespace blas = multi::blas; - { - multi::array c({2, 2}); - blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == complex(115, 104) ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == complex(115, 104) ); - } - { - multi::array c({2, 2}); - blas::gemm(1., a, blas::T(b), 0., c); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( c[1][0] == complex(178, 75) ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(a), size(a), begin(blas::T(b)), 0., begin(c)); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( c[1][0] == complex(178, 75) ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square_automatic_part2) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I, 3. - 3. * I}, - {9. + 1. * I, 7. + 4. * I}, - }; - multi::array const b = { - {11. + 1. * I, 12. + 1. * I}, - { 7. + 8. * I, 19. - 2. * I}, - }; - namespace blas = multi::blas; - { - multi::array c({2, 2}); - blas::gemm(1., blas::T(a), b, 0., c); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE(( c[1][1] == complex(180, 29) and c[1][0] == complex(53, 54) )); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(b), 0., begin(c)); // c=a⸆b, c⸆=b⸆a - BOOST_REQUIRE(( c[1][1] == complex(180, 29) and c[1][0] == complex(53, 54) )); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(( c[1][1] == complex(186, 65) and c[1][0] == complex(116, 25) )); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE(( c[1][1] == complex(186, 65) and c[1][0] == complex(116, 25) )); - } - { - multi::array c({2, 2}); - blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == complex(115, 104) ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][0] == complex(115, 104) ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::H(a), b, 0., c); // c=a†b, c†=b†a - BOOST_REQUIRE( c[1][0] == complex(111, 64) and c[1][1] == complex(158, -51) ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_square_automatic_part3) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I, 3. - 3. * I}, - {9. + 1. * I, 7. + 4. * I}, - }; - multi::array const b = { - {11. + 1. * I, 12. + 1. * I}, - { 7. + 8. * I, 19. - 2. * I}, - }; - namespace blas = multi::blas; - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(b), 0., begin(c)); // c=a†b, c†=b†a - BOOST_REQUIRE( c[1][0] == complex(111, 64) and c[1][1] == complex(158, -51) ); - } - { - multi::array c({2, 2}); - blas::gemm(1., a, blas::H(b), 0., c); // c=ab†, c†=ba† - BOOST_REQUIRE( c[1][0] == complex(188, 43) and c[1][1] == complex(196, 25) ); - auto c2 = +blas::gemm(1., a, blas::H(b)); - BOOST_REQUIRE( c2 == c ); - } - { - multi::array c({2, 2}); - blas::gemm(0.1, a, blas::H(b), 0., c); // c=ab†, c†=ba† - auto const c2 = +blas::gemm(0.1, a, blas::H(b)); - BOOST_REQUIRE( c2 == c ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::H(a), blas::H(b), 0., c); // c=a†b†, c†=ba - BOOST_REQUIRE( c[1][0] == complex(116, -25) and c[1][1] == complex(186, -65) ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::H(a)), size(blas::H(a)), begin(blas::H(b)), 0., begin(c)); // c=a†b†, c†=ba - BOOST_REQUIRE( c[1][0] == complex(116, -25) and c[1][1] == complex(186, -65) ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::T(a), blas::H(b), 0., c); // c=a⸆b†, c†=ba⸆† - BOOST_REQUIRE( c[1][0] == complex(118, 5) and c[1][1] == complex(122, 45) ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::H(b)), 0., begin(c)); // c=a⸆b†, c†=ba⸆† - BOOST_REQUIRE( c[1][0] == complex(118, 5) and c[1][1] == complex(122, 45) ); - } - { - multi::array c({2, 2}); - blas::gemm(1., blas::T(a), blas::T(b), 0., c); // c=a⸆b⸆, c⸆=ba - BOOST_REQUIRE( c[1][0] == complex(116, 25) and c[1][1] == complex(186, 65) ); - } - { - multi::array c({2, 2}); - blas::gemm_n(1., begin(blas::T(a)), size(blas::T(a)), begin(blas::T(b)), 0., begin(c)); // c=a⸆b⸆, c⸆=ba - BOOST_REQUIRE( c[1][0] == complex(116, 25) and c[1][1] == complex(186, 65) ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_nonsquare_automatic) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1. + 2. * I, 3. - 3. * I, 1. - 9. * I}, - {9. + 1. * I, 7. + 4. * I, 1. - 8. * I}, - }; - multi::array const b = { - {11. + 1. * I, 12. + 1. * I, 4. + 1. * I, 8. - 2. * I}, - { 7. + 8. * I, 19. - 2. * I, 2. + 1. * I, 7. + 1. * I}, - { 5. + 1. * I, 3. - 1. * I, 3. + 8. * I, 1. + 1. * I} - }; - { - multi::array c({2, 4}); - blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == complex(112, 12) ); - } - { - multi::array c({2, 4}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == complex(112, 12) ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_realcomplex_complex_nonsquare_automatic) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a = { - {1., 3., 1.}, - {9., 7., 1.}, - }; - multi::array const b = { - {11. + 1. * I, 12. + 1. * I, 4. + 1. * I, 8. - 2. * I}, - { 7. + 8. * I, 19. - 2. * I, 2. + 1. * I, 7. + 1. * I}, - { 5. + 1. * I, 3. - 1. * I, 3. + 8. * I, 1. + 1. * I} - }; - { - multi::array c = blas::gemm(1., a, b); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == complex(53, 24) ); - } - { - multi::array c({2, 4}); - c = blas::gemm(1., a, b); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == complex(53, 24) ); - } - { - multi::array c({2, 4}); - blas::gemm(1., a, b, 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == complex(53, 24) ); - } - { - multi::array c({2, 4}); - blas::gemm_n(1., begin(a), size(a), begin(b), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == complex(53, 24) ); - } - { - multi::array const a_real = { - {1., 3., 1.}, - {9., 7., 1.}, - }; - multi::array c({2, 4}); - blas::real_doubled(c) = blas::gemm(1., a_real, blas::real_doubled(b)); - - BOOST_REQUIRE( c[1][2] == complex(53, 24) ); - } -} - -BOOST_AUTO_TEST_CASE(submatrix_result_issue_97) { - using complex = std::complex; - constexpr complex I{0, 1}; - multi::array M = { - {2. + 3. * I, 2. + 1. * I, 1. + 2. * I}, - {4. + 2. * I, 2. + 4. * I, 3. + 1. * I}, - {7. + 1. * I, 1. + 5. * I, 0. + 3. * I} - }; - auto M2 = +M({0, 3}, {0, 1}); - BOOST_REQUIRE( M2 == M({0, 3}, {0, 1}) ); -} - -BOOST_AUTO_TEST_CASE(blas_context_gemm) { - using complex = std::complex; - static constexpr complex I{0, 1}; - auto rand = [d = std::normal_distribution<>{}, g = std::mt19937{}]() mutable { return d(g) + d(g) * I; }; // NOLINT(cert-msc32-c, cert-msc51-cpp): test purposes - - multi::array A({30, 40}); - multi::array B({40, 50}); - - std::generate(A.elements().begin(), A.elements().end(), rand); - std::generate(B.elements().begin(), B.elements().end(), rand); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare_hermitized_second_gemm_range) { - multi::array const a({2, 3}, 0.); - multi::array const b({4, 3}, 0.); - { - multi::array c({2, 4}); - c() = blas::gemm(0.1, a, blas::H(b)); - BOOST_REQUIRE_CLOSE(c[1][2], 0., 0.00001); - } - { - multi::array c = blas::gemm(0.1, a, blas::H(b)); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( c[1][2] == 0. ); - } - { - multi::array const a = { - {1, 3, 1}, - {9, 7, 1}, - }; - (void)a; - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_nonsquare_hermitized_second_gemm_range) { - using complex = std::complex; - multi::array const a({2, 3}, 0.); - multi::array const b({4, 3}, 0.); - { - multi::array c({2, 4}, 999.); - blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); - BOOST_REQUIRE( c[1][2] != 999. ); - } - { - multi::array c = blas::gemm(1., a, blas::H(b)); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE( c[1][2] == 0. ); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_real_nonsquare_hermitized_second) { - namespace blas = multi::blas; - multi::array const a = { - {1, 3, 1}, - {9, 7, 1}, - }; - multi::array const b = { - {11, 7, 5}, - {12, 19, 3}, - { 4, 2, 3}, - { 8, 7, 1} - }; - { - multi::array c({2, 4}); - blas::gemm(1., a, blas::H(b), 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 53. ); - } - { - multi::array c({2, 4}); - blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 53. ); - } - { - multi::array c({2, 4}); - blas::gemm(0.1, a, blas::H(b), 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); - } - { - multi::array c({2, 4}); - blas::gemm_n(0.1, begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); - } - { - multi::array c({2, 4}); - c() = blas::gemm(0.1, a, blas::H(b)); - } - { - multi::array c = blas::gemm(0.1, a, blas::H(b)); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE_CLOSE(c[1][2], 5.3, 0.00001); - } -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_gemm_complex_real_nonsquare_hermitized_second) { - namespace blas = multi::blas; - using complex = std::complex; - multi::array const a = { - {1., 3., 1.}, - {9., 7., 1.}, - }; - multi::array const b = { - {11., 7., 5.}, - {12., 19., 3.}, - { 4., 2., 3.}, - { 8., 7., 1.} - }; - { - multi::array c({2, 4}); - blas::gemm(1., a, blas::H(b), 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 53. ); - } - { - multi::array c({2, 4}); - blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE( c[1][2] == 53. ); - } - { - multi::array c({2, 4}); - blas::gemm(0.1, a, blas::H(b), 0., c); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE_CLOSE(real(c[1][2]), 5.3, 0.00001); - } - { - multi::array c({2, 4}); - blas::gemm_n(0.1, begin(a), size(a), begin(blas::H(b)), 0., begin(c)); // c=ab, c⸆=b⸆a⸆ - BOOST_REQUIRE_CLOSE(real(c[1][2]), 5.3, 0.00001); - } - { - multi::array c({2, 4}); - c() = blas::gemm(0.1, a, blas::H(b)); - } - { - multi::array c = blas::gemm(0.1, a, blas::H(b)); // c=ab⸆, c⸆=ba⸆ - BOOST_REQUIRE_CLOSE(real(c[1][2]), 5.3, 0.00001); - } -} - -BOOST_AUTO_TEST_CASE(blas_gemm_1xn_complex) { - using complex = std::complex; - multi::array const a({1, 100}, 1.); - multi::array const b({1, 100}, 1.); - - multi::array c({1, 1}, 999.); - blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); - BOOST_REQUIRE( c[0][0] == 100. ); -} - -BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_complex_inq_hydrogen_case) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a({3, 1}, 2. + 1. * I); - multi::array const b({1, 1}, 3. + 4. * I); - - multi::array c({3, 1}, 999.); - blas::gemm_n(1., begin(a), size(a), begin(blas::H(b)), 0., begin(c)); - BOOST_TEST_REQUIRE( c[0][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); - BOOST_TEST_REQUIRE( c[1][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); - BOOST_TEST_REQUIRE( c[2][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); -} - -BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_complex_inq_hydrogen_case_no_n_interface) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a({3, 1}, 2. + 1. * I); - multi::array const b({10, 1}, 3. + 4. * I); - - multi::array c({3, 10}, 999.); - blas::gemm(1., a, blas::H(b), 0., c); - BOOST_TEST_REQUIRE( c[0][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); - BOOST_TEST_REQUIRE( c[1][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); - BOOST_TEST_REQUIRE( c[0][1] == (2. + 1.*I)*std::conj(3. + 4.*I) ); -} - -BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_1x1_complex_inq_hydrogen_case_complex_value_hermitized) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a({1, 1}, 2. + 1. * I); - multi::array const b({1, 1}, 3. + 4. * I); - - multi::array c({1, 1}, 999.); - c = blas::gemm(1., a, blas::H(b)); - BOOST_REQUIRE( c[0][0] == (2. + 1.*I)*std::conj(3. + 4.*I) ); -} - -BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_1x1_complex_inq_hydrogen_case_complex_value) { - using complex = std::complex; - complex const I{0, 1}; - multi::array const a({1, 1}, 2. + 1. * I); - multi::array const b({1, 1}, 3. + 4. * I); - - multi::array c({1, 1}, 999.); - c = blas::gemm(1., a, b); - BOOST_REQUIRE( c[0][0] == (2. + 1.*I)*(3. + 4.*I) ); -} - -BOOST_AUTO_TEST_CASE(blas_gemm_nx1_times_1x1_1x1_complex_inq_hydrogen_case) { - using complex = std::complex; - multi::array const a({1, 1}, 2.); - multi::array const b({1, 1}, 3.); - - multi::array c({1, 1}, 999.); - c = blas::gemm(1., a, b); - BOOST_REQUIRE( c[0][0] == 6. ); -} - -BOOST_AUTO_TEST_CASE(blas_gemm_inq_case) { // https://gitlab.com/correaa/boost-multi/-/issues/97 - using complex = std::complex; - complex const I{0, 1}; - multi::array mat({10, 2}, 1.0 + 3. * I); - multi::array vec({10, 1}, -2.0 + 4. * I); - - mat({0, 10}, {1, 2}) = vec; - - namespace blas = multi::blas; - - { - auto olap1 = +blas::gemm(1., blas::H(mat), vec); - auto olap2 = +blas::gemm(1., blas::H(mat({0, 10}, {0, 1})), vec); - - BOOST_REQUIRE( blas::H(mat)[1].size() == (~vec)[0].size() ); - BOOST_REQUIRE( blas::dot(blas::H(mat)[0], (~vec)[0]) == olap1[0][0] ); - BOOST_REQUIRE( std::inner_product(blas::H(mat)[0].begin(), blas::H(mat)[0].end(), (~vec)[0].begin(), complex{0}) == olap1[0][0] ); - - multi::array mat2 = mat({0, 10}, {0, 1}); - auto olap3 = +blas::gemm(1., blas::H(mat2), vec); - - BOOST_REQUIRE(olap1[0][0] == olap2[0][0]); - BOOST_REQUIRE(olap3[0][0] == olap2[0][0]); - } - { - multi::array mat2 = mat({0, 3}, {0, 1}); - auto olap3 = +blas::gemm(1., blas::H(mat({0, 3}, {0, 1})), vec); - BOOST_REQUIRE( (+blas::gemm(1., blas::H(mat2), vec))[0][0] == (+blas::gemm(1., blas::H(mat({0, 3}, {0, 1})), vec))[0][0] ); - } -} -#endif - -BOOST_AUTO_TEST_CASE(blas_issue_109_part2) { - multi::array const A({3, 4}, 5.); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({2, 3}, 7.); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({2, 4}, 999.0); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm(1.0, ~A, ~B, 0.0, ~C); - - BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); - BOOST_TEST_REQUIRE( C[0][1] == 105.0 ); - BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); -} - -BOOST_AUTO_TEST_CASE(blas_issue_109) { - multi::array const A({3, 4}, 5.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({2, 3}, 7.0); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({4, 2}, 999.0); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm(1., ~A, ~B, 0., C); - - BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); - BOOST_TEST_REQUIRE( C[0][1] == 105.0 ); - BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); -} - -BOOST_AUTO_TEST_CASE(blas_issue_109_part2_complex) { - multi::array, 2> const A({3, 4}, {5.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - multi::array, 2> const B({2, 3}, {7.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - multi::array, 2> C({2, 4}, {999.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm({1.0, 0.0}, ~A, ~B, {0.0, 0.0}, ~C); - - BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); - BOOST_TEST_REQUIRE( C[0][1] == 105.0 ); - BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); -} - -BOOST_AUTO_TEST_CASE(blas_issue_109_complex) { - multi::array, 2> const A({3, 4}, {5.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - multi::array, 2> const B({2, 3}, {7.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - multi::array, 2> C({4, 2}, {999.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm({1.0, 0.0}, ~A, ~B, {0.0, 0.0}, C); - - BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); - BOOST_TEST_REQUIRE( C[0][1] == 105.0 ); - BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); -} -#endif - -BOOST_AUTO_TEST_CASE(blas_issue_109_complex_mx2) { - multi::array, 2> const A({3, 4}, {5.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - multi::array, 2> const B({2, 3}, {7.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - multi::array, 2> C({4, 2}, {999.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm({1.0, 0.0}, ~A, ~B, {0.0, 0.0}, C); - - BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); - BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); -} - -BOOST_AUTO_TEST_CASE(blas_issue_109_complex_mx1) { - multi::array, 2> const A({3, 4}, {5.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - multi::array, 2> const B({1, 3}, {7.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - - multi::array, 2> C({4, 1}, {999.0, 0.0}); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm(std::complex{1.0, 0.0}, ~A, ~B, std::complex{0.0, 0.0}, C); - - BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); - BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); -} - -BOOST_AUTO_TEST_CASE(blas_issue_109_double_mx1) { - multi::array const A({3, 4}, 5.0); // NOLINT(readability-identifier-length) BLAS naming - multi::array const B({1, 3}, 7.0); // NOLINT(readability-identifier-length) BLAS naming - - multi::array C({4, 1}, 999.0); // NOLINT(readability-identifier-length) BLAS naming - blas::gemm(1.0, ~A, ~B, 0.0, C); - - BOOST_TEST_REQUIRE( C[0][0] == 105.0 ); - BOOST_TEST_REQUIRE( C[1][0] == 105.0 ); -} diff --git a/include/multi/adaptors/blas/test/gemv.cpp b/include/multi/adaptors/blas/test/gemv.cpp deleted file mode 100644 index f7bf995c0..000000000 --- a/include/multi/adaptors/blas/test/gemv.cpp +++ /dev/null @@ -1,248 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS gemv" -#include - -#include - -#include "../../../adaptors/blas/gemv.hpp" -#include "../../../array.hpp" - -#include "../../../utility.hpp" - -#include "../../blas/axpy.hpp" -#include "../../blas/dot.hpp" -#include "../../blas/gemm.hpp" -#include "../../blas/nrm2.hpp" - -#include - -namespace multi = boost::multi; -namespace blas = multi::blas; - -using fp_types = boost::mpl::list; - -template -void MV(M const& a, VI const& x, VO&& y) { // NOLINT(readability-identifier-naming,readability-identifier-length) BLAS naming - std::transform( - begin(a), end(a), begin(y), - [&x](auto&& row){return std::inner_product(begin(row), end(row), begin(x), 0.);} - ); -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(multi_blas_gemv, T, fp_types) { - multi::array const a = { // NOLINT(readability-identifier-length) BLAS naming - { 9.0, 24.0, 30.0, 9.0}, - { 4.0, 10.0, 12.0, 7.0}, - {14.0, 16.0, 36.0, 1.0} - }; - multi::array const x = {1.1, 2.1, 3.1, 4.1}; // NOLINT(readability-identifier-length) BLAS naming - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming - blas::gemv_n(1.0, begin(a), size(a), begin(x), 0.0, begin(y)); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.0001 ); - BOOST_REQUIRE_CLOSE( y[2] , +blas::dot(a[2], x) , 0.0001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming - multi::array const aT = ~a; - blas::gemv_n(1.0, begin(~aT), size(~aT), begin(x), 0.0, begin(y)); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.0001 ); - BOOST_REQUIRE_CLOSE( y[2] , +blas::dot(a[2], x), 0.0001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming - auto mv = blas::gemv(1.0, a, x); - copy_n(mv.begin(), mv.size(), y.begin()); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - - multi::array w2(multi::extensions_t<1>{multi::iextension{size(a)}}); - MV(a, x, w2); - BOOST_REQUIRE_CLOSE( w2[0] , y[0], 0.00001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) BLAS naming - y = blas::gemv(1.0, a, x); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - } - { - multi::array y = blas::gemv(1.0, a, x); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}, 0.); // NOLINT(readability-identifier-length) BLAS naming - y += blas::gemv(1.0, a, x); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - } - { - multi::array y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) BLAS naming - blas::gemv(1.1, a, x, 1.0, y); // y = a*M*x + b*y - BOOST_REQUIRE_CLOSE( y[1] , 105.43 , 0.00001 ); - } -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(multi_blas_gemv_real, T, fp_types) { - namespace blas = multi::blas; - - using std::abs; - multi::array const a = { // NOLINT(readability-identifier-length) BLAS naming - { 9.0, 24.0, 30.0, 9.0}, - { 4.0, 10.0, 12.0, 7.0}, - {14.0, 16.0, 36.0, 1.0} - }; - multi::array const x = {1.1, 2.1, 3.1, 4.1}; // NOLINT(readability-identifier-length) BLAS naming - { - multi::array y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) BLAS naming - T const alpha = 1.1; - T const beta = 1.2; - blas::gemv(alpha, a, x, beta, y); // y = a*M*x + b*y - - multi::array const y3 = {214.02, 106.43, 188.37}; - BOOST_REQUIRE( abs(y[1] - y3[1]) < 2e-14 ); - } - { - auto Y = +blas::gemv(1., a, x); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE_CLOSE( Y[0] , +blas::dot(a[0], x) , 0.00001 ); - BOOST_REQUIRE_CLOSE( Y[1] , +blas::dot(a[1], x) , 0.00001 ); - BOOST_REQUIRE_CLOSE( Y[2] , +blas::dot(a[2], x) , 0.00001 ); - } - { - multi::array const x = {1., 2., 3.}; // NOLINT(readability-identifier-length) BLAS naming - multi::array const y = {4., 5., 6.}; // NOLINT(readability-identifier-length) BLAS naming - multi::array const dot = blas::gemv(1., multi::array({x}), y); - BOOST_REQUIRE( dot[0] == blas::dot(x, y) ); - } - { - using blas::operators::operator%; - using blas::operators::operator-; - using blas::operators::operator^; - BOOST_REQUIRE_SMALL( ((~+~a)%x - a%x)^2 , 1e-9 ); - } -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(multi_blas_gemv_real_complex, T, fp_types) { - namespace blas = multi::blas; - using complex = std::complex; - using std::abs; - multi::array const M = { // NOLINT(readability-identifier-length) BLAS naming - { { 9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0} }, - { { 4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0} }, - { {14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0} }, - }; - multi::array const X = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0}, {4.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - { - multi::array Y = { {4.0, 0.0}, {5.0, 0.0}, {6.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - T const alpha = 1.1; - T const beta = 1.2; - blas::gemv(alpha, M, X, beta, Y); // y = a*M*x + b*y - - multi::array const Y3 = { {214.02, 0.0}, {106.43, 0.0}, {188.37, 0.0} }; - - using blas::operators::operator-; - T const n2{blas::nrm2(Y - Y3)}; - BOOST_REQUIRE_SMALL( n2 , T{1e-4}); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_gemv_complex) { - namespace blas = multi::blas; - using complex = std::complex; std::complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - using std::abs; - multi::array const a = { // NOLINT(readability-identifier-length) BLAS naming - {2.0 + 3.0*I, 2.0 + 1.0*I, 1.0 + 2.0*I}, - {4.0 + 2.0*I, 2.0 + 4.0*I, 3.0 + 1.0*I}, - {7.0 + 1.0*I, 1.0 + 5.0*I, 0.0 + 3.0*I} - }; - multi::array const x = {1.0 + 2.0*I, 2.0 + 1.0*I, 9.0 + 2.0*I}; // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE(( +blas::gemv(1., a, x) == multi::array{4.0 + 31.*I, 25.0 + 35.0*I, -4.0 + 53.0*I} )); - - auto aT = +~a; - BOOST_REQUIRE(( +blas::gemv(1., ~aT, x) == multi::array{4.0 + 31.0*I, 25.0 + 35.0*I, -4.0 + 53.0*I} )); - - BOOST_REQUIRE( +blas::gemv(1., ~a, x) == (multi::array{63.0 + 38.0*I, -1.0 + 62.0*I, -4.0 + 36.0*I}) ); - BOOST_REQUIRE( +blas::gemv(1., ~a, x) == + blas::gemv(1.0, aT, x) ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_gemv_temporary) { - using complex = std::complex; - - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { {1.0, 0.0}, {0.0, 0.0}, {0.0, 0.0} }, - { {0.0, 0.0}, {1.0, 0.0}, {0.0, 0.0} }, - { {0.0, 0.0}, {0.0, 0.0}, {1.0, 0.0} } - }; - - auto const B = [](auto array) { // NOLINT(readability-identifier-length) BLAS naming - auto rand = [gauss = std::normal_distribution<>{}, gen = std::mt19937{1}]() mutable {return complex{gauss(gen), gauss(gen)};}; // NOLINT(cert-msc32-c,cert-msc51-cpp) test purposes - std::generate(array.elements().begin(), array.elements().end(), rand); - return array; - }(multi::array({3, 3})); - - using blas::operators::operator*; - using blas::operators::operator-; - using blas::operators::operator^; - BOOST_REQUIRE( (((+(A*B))[0] - B[0])^2) == 0.0 ); - BOOST_REQUIRE( (((+(A*B))[1] - B[1])^2) == 0.0 ); - BOOST_REQUIRE( (((+(A*B))[2] - B[2])^2) == 0.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_gemv_context) { - multi::array const a = { // NOLINT(readability-identifier-length) BLAS naming - { 9.0, 24.0, 30.0, 9.0}, - { 4.0, 10.0, 12.0, 7.0}, - {14.0, 16.0, 36.0, 1.0} - }; - multi::array const x = {1.1, 2.1, 3.1, 4.1}; // NOLINT(readability-identifier-length) conventional name in BLAS - - blas::context ctxt; - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS - blas::gemv_n(&ctxt, 1.0, begin(a), size(a), begin(x), 0.0, begin(y)); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.0001 ); - BOOST_REQUIRE_CLOSE( y[2] , +blas::dot(a[2], x) , 0.0001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS - multi::array const aT = ~a; - blas::gemv_n(&ctxt, 1.0, begin(~aT), size(~aT), begin(x), 0.0, begin(y)); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - BOOST_REQUIRE_CLOSE( y[2] , +blas::dot(a[2], x) , 0.00001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS - auto&& mv = blas::gemv(&ctxt, 1.0, a, x); - copy_n(mv.begin(), mv.size(), y.begin()); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS - y = blas::gemv(&ctxt, 1.0, a, x); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS - y = blas::gemv(1.0, a, x); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}); // NOLINT(readability-identifier-length) conventional name in BLAS - y() = blas::gemv(1.0, a, x); - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - } - { - multi::array y = blas::gemv(&ctxt, 1.0, a, x); // NOLINT(readability-identifier-length) conventional name in BLAS - BOOST_REQUIRE_CLOSE( y[1] , 91.3 , 0.00001 ); - } - { - multi::array y(multi::extensions_t<1>{multi::iextension{size(a)}}, 0.0); // NOLINT(readability-identifier-length) conventional name in BLAS - y += blas::gemv(&ctxt, 1.0, a, x); - BOOST_REQUIRE_CLOSE( y[1] , 91.3, 0.00001 ); - } - { - multi::array y = {4.0, 5.0, 6.0}; // NOLINT(readability-identifier-length) conventional name in BLAS - y += blas::gemv(&ctxt, 1.1, a, x); - BOOST_REQUIRE_CLOSE( y[1] , 105.43, 0.00001 ); - } -} diff --git a/include/multi/adaptors/blas/test/herk.cpp b/include/multi/adaptors/blas/test/herk.cpp deleted file mode 100644 index 70dd8d348..000000000 --- a/include/multi/adaptors/blas/test/herk.cpp +++ /dev/null @@ -1,253 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS herk" -#include - -#include -#include -#include - -#include - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_blas_herk) { - namespace blas = multi::blas; - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS - multi::array const a = { - {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, - {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, - }; - { - multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional name in BLAS - blas::herk(a, c); - BOOST_REQUIRE( c[1][0] == complex(50.0, -49.0) ); - BOOST_REQUIRE( c[0][1] == complex(50.0, +49.0) ); - - multi::array const c_copy = blas::herk(1., a); - BOOST_REQUIRE( c == c_copy ); - - BOOST_REQUIRE( +blas::gemm(1.0, a, blas::H(a)) == blas::herk(a) ); - } -} - -BOOST_AUTO_TEST_CASE(inq_case) { - namespace blas = multi::blas; - // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS - multi::array const a = { - {0.0, 1.0, 2.0}, - {3.0, 4.0, 5.0}, - {6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0}, - }; - BOOST_REQUIRE( (+blas::gemm(1.0, a, blas::T(a)))[1][2] == 86.0 ); - { - multi::array c({4, 4}); // NOLINT(readability-identifier-length) conventional name in BLAS - blas::herk(1.0, a, c); - BOOST_REQUIRE( c[1][2] == (+blas::gemm(1.0, a, blas::T(a)))[1][2] ); - // BOOST_REQUIRE( c[2][1] == (+blas::gemm(1., a, blas::T(a)))[2][1] ); - } - { - multi::array const c = blas::herk(1.0, a); // NOLINT(readability-identifier-length) conventional name in BLAS - BOOST_REQUIRE( c == +blas::gemm(1., a, blas::T(a)) ); - BOOST_REQUIRE( blas::herk(a) == +blas::gemm(1.0, a, blas::T(a)) ); - BOOST_REQUIRE( blas::herk(2.0, a) == +blas::gemm(2.0, a, blas::T(a)) ); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk_real) { - namespace blas = multi::blas; - // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS - multi::array const a = { - {1.0, 3.0, 4.0}, - {9.0, 7.0, 1.0}, - }; - { - multi::array c({2, 2}, 9999.0); // NOLINT(readability-identifier-length) BLAS naming - blas::herk(1., a, c); - BOOST_REQUIRE( c[0][1] == 34.0 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_case) { - namespace blas = multi::blas; - // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS - multi::array const a = { - {1.0, 2.0, 3.0}, - }; - multi::array b = blas::herk(a); // NOLINT(readability-identifier-length) BLAS naming - - BOOST_REQUIRE( size(b) == 1 ); - BOOST_REQUIRE( b[0][0] == 1.0*1.0 + 2.0*2.0 + 3.0*3.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_case_scale) { - namespace blas = multi::blas; - // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS - multi::array const a = { - {1.0, 2.0, 3.0}, - }; // NOLINT(readability-identifier-length) BLAS naming - multi::array b = blas::herk(0.1, a); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( size(b) == 1 ); - BOOST_TEST( b[0][0] == (1.0*1.0 + 2.0*2.0 + 3.0*3.0)*0.1 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_real_case) { - namespace blas = multi::blas; - - using complex = std::complex; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const a = { - {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}}, - }; - multi::array b = blas::herk(1.0, a); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( size(b) == 1 ); - BOOST_REQUIRE( b[0][0] == 1.0*1.0 + 2.0*2.0 + 3.0*3.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_real_case_scale, *boost::unit_test::tolerance(0.00001)) { - namespace blas = multi::blas; - - using complex = std::complex; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const a = { - {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}}, - }; - multi::array b = blas::herk(0.1, a); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( size(b) == 1 ); - BOOST_TEST( real( b[0][0]/0.1 ) == 1.0*1.0 + 2.0*2.0 + 3.0*3.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_case) { - namespace blas = multi::blas; - - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS - multi::array const a = { - {1.0 + 2.0 * I, 2.0 + 3.0 * I, 3.0 + 4.0 * I}, - }; - multi::array b = blas::herk(a); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( size(b) == 1 ); - BOOST_REQUIRE( b[0][0] == std::norm(1.0 + 2.0*I) + std::norm(2.0 + 3.0*I) + std::norm(3.0 + 4.0*I) ); - - BOOST_TEST( std::sqrt(real(blas::herk(a)[0][0])) == blas::nrm2(a[0]) ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_case_hermitized_out_param) { - namespace blas = multi::blas; - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array const a = {{1.0 + 2.0 * I}, {2.0 + 3.0 * I}, {3.0 + 4.0 * I}}; // NOLINT(readability-identifier-length) BLAS naming - multi::array b({1, 1}); // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( size(b) == 1 ); - - blas::herk(blas::filling::upper, 1.0, blas::H(a), 0.0, b); - - BOOST_REQUIRE( b[0][0] == std::norm(1.0 + 2.0*I) + std::norm(2.0 + 3.0*I) + std::norm(3.0 + 4.0*I) ); - - // BOOST_TEST( std::sqrt(real(b[0][0])) == blas::nrm2(blas::T(a)[0])() ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_case_hermitized) { - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) conventional name in BLAS - - // NOLINTNEXTLINE(readability-identifier-length) conventional name in BLAS - multi::array const a = { - {1.0 + 2.0 * I}, - {2.0 + 3.0 * I}, - {3.0 + 4.0 * I}, - }; - - namespace blas = multi::blas; - - multi::array b = blas::herk(blas::H(a)); // NOLINT(readability-identifier-length) BLAS naming - - BOOST_REQUIRE( size(b) == 1 ); - BOOST_REQUIRE( b[0][0] == std::norm(1.0 + 2.0*I) + std::norm(2.0 + 3.0*I) + std::norm(3.0 + 4.0*I) ); - - BOOST_TEST( std::sqrt(real(blas::herk(blas::H(a))[0][0])) == blas::nrm2(rotated(a)[0]) ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk1x1_complex_case_hermitized_auto) { - namespace blas = multi::blas; - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array const arr = { - {1.0 + 2.0 * I}, - {2.0 + 3.0 * I}, - {3.0 + 4.0 * I}, - }; - auto arr2 = blas::herk(1.0, blas::hermitized(arr)); - static_assert(std::is_same>{}); - BOOST_REQUIRE( size(arr2) == 1 ); - BOOST_REQUIRE( arr2[0][0] == std::norm(1.0 + 2.0*I) + std::norm(2.0 + 3.0*I) + std::norm(3.0 + 4.0*I) ); - - BOOST_TEST( std::sqrt(real(blas::herk(blas::H(arr))[0][0])) == blas::nrm2(rotated(arr)[0]) ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_herk_complex_identity) { - namespace blas = multi::blas; - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array const arr = { - {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, - {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, - }; - - { - multi::array arr2({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional one-letter operation BLASs - blas::herk(blas::filling::lower, 1.0, arr, 0.0, arr2); // c†=c=aa†=(aa†)†, `c` in lower triangular - BOOST_REQUIRE(( arr2[1][0] == complex{50.0, -49.0} )); - BOOST_REQUIRE( arr2[0][1] == 9999.0 ); - } - #if 1 - { - multi::array const c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional one-letter operation BLASs - static_assert(blas::is_conjugated{}); - - blas::herk(blas::filling::lower, 1.0, arr, 0.0, blas::H(c)); // c†=c=aa†=(aa†)†, `c` in upper triangular - - BOOST_REQUIRE(( blas::H(c)[1][0] == complex{50.0, -49.0} )); - BOOST_REQUIRE( blas::H(c)[0][1] == 9999.0 ); - } - { - multi::array c({3, 3}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLASs - herk(blas::filling::lower, 1., blas::T(arr), 0., blas::T(c)); // c†=c=aT(aT)† not supported - BOOST_REQUIRE(( transposed(c)[1][0] == complex{52.0, -90.0} )); - BOOST_REQUIRE( transposed(c)[0][1] == 9999.0 ); - } - { - multi::array c({3, 3}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLASs - blas::herk(blas::filling::lower, 1.0, blas::T(arr), 0.0, blas::H(blas::T(c))); // c†=c=aT(aT)† not supported - BOOST_REQUIRE(( blas::H(blas::T(c))[1][0] == complex{52.0, -90.0} )); - BOOST_REQUIRE( blas::H(blas::T(c))[0][1] == 9999.0 ); - } - { - multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLAS - blas::herk(blas::filling::upper, 1.0, arr, 0.0, c); // c†=c=aa†=(aa†)†, `c` in upper triangular - BOOST_REQUIRE(( c[0][1] == complex{50.0, +49.0} )); - BOOST_REQUIRE( c[1][0] == 9999.0 ); - } - { - multi::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLAS - blas::herk(1., arr, c); // c†=c=aa†=(aa†)† - BOOST_REQUIRE(( c[0][1] == complex{50.0, +49.0} )); - BOOST_REQUIRE(( c[1][0] == complex{50.0, -49.0} )); - } - { - multi::array c({3, 3}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) : conventional one-letter operation BLAS - blas::herk(blas::filling::lower, 1.0, blas::H(arr), 0.0, c); // c†=c=aa†=(aa†)†, `c` in lower triangular - BOOST_REQUIRE(( c[1][0] == complex{52.0, 90.0} )); - BOOST_REQUIRE( c[0][1] == 9999.0 ); - } - #endif -} diff --git a/include/multi/adaptors/blas/test/iamax.cpp b/include/multi/adaptors/blas/test/iamax.cpp deleted file mode 100644 index 3b998d435..000000000 --- a/include/multi/adaptors/blas/test/iamax.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXX $0 -o $0x `pkg-config --libs blas` -lcudart -lcublas -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2019-2020 - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS/cuBLAS iamax" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../blas/iamax.hpp" - -#include "../../../array.hpp" -#include "../../../adaptors/cuda.hpp" -#include "../../../adaptors/blas/cuda.hpp" - -#include - -using std::cout; -namespace multi = boost::multi; -namespace blas = multi::blas; - -using complex = std::complex; constexpr complex I{0, 1}; - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_iamax){ - multi::array const A = { - {1. + 2.*I, 2., 3., 4.}, - {5., 6. + 3.*I, 7., 8.}, - {9., 10., 11.+ 4.*I, 12.} - }; - using blas::iamax; - auto chess = [](auto const& a, auto const& b){ - using std::abs; - return abs(real(a))+abs(imag(a)) < abs(real(b))+abs(imag(b)); - }; - BOOST_REQUIRE(iamax(A[1])==std::max_element(begin(A[1]), end(A[1]), chess)-begin(A[1])); - BOOST_REQUIRE(A[1][iamax(A[1])]==*std::max_element(begin(A[1]), end(A[1]), chess)); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_iamax_cuda){ - multi::cuda::array const A = { - {1. + 2.*I, 2. , 3. , 4.}, - {5. , 6. + 3.*I, 7. , 8.}, - {9. , 10. , 11.+ 4.*I, 12.} - }; - using blas::iamax; - BOOST_REQUIRE(iamax(A[1])==1); -} - - diff --git a/include/multi/adaptors/blas/test/nrm2.cpp b/include/multi/adaptors/blas/test/nrm2.cpp deleted file mode 100644 index 9c34241cf..000000000 --- a/include/multi/adaptors/blas/test/nrm2.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS nrm2" -#include - -#include -#include - -#include - -#include - -namespace multi = boost::multi; - -using complex = multi::complex; -constexpr complex I{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit - -BOOST_AUTO_TEST_CASE(multi_blas_nrm2){ - namespace blas = multi::blas; - - multi::array const A = { // NOLINT(readability-identifier-length) blas conventional name - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0} - }; - BOOST_REQUIRE( blas::nrm2(A[1]) == std::sqrt(blas::dot(A[1], A[1])) ); - - { - multi::array const x = {1.0 + 1.0*I, 3.0 + 2.0*I, 3.0 + 4.0*I}; // NOLINT(readability-identifier-length) blas conventional name - BOOST_REQUIRE( blas::dot(x, x) == (1.0 + 1.0*I)*(1.0 + 1.0*I) + (3.0 + 2.0*I)*(3.0 + 2.0*I) + (3.0 + 4.0*I)*(3.0 + 4.0*I) ); - using std::sqrt; - BOOST_REQUIRE( blas::nrm2(x) == sqrt(norm(1.0 + 1.0*I) + norm(3.0 + 2.0*I) + norm(3.0 + 4.0*I)) ); - } -} diff --git a/include/multi/adaptors/blas/test/numeric.cpp b/include/multi/adaptors/blas/test/numeric.cpp deleted file mode 100644 index d575b40cd..000000000 --- a/include/multi/adaptors/blas/test/numeric.cpp +++ /dev/null @@ -1,171 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS numeric" -#include - -#include -#include - -#include - -#include - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_numeric_imag) { - using complex = std::complex; auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - namespace blas = multi::blas; - multi::array const array = { 1.0 + 2.0*I, 3.0 + 5.0*I, 9.0 + 2.0*I }; - BOOST_REQUIRE( blas::imag(array)[2] == 2.0 ); - BOOST_REQUIRE( blas::real(array)[2] == 9.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_numeric_real_conjugated) { - using complex = std::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array array = { - {1.0 - 3.0*I, 6.0 + 2.0*I}, - {8.0 + 2.0*I, 2.0 + 4.0*I}, - {2.0 - 1.0*I, 1.0 + 1.0*I} - }; - BOOST_REQUIRE( array[0][0] == 1.0 - 3.0*I ); - - multi::array const carray = { - {1.0 - 3.0*I, 6.0 + 2.0*I}, - {8.0 + 2.0*I, 2.0 + 4.0*I}, - {2.0 - 1.0*I, 1.0 + 1.0*I} - }; - BOOST_REQUIRE( carray[0][0] == 1.0 - 3.0*I ); - - namespace blas = multi::blas; - auto conjr = blas::make_conjugater(array.data_elements()); - - decltype(blas::make_conjugater(carray.data_elements())) ppp; // = BdataC; - ppp = conjr; - - BOOST_REQUIRE( *ppp == 1.0 + 3.0*I ); - -// static_assert( multi::blas::is_complex_array, 2>>{}, "!"); - static_assert( blas::is_complex_array{} ); - static_assert(not blas::is_conjugated{} ); - - auto&& conjd_array = blas::conj(array); - static_assert( blas::is_conjugated{} ); - - BOOST_REQUIRE( conjd_array[0][0] == 1.0 + 3.0*I ); - BOOST_REQUIRE( imag(*base(conjd_array)) == +3.0 ); - -// BOOST_TEST_REQUIRE( base(Bconj)->imag() == +3 ); - BOOST_REQUIRE( conjd_array[0][1] == rotated(conjd_array)[1][0] ); - BOOST_REQUIRE( rotated(conjd_array)[1][0] == conjd_array[0][1] ); - -// BOOST_REQUIRE( base(Bconj) == -3.0*I ); - static_assert( blas::is_complex_array{} ); - - BOOST_REQUIRE( blas::conj(conjd_array) == array ); - - BOOST_REQUIRE( blas::conj(array)[1][0] == std::conj(array[1][0]) ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_numeric_decay) { - using complex = std::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array arr = { - { 1.0 - 3.0*I, 6.0 + 2.0*I, 9.0 + 3.0*I}, - { 8.0 + 2.0*I, 2.0 + 4.0*I, 9.0 + 3.0*I}, - { 2.0 - 1.0*I, 1.0 + 1.0*I, 9.0 + 3.0*I}, - { 9.0 + 3.0*I, 9.0 + 3.0*I, 9.0 + 3.0*I} - }; - - namespace blas = multi::blas; - multi::array conj_arr{blas::conj(arr)}; - multi::array const conj_arr2 = blas::conj(arr); - - BOOST_REQUIRE( conj_arr[2][1] == std::conj(arr[2][1]) ); - BOOST_REQUIRE( blas::conj(arr)[2][1] == std::conj(arr[2][1]) ); - - BOOST_REQUIRE( blas::transposed(arr)[1][2] == arr[2][1] ); - BOOST_REQUIRE( blas::transposed(arr) == ~arr ); - - BOOST_REQUIRE( blas::conj(arr)[1][2] == blas::hermitized(arr)[2][1] ); - BOOST_REQUIRE( blas::conj(blas::transposed(arr)) == blas::hermitized(arr) ); - - BOOST_REQUIRE( blas::hermitized(arr)[2][1] == blas::conj(arr)[1][2] ); - BOOST_REQUIRE( blas::hermitized(arr) == blas::conj(blas::transposed(arr)) ); - - BOOST_REQUIRE( blas::real(arr)[2][1] == std::real(arr[2][1]) ); - BOOST_REQUIRE( blas::imag(arr)[2][1] == std::imag(arr[2][1]) ); - - multi::array const B_real_doubled = { - { 1.0, -3.0, 6.0, 2.0, 9.0, 3.0}, - { 8.0, 2.0, 2.0, 4.0, 9.0, 3.0}, - { 2.0, -1.0, 1.0, 1.0, 9.0, 3.0}, - { 9.0, 3.0, 9.0, 3.0, 9.0, 3.0} - }; - BOOST_REQUIRE( sizes(blas::real_doubled(arr)) == sizes(B_real_doubled) ); - BOOST_REQUIRE( blas::real_doubled(arr) == B_real_doubled ); -} - -#if defined(CUDA_FOUND) and CUDA_FOUND -#include - -BOOST_AUTO_TEST_CASE(multi_blas_numeric_decay_thrust) { - using complex = thrust::complex; complex const I{0.0, 1.0}; - - multi::array B = { - {1.0 - 3.0*I, 6.0 + 2.0*I}, - {8.0 + 2.0*I, 2.0 + 4.0*I}, - {2.0 - 1.0*I, 1.0 + 1.0*I}, - }; - - namespace blas = multi::blas; - multi::array conjB = blas::conj(B); - BOOST_REQUIRE( conjB[1][2] == conj(B[1][2]) ); -} -#endif - -BOOST_AUTO_TEST_CASE(multi_blas_numeric_real_imag_part) { - using complex = std::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array arr = { - {1.0, 3.0, 4.0}, - {9.0, 7.0, 1.0} - }; - multi::array complex_arr = arr; - BOOST_REQUIRE( complex_arr[1][1] == arr[1][1] ); - - multi::array arr2 = { - {1.0 - 3.0*I, 6.0 + 2.0*I}, - {8.0 + 2.0*I, 2.0 + 4.0*I}, - {2.0 - 1.0*I, 1.0 + 1.0*I} - }; - - multi::array const arr2_real = { - {1.0, 6.0}, - {8.0, 2.0}, - {2.0, 1.0}, - }; - multi::array const arr2_imag = { - {-3.0, +2.0}, - {+2.0, +4.0}, - {-1.0, +1.0}, - }; - - using multi::blas::real; - using multi::blas::imag; - - BOOST_REQUIRE( arr2_real == real(arr2) ); - BOOST_REQUIRE( real(arr2) == arr2_real ); - BOOST_REQUIRE( imag(arr2) == arr2_imag ); - - BOOST_REQUIRE( arr2[1][0] == 8.0 + 2.0*I ); - BOOST_REQUIRE( arr2[1][0].imag() == 2.0 ); - - namespace blas = multi::blas; - BOOST_REQUIRE( blas::hermitized(arr2)[1][2] == std::conj( arr2[2][1] ) ); - - blas::hermitized(arr2)[1][2] = 20.0 + 30.0*I; - BOOST_REQUIRE( arr2[2][1] == 20.0 - 30.0*I ); -} diff --git a/include/multi/adaptors/blas/test/operations.cpp b/include/multi/adaptors/blas/test/operations.cpp deleted file mode 100644 index 13fe75c9c..000000000 --- a/include/multi/adaptors/blas/test/operations.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXX $0 -o $0x `pkg-config --libs blas` -lcudart -lcublas -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2019-2020 - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS operations and cuda" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../blas/dot.hpp" - -#include "../../../array.hpp" -#include "../../blas/cuda.hpp" - -#include "../../../adaptors/cuda.hpp" -#include "../../../complex.hpp" - -#include -#include -#include - -using std::cout; -namespace multi = boost::multi; -namespace blas = multi::blas; - -using complex = std::complex; constexpr complex I{0, 1}; - -BOOST_AUTO_TEST_CASE(blas_conjugated_cpu){ - multi::array const a = {5. + 2.*I, 6. + 6.*I, 7. + 2.*I, 8. - 3.*I}; - BOOST_REQUIRE( blas::C(a)[1] == conj(a[1]) ); - - namespace cuda = multi::cuda; - - cuda::array const agpu = {5. + 2.*I, 6. + 6.*I, 7. + 2.*I, 8. - 3.*I}; - BOOST_REQUIRE( blas::C(agpu)[1] == conj(agpu[1]) ); -} - -BOOST_AUTO_TEST_CASE(blas_conjugated_gpu){ -#if 0 - cuda::array const acu = {1. + I, 2. + 3.*I, 3. + 2.*I, 4. - 9.*I}; - cuda::array const bcu = {5. + 2.*I, 6. + 6.*I, 7. + 2.*I, 8. - 3.*I}; - - { - cuda::array ccu; - blas::dot(acu, bcu, ccu); - BOOST_REQUIRE( ccu() == 19. - 27.*I ); - } - BOOST_REQUIRE( blas::C(bcu)[1] == 2. - 3.*I ); -#endif -} - diff --git a/include/multi/adaptors/blas/test/scal.cpp b/include/multi/adaptors/blas/test/scal.cpp deleted file mode 100644 index a6052d453..000000000 --- a/include/multi/adaptors/blas/test/scal.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS scal" -#include - -#include - -#include - -namespace multi = boost::multi; -namespace blas = multi::blas; - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_n) { - multi::array arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0}, - }; - BOOST_REQUIRE( (arr[0][2] == 3.0) and (arr[2][2] == 11.0) ); - - blas::scal_n(2.0, arr[2].begin(), arr[2].size()); - BOOST_REQUIRE( arr[0][2] == 3. and arr[2][2] == 11.0*2.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_it) { - multi::array arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0}, - }; - BOOST_REQUIRE( arr[0][2] == 3.0 ); - BOOST_REQUIRE( arr[2][2] == 11.0 ); - - blas::scal(2.0, arr[2].begin(), arr[2].end()); - BOOST_REQUIRE( arr[0][2] == 3.0 ); - BOOST_REQUIRE(arr[2][2] == 11.0*2.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_real) { - multi::array arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0}, - }; - BOOST_REQUIRE( arr[0][2] == 3.0 ); - BOOST_REQUIRE( arr[2][2] == 11.0 ); - - BOOST_REQUIRE( blas::scal(1.0, arr[2]) == arr[2] ); - BOOST_REQUIRE( &blas::scal(1.0, arr[2]) == &arr[2] ); - BOOST_REQUIRE( +blas::scal(1.0, arr[2]) == arr[2] ); - - blas::scal(2.0, arr[2]); - BOOST_REQUIRE( arr[0][2] == 3.0 and arr[2][2] == 11.0*2.0 ); - - BOOST_REQUIRE( &blas::scal(1.0, arr[2]) == &arr[2] ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_real_2D) { - multi::array arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 10.0, 11.0, 12.0}, - }; - BOOST_REQUIRE( arr[0][2] == 3.0 ); - BOOST_REQUIRE( arr[2][2] == 11.0 ); - - blas::scal(2.0, arr.elements()); - - BOOST_REQUIRE( arr[0][2] == 6.0 ); - BOOST_REQUIRE( arr[2][2] == 22.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_test_scal_complex_2D) { - auto const I = std::complex(0.0, 1.0); // NOLINT(readability-identifier-length) blas conventional name - multi::array, 2> arr = { - {1.0 + 0.0*I, 2.0 + 0.0*I, 3.0 + 0.0*I, 4.0 + 0.0*I}, - {5.0 + 0.0*I, 6.0 + 0.0*I, 7.0 + 0.0*I, 8.0 + 0.0*I}, - {9.0 + 0.0*I, 10.0 + 0.0*I, 11.0 + 0.0*I, 12.0 + 0.0*I}, - }; - BOOST_REQUIRE( arr[0][2] == 3.0 ); - BOOST_REQUIRE( arr[2][2] == 11.0 ); - - blas::scal(2.0, arr.elements()); - - BOOST_REQUIRE( arr[0][2] == 6.0 ); - BOOST_REQUIRE( arr[2][2] == 22.0 ); -} diff --git a/include/multi/adaptors/blas/test/traits.cpp b/include/multi/adaptors/blas/test/traits.cpp deleted file mode 100644 index c2d53a031..000000000 --- a/include/multi/adaptors/blas/test/traits.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS traits" -#include - -#include "../../blas/traits.hpp" - -#include - -namespace multi = boost::multi; -namespace blas = multi::blas; - -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_traits) { - static_assert( blas::is_d{} ); - static_assert( blas::is_s{} ); - - static_assert( blas::is_c>{} ); - static_assert( blas::is_z>{} ); -} - -#if 0 -#if CUDA_FOUND // TODO(correaa) move test to thrust adaptor -#include -BOOST_AUTO_TEST_CASE(multi_adaptors_blas_traits_thrust) { - static_assert( blas::is_c>{} ); - static_assert( blas::is_z>{} ); -} -#endif -#endif diff --git a/include/multi/adaptors/blas/test/trsm.cpp b/include/multi/adaptors/blas/test/trsm.cpp deleted file mode 100644 index 0f91e3eba..000000000 --- a/include/multi/adaptors/blas/test/trsm.cpp +++ /dev/null @@ -1,366 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS trsm" -#include - -#include -#include - -#include - -#include - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_0x0) { - namespace blas = multi::blas; - multi::array const A; // NOLINT(readability-identifier-length) BLAS naming - - { - multi::array B; // NOLINT(readability-identifier-length) BLAS naming - // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::general, 1.0, A, B); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_1x1) { - namespace blas = multi::blas; - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - {10.0, }, - }; - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {3.0, }, - }; - auto const B_cpy = B; - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::general, 1.0, A, B); - // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) - BOOST_REQUIRE_CLOSE( B[0][0] , 3.0/10.0 , 0.00001 ); - BOOST_REQUIRE_CLOSE( (+blas::gemm(1.0, A, B))[0][0] , B_cpy[0][0] , 0.00001 ); - } - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {3.0, }, - }; - auto const B_cpy = B; - // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::general, 2.0, A, B); - BOOST_REQUIRE_CLOSE( B[0][0] , 2.0*3.0/10.0 , 0.00001 ); - BOOST_REQUIRE_CLOSE( (+blas::gemm(1.0, A, B))[0][0] , 2.*B_cpy[0][0] , 0.00001 ); - } - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {3.0, 4.0, 5.0}, - }; - auto const B_cpy = B; - // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::general, 1.0, A, B); - BOOST_REQUIRE_CLOSE( B[0][0] , 3./10. , 0.00001 ); - BOOST_REQUIRE_CLOSE( B[0][1] , 4./10. , 0.00001 ); - BOOST_REQUIRE_CLOSE( B[0][2] , 5./10. , 0.00001 ); - BOOST_REQUIRE_CLOSE( (+blas::gemm(1., A, B))[0][1] , B_cpy[0][1] , 0.00001 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_real_square) { - namespace blas = multi::blas; - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - {1.0, 3.0, 4.0}, - {NAN, 7.0, 1.0}, - {NAN, NAN, 8.0} - }; - auto const A_cpy = triangular(blas::filling::upper, A); - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0, 3.0, 4.0}, - {2.0, 7.0, 1.0}, - {3.0, 4.0, 2.0} - }; - auto const B_cpy = B; - blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) - BOOST_REQUIRE_CLOSE( B[1][2] , 0.107143 , 0.001 ); - BOOST_REQUIRE( (+blas::gemm(1., A_cpy, B))[1][2] == B_cpy[1][2] ); - } - { - auto const AT =+ ~A; - auto const AT_cpy = triangular(blas::filling::lower, AT); - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0, 3.0, 4.0}, - {2.0, 7.0, 1.0}, - {3.0, 4.0, 2.0} - }; - auto const B_cpy = B; - blas::trsm(blas::side::left, blas::filling::upper, 1., blas::T(AT), B); - BOOST_REQUIRE_CLOSE( B[1][2] , 0.107143 , 0.001 ); - BOOST_REQUIRE( (+blas::gemm(1., blas::T(AT_cpy), B))[1][2] == B_cpy[1][2] ); - } - { - auto const AT =+ ~A; - auto const AT_cpy = triangular(blas::filling::lower, AT); - multi::array const B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0, 3.0, 4.0}, - {2.0, 7.0, 1.0}, - {3.0, 4.0, 2.0} - }; - auto BT =+ ~B; - blas::trsm(blas::side::left, blas::filling::upper, 1., blas::T(AT), blas::T(BT)); - BOOST_REQUIRE_CLOSE( blas::T(BT)[1][2] , 0.107143 , 0.001 ); - BOOST_REQUIRE( (+blas::gemm(1., blas::T(AT_cpy), blas::T(BT)))[1][2] == B[1][2] ); - } - { - multi::array const B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0, 3.0, 4.0}, - {2.0, 7.0, 1.0}, - {3.0, 4.0, 2.0} - }; - auto BT =+ ~B; - blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, blas::T(BT)); - BOOST_REQUIRE_CLOSE( (~BT)[1][2] , 0.107143 , 0.001 ); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex) { - namespace blas = multi::blas; - using complex = std::complex; auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + 2.0*I, 3.0 - 1.0*I, 4.0 + 9.0*I}, - {NAN , 7.0 + 4.0*I, 1.0 + 8.0*I}, - {NAN , NAN , 8.0 + 2.0*I} - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 - 9.0*I, 3.0 + 2.0*I, 4.0 + 3.0*I}, - {2.0 - 2.0*I, 7.0 - 2.0*I, 1.0 - 1.0*I}, - {3.0 + 1.0*I, 4.0 + 8.0*I, 2.0 + 7.0*I} - }; - blas::trsm(blas::side::left, blas::filling::lower, 2.+1.*I, blas::H(A), B); // B=alpha Inv[A†].B, B†=B†.Inv[A], Solve(A†.X=B, X), Solve(X†.A=B†, X), A is upper triangular (with implicit zeros below) - BOOST_REQUIRE_CLOSE( real(B[1][2]) , 2.33846 , 0.0001 ); - BOOST_REQUIRE_CLOSE( imag(B[1][2]) , -0.0923077 , 0.0001 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_rectangular) { - namespace blas = multi::blas; - using complex = std::complex; complex const I{0, 1}; // NOLINT(readability-identifier-length) imag unit - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { 1.0 + 2.0*I, 3.0 - 1.0*I, 4.0 + 9.0*I}, - {NAN , 7.0 + 4.0*I, 1.0 + 8.0*I}, - {NAN , NAN , 8.0 + 2.0*I} - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1. - 9.*I, 3. + 2.*I}, - {2. - 2.*I, 7. - 2.*I}, - {3. + 1.*I, 4. + 8.*I} - }; - blas::trsm(blas::side::left, blas::filling::lower, 2.0 + 1.0*I, blas::H(A), B); // B=alpha Inv[A†].B, B†=B†.Inv[A], Solve(A†.X=B, X), Solve(X†.A=B†, X), A is upper triangular (with implicit zeros below) - BOOST_REQUIRE_CLOSE( real(B[2][0]) , -4.16471 , 0.0001 ); - BOOST_REQUIRE_CLOSE( imag(B[2][0]) , 8.25882 , 0.0001 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_column) { - namespace blas = multi::blas; - using complex = std::complex; complex const I{0, 1}; // NOLINT(readability-identifier-length) imag unit - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + 2.0*I, 3.0 - 1.0*I, 4.0 + 9.0*I}, - {NAN , 7.0 + 4.0*I, 1.0 + 8.0*I}, - {NAN , NAN , 8.0 + 2.0*I} - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1. - 9.*I}, - {2. - 2.*I}, - {3. + 1.*I} - }; - blas::trsm(blas::side::left, blas::filling::lower, 2.0 + 1.0*I, blas::H(A), B); // B=alpha Inv[A†].B, B†=B†.Inv[A], Solve(A†.X=B, X), Solve(X†.A=B†, X), A is upper triangular (with implicit zeros below) - BOOST_REQUIRE_CLOSE( real(B[2][0]) , -4.16471 , 0.0001); - BOOST_REQUIRE_CLOSE( imag(B[2][0]) , 8.25882 , 0.0001); -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_column_cpu) { - namespace blas = multi::blas; - using complex = std::complex; complex const I{0, 1}; // NOLINT(readability-identifier-length) imaginary unit - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { 1.0 + 2.0*I, 3.0 - 1.0*I, 4.0 + 9.0*I}, - {NAN , 7.0 + 4.0*I, 1.0 + 8.0*I}, - {NAN , NAN , 8.0 + 2.0*I} - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 - 9.0*I}, - {2.0 - 2.0*I}, - {3.0 + 1.0*I} - }; - blas::trsm(blas::side::left, blas::filling::lower, 2.0 + 1.0*I, blas::H(A), B); // B=alpha Inv[A†].B, B†=B†.Inv[A], Solve(A†.X=B, X), Solve(X†.A=B†, X), A is upper triangular (with implicit zeros below) - BOOST_REQUIRE_CLOSE( real(B[2][0]) , -4.16471 , 0.0001 ); - BOOST_REQUIRE_CLOSE( imag(B[2][0]) , 8.25882 , 0.0001 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_hydrogen_inq_case_real) { - namespace blas = multi::blas; - multi::array const A = {{2.0, }, }; // NOLINT(readability-identifier-length) BLAS naming - { - multi::array B = {{1.0, 2.0, 3.0}, }; // NOLINT(readability-identifier-length) BLAS naming - BOOST_REQUIRE( B.size() == 1 ); - auto const B_cpy = B; - blas::trsm(blas::side::left, blas::filling::lower, 1.0, A, B); - BOOST_REQUIRE( B[0][1] == B_cpy[0][1]/A[0][0] ); - } - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0}, - {2.0}, - {3.0}, - }; - auto const B_cpy = B; - blas::trsm(blas::side::left, blas::filling::lower, 1.0, A, blas::T(B)); - BOOST_REQUIRE( blas::T(B)[0][1] == blas::T(B_cpy)[0][1]/A[0][0] ); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_hydrogen_inq_case_complex) { - namespace blas = multi::blas; - using complex = std::complex; - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { {2.0, 0.0}, }, - }; - - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - { {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0} }, - }; - auto const B_cpy = B; - blas::trsm(blas::side::left, blas::filling::lower, {1.0, 0.0}, A, B); - BOOST_REQUIRE( B[0][1] == B_cpy[0][1]/A[0][0] ); - } - multi::array const B1 = { - { {1.0, 0.0} }, - { {2.0, 0.0} }, - { {3.0, 0.0} }, - }; - multi::array B2 = { - { {1.0, 0.0} }, - { {2.0, 0.0} }, - { {3.0, 0.0} }, - }; - - blas::trsm(blas::side::left, blas::filling::lower, {1.0, 0.0}, A, blas::H(B1)); - - { - auto const B_cpy = B2; - blas::trsm(blas::side::right, blas::filling::upper, {1.0, 0.0}, blas::H(A), B2); - // BOOST_REQUIRE( (+blas::gemm(1., A, blas::H(B)))[0][1] == blas::H(B_cpy)[0][1] ); - BOOST_REQUIRE( (+blas::gemm(1., B2, blas::H(A)))[1][0] == B_cpy[1][0] ); - } - BOOST_REQUIRE( B1 == B2 ); -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_real_nonsquare) { - namespace blas = multi::blas; - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - {1.0, 3.0, 40.0}, - {NAN, 7.0, 1.0}, - {NAN, NAN, 8.0} - }; - auto const A_cpy = triangular(blas::filling::upper, A); - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0, 3.0, 4.0, 8.0}, - {2.0, 7.0, 1.0, 9.0}, - {3.0, 4.0, 2.0, 1.0}, - }; - auto const B_cpy =+ B; - multi::array BT =+ ~B; - BOOST_REQUIRE( BT == ~B ); - blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) - BOOST_REQUIRE_CLOSE( B[1][2] , 0.107143 , 0.001); - BOOST_REQUIRE_CLOSE( (+blas::gemm(1., A_cpy, B))[1][2] , B_cpy[1][2] , 0.001); - - auto const BT_cpy = BT; - blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, blas::T(BT)); - BOOST_REQUIRE_CLOSE( blas::T(BT)[1][2], 0.107143, 0.001 ); - - BOOST_REQUIRE_CLOSE( (+blas::gemm(1., A_cpy, blas::T(BT)))[1][2] , blas::T(BT_cpy)[1][2] , 0.00001 ); - } - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0, 3.0, 4.0, 8.0}, - {2.0, 7.0, 1.0, 9.0}, - {3.0, 4.0, 2.0, 1.0}, - }; - multi::array AT = ~A; - multi::array BT = ~B; - blas::trsm(blas::side::left, blas::filling::upper, 1.0, blas::T(AT), B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) - BOOST_REQUIRE_CLOSE( B[1][2] , 0.107143 , 0.001 ); - - blas::trsm(blas::side::left, blas::filling::upper, 1.0, blas::T(AT), blas::T(BT)); - BOOST_REQUIRE_CLOSE( (~BT)[1][2] , 0.107143, 0.001 ); - } - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0}, - {2.0}, - {3.0}, - }; - auto const B_cpy =+ B; - blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) - BOOST_REQUIRE_CLOSE( B[2][0] , 0.375 , 0.00001 ); - BOOST_REQUIRE_CLOSE( (+blas::gemm(1., A_cpy, B))[1][0] , B_cpy[1][0] , 0.00001 ); - } - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0}, - {2.0}, - {3.0}, - }; - auto const B_cpy =+ B; - blas::trsm(blas::side::left, blas::filling::upper, 1.2, A, B); - BOOST_REQUIRE_CLOSE( (+blas::gemm(1.0, A_cpy, B))[1][0] , 1.2*B_cpy[1][0] , 0.00001 ); - BOOST_REQUIRE_CLOSE( (+blas::gemm(1.0/1.2, A_cpy, B))[1][0] , B_cpy[1][0] , 0.00001 ); - } - { - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0}, - {2.0}, - {3.0}, - }; - multi::array BT = rotated(B); - blas::trsm(blas::side::left, blas::filling::upper, 1.0, A, blas::T(BT)); - BOOST_REQUIRE_CLOSE( (~BT)[2][0] , 0.375 , 0.00001); - } -} - -BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { - namespace blas = multi::blas; - using complex = std::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - using multi::blas::trsm; - using multi::blas::filling; - using multi::blas::hermitized; - blas::trsm(blas::side::left, blas::filling::upper, {1.0, 0.0}, A, blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† - BOOST_REQUIRE_CLOSE( imag(B[1][2]) , -0.147059 , 0.001); -} - -BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { - namespace blas = multi::blas; - using complex = std::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - using multi::blas::trsm; - - blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(A), blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† - BOOST_REQUIRE_CLOSE( imag(B[1][2]) , -0.147059 , 0.001); -} diff --git a/include/multi/adaptors/blas/trsm.hpp b/include/multi/adaptors/blas/trsm.hpp deleted file mode 100644 index cea81698a..000000000 --- a/include/multi/adaptors/blas/trsm.hpp +++ /dev/null @@ -1,166 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_TRSM_HPP -#define MULTI_ADAPTORS_BLAS_TRSM_HPP - -#include "../blas/core.hpp" -#include "../blas/filling.hpp" -#include "../blas/operations.hpp" // uplo -#include "../blas/side.hpp" - -namespace boost::multi::blas { - -enum class diagonal : char { - unit = 'U', - non_unit = 'N', general = non_unit -}; - -template -auto triangular_parted(Array const& arr) { - class triangular_part { - Array const& ref_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - - public: - explicit triangular_part(Array const& ref) : ref_{ref} {} - static constexpr auto filling() { return Fill; } - using underlying_type __attribute__((unused)) = Array; - auto underlying() const -> Array const& { return ref_;} - }; - - return triangular_part{arr}; -} - -template -auto lower_parted(Array const& arr) {return triangular_parted(arr);} - -template -auto upper_parted(Array const& arr) {return triangular_parted(arr);} - -template auto L(Array const& arr) { return lower_parted(arr); } // NOLINT(readability-identifier-naming) BLAS naming -template auto U(Array const& arr) { return upper_parted(arr); } // NOLINT(readability-identifier-naming) BLAS naming - -template -auto triangular(multi::blas::filling f, Matrix const& m) { // NOLINT(readability-identifier-length) BLAS naming - auto ret =+ m; - switch(f) { - case multi::blas::filling::upper: - { - auto ext = extension(ret); - std::for_each(ext.begin(), ext.end(), [&ret](auto idx) { - std::fill_n(ret[idx].begin(), std::min(idx, size(~ret)), 0.0); - }); - } - break; - case multi::blas::filling::lower: - { - auto extt = extension(~ret); - std::for_each(extt.begin(), extt.end(), [&ret](auto jdx) { - std::fill_n( (~ret)[jdx].begin(), std::min(jdx, size( ret)), 0.0); - }); - } - break; - } - return ret; -} - -using core::trsm; - -template -auto trsm(Context&& ctxt, blas::side a_side, blas::filling a_fill, blas::diagonal a_diag, typename A2D::element_type alpha, A2D const& a, B2D&& b) // NOLINT(readability-function-cognitive-complexity,readability-identifier-length) cognitive load 115, BLAS naming --> B2D&& { - if(a_side == blas::side::left ) {assert(size(~a) >= size( b));} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - if(a_side == blas::side::right) {assert(size( a) >= size(~b));} // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - - assert( stride( a) == 1 or stride(~a) == 1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - assert( stride( b) == 1 or stride(~b) == 1 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - - if(size(b) != 0) { - #define CTXT std::forward(ctxt) - if constexpr(not is_conjugated{} and not is_conjugated{}) { - if (stride( a)==1 and stride( b)==1) {CTXT->trsm(char{ (a_side)}, static_cast(-a_fill), 'N', static_cast(a_diag), size( b), size(~b), alpha , base(a) , stride(~a), base(b) , stride(~b));} - else if(stride(~a)==1 and stride(~b)==1) {CTXT->trsm(char{swap(a_side)}, static_cast(+a_fill), 'N', static_cast(a_diag), size(~b), size( b), alpha , base(a) , stride( a), base(b) , stride( b));} - else if(stride( a)==1 and stride(~b)==1) {CTXT->trsm(char{swap(a_side)}, static_cast(-a_fill), 'T', static_cast(a_diag), size(~b), size( b), alpha , base(a) , stride(~a), base(b) , stride( b));} - else if(stride(~a)==1 and stride( b)==1) {CTXT->trsm(char{ (a_side)}, static_cast(+a_fill), 'T', static_cast(a_diag), size( b), size(~b), alpha , base(a) , stride( a), base(b) , stride(~b));} - else {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE - } else if constexpr( is_conjugated{} and not is_conjugated{}) { - if (stride( a)==1 and stride(~b)==1) {CTXT->trsm(char{swap(a_side)}, static_cast(-a_fill), 'C', static_cast(a_diag), size(~b), size( b), alpha , underlying(base(a)), stride(~a), base(b) , stride( b));} - else if(stride(~a)==1 and stride( b)==1) {CTXT->trsm(char{ (a_side)}, static_cast(+a_fill), 'C', static_cast(a_diag), size( b), size(~b), alpha , underlying(base(a)), stride( a), base(b) , stride(~b));} - else {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE - } else if constexpr(not is_conjugated{} and is_conjugated{}) { - if (stride(~a)==1 and stride( b)==1) {CTXT->trsm(char{ (a_side)}, static_cast(+a_fill), 'C', static_cast(a_diag), size( b), size(~b), conj(alpha), base(a) , stride( a), underlying(base(b)), stride(~b));} - // else if(stride( a)==1 and stride(~b)==1) {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE - else {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE - } else if constexpr( is_conjugated{} and is_conjugated{}) { - if (stride( a)==1 and stride(~b)==1) {CTXT->trsm(char{swap(a_side)}, static_cast(-a_fill), 'T', static_cast(a_diag), size(~b), size( b), conj(alpha), underlying(base(a)), stride(~a), underlying(base(b)), stride( b));} - else if(stride(~a)==1 and stride( b)==1) {CTXT->trsm(char{ (a_side)}, static_cast(+a_fill), 'T', static_cast(a_diag), size( b), size(~b), conj(alpha), underlying(base(a)), stride( a), underlying(base(b)), stride(~b));} - else {assert(0 && "not implemented in blas");} // LCOV_EXCL_LINE - } - #undef CTXT - } - return std::forward(b); -} - -template -auto trsm(blas::side a_side, blas::filling a_fill, blas::diagonal a_diag, typename A2D::element_type alpha, A2D const& a, B2D&& b) -> decltype(auto) { // NOLINT(readability-identifier-length) BLAS naming - if constexpr(not is_conjugated{}) {return trsm(default_context_of( a.base() ), a_side, a_fill, a_diag, alpha, a, std::forward(b));} - else {return trsm(default_context_of(underlying(a.base())), a_side, a_fill, a_diag, alpha, a, std::forward(b));} -} - -template -auto trsm(Context&& ctxt, blas::side a_side, blas::filling a_fill, typename A2D::element_type alpha, A2D const& a, B2D&& b) // NOLINT(readability-identifier-length) BLAS naming -->decltype(trsm(std::forward(ctxt), a_side, a_fill, blas::diagonal::non_unit, alpha, a, std::forward(b))) { - return trsm(std::forward(ctxt), a_side, a_fill, blas::diagonal::non_unit, alpha, a, std::forward(b)); } - -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -template -auto trsm(blas::side a_side, blas::filling a_fill, typename A2D::element_type alpha, A2D const& a, B2D&& b) -> decltype(auto) { // NOLINT(readability-identifier-length) BLAS naming - if constexpr(not is_conjugated{}) {return trsm(blas::default_context_of( a.base() ), a_side, a_fill, alpha, a, std::forward(b));} - else {return trsm(blas::default_context_of(underlying(a.base())), a_side, a_fill, alpha, a, std::forward(b));} -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #pragma diagnostic pop - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -template -auto trsm(blas::side a_side, typename UTArr::underlying_type::element_type alpha, UTArr const& a, B2D&& b) // NOLINT(readability-identifier-length) BLAS naming -->decltype(trsm(a_side, a.filling(), blas::diagonal::non_unit, alpha, a.underlying(), std::forward(b))) { - return trsm(a_side, a.filling(), blas::diagonal::non_unit, alpha, a.underlying(), std::forward(b)); } - -namespace operators { - - template - auto operator/=(B2D&& b, UL const& a) // NOLINT(readability-identifier-length) BLAS naming - ->decltype(blas::trsm(blas::side::right, 1.0, a, std::forward(b))) { - return blas::trsm(blas::side::right, 1.0, a, std::forward(b)); } - - template - auto operator|=(B2D&& b, UL const& a) // NOLINT(readability-identifier-length) BLAS naming - ->decltype(blas::trsm(blas::side::left, 1.0, a, std::forward(b))) { - return blas::trsm(blas::side::left, 1.0, a, std::forward(b)); } - - using blas::U; - using blas::L; - -} // end namespace operators - -} // end namespace boost::multi::blas - -#endif diff --git a/include/multi/adaptors/blas/trsv.hpp b/include/multi/adaptors/blas/trsv.hpp deleted file mode 100644 index 154e2cf81..000000000 --- a/include/multi/adaptors/blas/trsv.hpp +++ /dev/null @@ -1,603 +0,0 @@ -#ifndef MULTI_ADAPTORS_BLAS_TRSV_HPP // -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -#define MULTI_ADAPTORS_BLAS_TRSV_HPP -// Copyright 2019-2021 Alfredo A. Correa - -#include "../blas/core.hpp" - -#include "../blas/operations.hpp" // uplo -#include "../blas/filling.hpp" -#include "../blas/side.hpp" - -#include "../../config/NODISCARD.hpp" - -namespace boost::multi::blas { - -//enum DIAG : char{U='U', N='N'}; - -enum class diagonal : char {//typename std::underlying_type::type{ - unit = 'U', - non_unit = 'N', general = non_unit -}; - -using core::trsv; - -template{}, int> =0> -auto trsv_base(A&& a) {return base(a);} - -template{}, int> =0> -auto trsv_base(A&& a) {return underlying(base(a));} - -template -auto trsv(filling a_nonzero_side, diagonal a_diag, A2D const& a, X1D&& x) -->decltype(trsv(static_cast(flip(a_nonzero_side)), 'N', static_cast(a_diag), size(x), trsv_base(a), stride(rotated(a)), trsv_base(x), stride(x)), std::forward(x)) -{ -// if(is_conjugated(x)) trsv(a_nonzero_side, a_diag, conjugated(a), conjugated(std::forward(x))); - { - auto base_a = trsv_base(a); - auto base_x = trsv_base(x); - if(not is_conjugated{}) { - if(stride( a )==1) {trsv(static_cast(flip(a_nonzero_side)), 'N', static_cast(a_diag), size(x), base_a, stride(rotated(a)), base_x, stride(x));} - else if(stride(rotated(a))==1) {trsv(static_cast( a_nonzero_side ), 'T', static_cast(a_diag), size(x), base_a, stride( a ), base_x, stride(x));} - else {assert(0);} - }else{ - if(stride( a )==1) {assert(0);} //TODO fallback to trsm? - else if(stride(rotated(a))==1) {trsv(static_cast( a_nonzero_side ), 'C', static_cast(a_diag), size(x), base_a, stride( a ), base_x, stride(x));} - else {assert(0);} - } - } - return std::forward(x); -} - -template -auto trsv(filling a_nonzero_side, A2D const& a, X1D&& x) -->decltype(trsv(a_nonzero_side, diagonal::general, a, std::forward(x))) { - return trsv(a_nonzero_side, diagonal::general, a, std::forward(x)); } - -#if 0 - - -#if 1 -template -Ret trsv(filling a_nonzero_side, diagonal a_diag, A2D const& a, X1D const& x, void* = 0){ - return trsv(a_nonzero_side, a_diag, a, Ret{x});} - -template -Ret trsv(filling a_nonzero_side, A2D const& a, X1D const& x, void* = 0){ - return trsv(a_nonzero_side, a, Ret{x});} -#endif -#endif - -} // end namespace boost::multi::blas - -//#if not __INCLUDE_LEVEL__ // _TEST_MULTI_ADAPTORS_BLAS_TRSV - -//#define BOOST_TEST_MODULE "C++ Unit Tests for Multi.BLAS trsv" -//#define BOOST_TEST_DYN_LINK -//#include - -//#include "../blas/gemm.hpp" - -//#include "../../array.hpp" - -//#include - -//namespace multi = boost::multi; - -//template decltype(auto) print_1D(M const& C){ -// using boost::multi::size; using std::cout; -// for(int i = 0; i != size(C); ++i) -// cout<< C[i] <<' '; -// cout< decltype(auto) print(M const& C){ -// using boost::multi::size; using std::cout; -// for(int i = 0; i != size(C); ++i){ -// for(int j = 0; j != size(C[i]); ++j) -// cout<< C[i][j] <<' '; -// cout< const A = { -// { 1., 3., 4.}, -// { NAN, 7., 1.}, -// { NAN, NAN, 8.} -// }; -// multi::array b = {1., 3., 4.}; -// blas::trsv(blas::filling::upper, blas::diagonal::general, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ -// BOOST_TEST( b[0] == -2.07143 ); -// BOOST_TEST( b[1] == 0.357143 ); -// BOOST_TEST( b[2] == 0.5 ); -// } -// { -// multi::array const A = { -// { 1., 3., 4.}, -// { NAN, 7., 1.}, -// { NAN, NAN, 8.} -// }; -// multi::array b = {1., 3., 4.}; -// blas::trsv(blas::filling::lower, blas::diagonal::general, blas::T(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -// BOOST_TEST( b[0] == 1. ); -// BOOST_TEST( b[1] == 0. ); -// BOOST_TEST( b[2] == 0. ); -// } -//#if 0 -// { -// multi::array b = {3., 3., 1.}; -// // trsv(filling::lower, diagonal::general, hermitized(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -// // BOOST_TEST( b[0] == 3. ); -// // BOOST_TEST( b[1] == -0.857143 ); -// // BOOST_TEST( b[2] == -1.26786 ); -// } -//#endif -//} - -//#if 0 -//using complex = std::complex; - -//BOOST_AUTO_TEST_CASE(multi_blas_trsv_complex_real_case_square, *utf::tolerance(0.00001)){ -// multi::array const A = { -// { 1., 3., 4.}, -// {NAN, 7., 1.}, -// {NAN, NAN, 8.} -// }; -// using blas::filling; -// using blas::diagonal; -// using blas::transposed; -// using blas::hermitized; -// using blas::conjugated; -// using blas::trsv; -// { -// multi::array b = {1., 3., 4.}; -// blas::trsv(filling::upper, diagonal::general, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ -// BOOST_TEST( real(b[0]) == -2.07143 ); -// BOOST_TEST( real(b[1]) == 0.357143 ); -// BOOST_TEST( real(b[2]) == 0.5 ); -// } -// { -// multi::array const b = {1., 3., 4.}; -// auto b_copy = blas::trsv(filling::upper, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ -// BOOST_TEST( real(b[0]) == 1. ); -// BOOST_TEST( real(b_copy[0]) == -2.07143 ); -// BOOST_TEST( real(b_copy[1]) == 0.357143 ); -// BOOST_TEST( real(b_copy[2]) == 0.5 ); -// } -// { -// multi::array const b = {1., 3., 4.}; -// auto b_copy = blas::trsv(filling::upper, diagonal::general, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ -// BOOST_TEST( real(b[0]) == 1. ); -// BOOST_TEST( real(b_copy[0]) == -2.07143 ); -// BOOST_TEST( real(b_copy[1]) == 0.357143 ); -// BOOST_TEST( real(b_copy[2]) == 0.5 ); -// } -// { -// multi::array b = {3., 3., 1.}; -// trsv(filling::lower, diagonal::general, transposed(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -// BOOST_TEST( real(b[0]) == 3. ); -// BOOST_TEST( real(b[1]) == -0.857143 ); -// BOOST_TEST( real(b[2]) == -1.26786 ); -// } -// { -// multi::array b = {3., 3., 1.}; -// // trsv(filling::lower, diagonal::general, hermitized(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -// // BOOST_TEST( real(b[0]) == 3. ); -// // BOOST_TEST( real(b[1]) == -0.857143 ); -// // BOOST_TEST( real(b[2]) == -1.26786 ); -// } -// { -// multi::array b = {3., 3., 1.}; -//// trsv(filling::lower, diagonal::general, hermitized(A), conjugated(b)); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -//// BOOST_TEST( real(b[0]) == 3. ); -//// BOOST_TEST( real(b[1]) == -0.857143 ); -//// BOOST_TEST( real(b[2]) == -1.26786 ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsv_complex_square, *utf::tolerance(0.00001)){ -// namespace blas = multi::blas; - -// multi::array const A = { -// { 1. + 1.*I, 3. - 2.*I, 4. + 1.*I}, -// {NAN , 7. - 10.*I, 1. + 2.*I}, -// {NAN , NAN , 8. + 1.*I} -// }; -// using blas::filling; -// using blas::diagonal; -// using blas::transposed; -// using blas::hermitized; -// using blas::conjugated; -// using blas::trsv; -// { -// multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; -// blas::trsv(filling::upper, diagonal::general, A, b); // B<-Solve(A.X==B), B<-A⁻¹.B, B⊤<-(A⁻¹.B)⊤, B<-B⊤.A⁻¹⊤ -// BOOST_TEST( real(b[0]) == -1.37259 ); -// BOOST_TEST( real(b[1]) == 0.2127 ); -// BOOST_TEST( real(b[2]) == 0.569231 ); -// } -// { -// multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; -// trsv(filling::lower, diagonal::general, transposed(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -// BOOST_TEST( real(b[0]) == 1.5 ); BOOST_TEST( imag(b[0]) == 0.5 ); -// BOOST_TEST( real(b[1]) == -0.285235 ); BOOST_TEST( imag(b[1]) == -0.0503356 ); -// BOOST_TEST( real(b[2]) == -0.129272 ); BOOST_TEST( imag(b[2]) == 0.28126 ); -// } -// { -// multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; -// trsv(filling::upper, diagonal::general, blas::H(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -// print_1D(b); -// BOOST_TEST( real(b[0]) == -0.661693 ); BOOST_TEST( imag(b[0]) == -1.13934 ); -// BOOST_TEST( real(b[1]) == 0.135261 ); BOOST_TEST( imag(b[1]) == -0.0283944 ); -// BOOST_TEST( real(b[2]) == 0.415385 ); BOOST_TEST( imag(b[2]) == 0.676923 ); -// } -// { -// multi::array b = {1. - 2.*I, 3. - 1.*I, 4. - 5.*I}; -// trsv(filling::upper, diagonal::general, blas::H(A), blas::conj(b)); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -//// print_1D(b); -//// BOOST_TEST( real(conjugated(b)[0]) == -0.661693 ); BOOST_TEST( imag(conjugated(b)[0]) == -1.13934 ); -//// BOOST_TEST( real(conjugated(b)[1]) == 0.135261 ); BOOST_TEST( imag(conjugated(b)[1]) == -0.0283944 ); -//// BOOST_TEST( real(conjugated(b)[2]) == 0.415385 ); BOOST_TEST( imag(conjugated(b)[2]) == 0.676923 ); -// } -// { -// multi::array b = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; -// // trsv(filling::lower, diagonal::general, hermitized(A), b); // B<-Solve(A.X==B), B<-A⊤⁻¹.B, B⊤<-(A⊤⁻¹.B)⊤, B<-B⊤.A⁻¹ -// // BOOST_TEST( real(b[0]) == -0.5 ); BOOST_TEST( imag(b[0]) == 1.5 ); -// // BOOST_TEST( real(b[1]) == 0.184564 ); BOOST_TEST( imag(b[1]) == -0.620805 ); -// // BOOST_TEST( real(b[2]) == 0.691791 ); BOOST_TEST( imag(b[2]) == 0.0227155 ); -// } -//} - - -//#if 0 -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_1x1, *utf::tolerance(0.00001)){ -// multi::array const A = { -// {10.,}, -// }; -// using multi::blas::side; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array B = { -// {3.,}, -// }; -// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) -// BOOST_TEST( B[0][0] == 3./10. ); -// } -// { -// multi::array B = { -// {3.,}, -// }; -// trsm(filling::upper, diagonal::general, 2., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) -// BOOST_TEST( B[0][0] == 2.*3./10. ); -// } -// { -// multi::array B = { -// {3., 4., 5.}, -// }; -// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) -// BOOST_TEST( B[0][1] == 4./10. ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_double_0x0, *utf::tolerance(0.00001)){ -// multi::array const A; -// using multi::blas::side; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array B; -// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_real_nonsquare, *utf::tolerance(0.00001)){ -// multi::array const A = { -// { 1., 3., 4.}, -// { 0., 7., 1.}, -// { 0., 0., 8.} -// }; -// using multi::blas::side; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array B = { -// {1., 3., 4., 8.}, -// {2., 7., 1., 9.}, -// {3., 4., 2., 1.}, -// }; -// multi::array BT = rotated(B); -// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) -// BOOST_TEST( B[1][2] == 0.107143 ); - -// trsm(filling::upper, diagonal::general, 1., A, rotated(BT)); -// BOOST_TEST( rotated(BT)[1][2] == 0.107143 ); -// } -// { -// multi::array B = { -// {1., 3., 4., 8.}, -// {2., 7., 1., 9.}, -// {3., 4., 2., 1.}, -// }; -// multi::array AT = rotated(A); -// multi::array BT = rotated(B); -// trsm(filling::upper, diagonal::general, 1., rotated(AT), B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) -// BOOST_TEST( B[1][2] == 0.107143 ); - -// trsm(filling::upper, diagonal::general, 1., rotated(AT), rotated(BT)); -// print(rotated(BT)); -// BOOST_TEST( rotated(BT)[1][2] == 0.107143 ); -// } -// { -// multi::array B = { -// {1.}, -// {2.}, -// {3.}, -// }; -// trsm(filling::upper, diagonal::general, 1., A, B); // B=Solve(A.X=alpha*B, X) B=A⁻¹B, B⊤=B⊤.(A⊤)⁻¹, A upper triangular (implicit zeros below) -// BOOST_TEST( B[2][0] == 0.375 ); -// } -// { -// multi::array B = { -// {1.}, -// {2.}, -// {3.}, -// }; -// multi::array BT = rotated(B); -// trsm(filling::upper, diagonal::general, 1., A, rotated(BT)); -// BOOST_TEST( rotated(BT)[2][0] == 0.375 ); -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_real_nonsquare_default_diagonal_gemm_check, *utf::tolerance(0.00001)){ -// multi::array const A = { -// { 1., 3., 4.}, -// { 0., 7., 1.}, -// { 0., 0., 8.} -// }; -// using multi::blas::side; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array const B = { -// {1.},// 3., 4.}, -// {2.},// 7., 1.}, -// {3.},// 4., 2.}, -// }; -// using multi::blas::gemm; -// { -// auto S = trsm(filling::upper, diagonal::general, 1., A, B); -// BOOST_REQUIRE( S[2][0] == 0.375 ); -// auto Bck=gemm(1., A, S); -// BOOST_REQUIRE( Bck[2][0] == 3. ); -// for(int i{};i<3;++i)for(int j{};j const BT = rotated(B); -// auto Bck=gemm(1., A, trsm(filling::upper, diagonal::general, 1., A, rotated(BT))); -// for(int i{};i<3;++i)for(int j{};j const A = { -// { 4.}, -// }; -// using multi::blas::side; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array const B = { -// {5.}, -// }; -// { -// auto S = trsm(filling::upper, diagonal::general, 3., A, B); -// BOOST_REQUIRE( S[0][0] == 3.*5./4. ); -// } -// { -// auto S = trsm(filling::upper, 1., A, B); -// BOOST_REQUIRE( S[0][0] == 1.*5./4. ); -// } -// { -// auto S = trsm(filling::upper, A, B); -// BOOST_REQUIRE( S[0][0] == 1.*5./4. ); -// } -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_1x1_check, *utf::tolerance(0.00001)){ -// using complex = std::complex; complex const I = complex{0, 1}; -// multi::array const A = { -// { 4. + 2.*I}, -// }; -// using multi::blas::side; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array const B = { -// {5. + 1.*I}, -// }; -// using multi::blas::gemm; -// { -// auto S = trsm(filling::upper, diagonal::general, 3.+5.*I, A, B); -// BOOST_TEST( real(S[0][0]) == real((3.+5.*I)*B[0][0]/A[0][0]) ); -// BOOST_TEST( imag(S[0][0]) == imag((3.+5.*I)*B[0][0]/A[0][0]) ); -// } -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_one_check, *utf::tolerance(0.00001)){ -// using complex = std::complex; complex const I{0, 1}; -// multi::array const A = { -// { 1. + 4.*I, 3., 4.- 10.*I}, -// { 0., 7.- 3.*I, 1.}, -// { 0., 0., 8.- 2.*I} -// }; -// using multi::blas::side; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array const B = { -// {1. + 1.*I}, -// {2. + 1.*I}, -// {3. + 1.*I}, -// }; -// using multi::blas::gemm; -// { -// auto S = trsm(filling::upper, diagonal::general, 1., A, B); -// BOOST_TEST( real(S[2][0]) == 0.323529 ); -// } -// { -// auto const BT = +rotated(B); -// auto S = trsm(filling::upper, diagonal::general, 1., A, rotated(BT)); -// BOOST_TEST( real(S[2][0]) == 0.323529 ); -// } -// { -// auto const AT = +rotated(A); -// auto S = trsm(filling::upper, diagonal::general, 1., rotated(AT), B); -// BOOST_TEST( real(S[2][0]) == 0.323529 ); -// } -// { -// auto const AT = +rotated(A); -// auto const BT = +rotated(B); -// auto S = trsm(filling::upper, diagonal::general, 1., rotated(AT), rotated(BT)); -// BOOST_TEST( real(S[2][0]) == 0.323529 ); -// } -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_gemm_check, *utf::tolerance(0.00001)){ -// using complex = std::complex; complex const I{0, 1}; -// multi::array const A = { -// { 1. + 4.*I, 3., 4.- 10.*I}, -// { 0., 7.- 3.*I, 1.}, -// { 0., 0., 8.- 2.*I} -// }; -// using multi::blas::side; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array const B = { -// {1. + 1.*I, 5. + 3.*I}, -// {2. + 1.*I, 9. + 3.*I}, -// {3. + 1.*I, 1. - 1.*I}, -// }; -// using multi::blas::gemm; -// { -// auto S = trsm(filling::upper, diagonal::general, 1., A, B); // S = Ainv.B -// BOOST_TEST( real(S[2][1]) == 0.147059 ); -// } -// { -// auto const BT = +rotated(B); -// auto S = trsm(filling::upper, diagonal::general, 1., A, rotated(BT)); -// BOOST_TEST( real(S[2][1]) == 0.147059 ); -// } -// { -// auto const AT = +rotated(A); -// auto S = trsm(filling::upper, diagonal::general, 1., rotated(AT), B); -// BOOST_TEST( real(S[2][1]) == 0.147059 ); -// } -// { -// auto const AT = +rotated(A); -// auto const BT = +rotated(B); -// auto S = trsm(filling::upper, diagonal::general, 1., rotated(AT), rotated(BT)); -// BOOST_TEST( real(S[2][1]) == 0.147059 ); -// } -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check, *utf::tolerance(0.00001)){ -// using complex = std::complex; complex const I{0, 1}; -// multi::array const A = { -// { 1. + 4.*I, 3., 4.- 10.*I}, -// { 0., 7.- 3.*I, 1.}, -// { 0., 0., 8.- 2.*I} -// }; -// using multi::blas::filling; -// using multi::blas::diagonal; -// { -// multi::array const B = { -// {1. + 1.*I, 5. + 3.*I}, -// {2. + 1.*I, 9. + 3.*I}, -// {3. + 1.*I, 1. - 1.*I}, -// }; -// using multi::blas::hermitized; -// { -// auto S = trsm(filling::lower, diagonal::general, 1., hermitized(A), B); // S = A⁻¹†.B, S† = B†.A⁻¹ -// BOOST_TEST( real(S[2][1]) == 1.71608 ); -// } -// { -// multi::array const B = { -// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, -// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} -// }; -// auto S =* trsm(filling::upper, 1., A, hermitized(B)); // S = A⁻¹B†, S†=B.A⁻¹†, S=(B.A⁻¹)†, B <- S†, B <- B.A⁻¹† -// BOOST_TEST( imag(S[2][1]) == +0.147059 ); -// BOOST_TEST( imag(B[1][2]) == -0.147059 ); -// } -// { -// multi::array const B = { -// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, -// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} -// }; -// auto S =* trsm(filling::upper, 2., A, hermitized(B)); // S = A⁻¹B†, S†=B.A⁻¹†, S=(B.A⁻¹)†, B <- S†, B <- B.A⁻¹† -// BOOST_TEST( imag(S[2][1]) == +0.147059*2. ); -// BOOST_TEST( imag(B[1][2]) == -0.147059*2. ); -// } -// } -//} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const, *utf::tolerance(0.00001)){ -// using complex = std::complex; complex const I{0, 1}; -// multi::array const A = { -// { 1. + 4.*I, 3., 4.- 10.*I}, -// { 0., 7.- 3.*I, 1.}, -// { 0., 0., 8.- 2.*I} -// }; -// multi::array B = { -// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, -// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} -// }; -// using multi::blas::trsm; -// using multi::blas::filling; -// using multi::blas::hermitized; -// trsm(filling::upper, A, hermitized(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† -// BOOST_TEST( imag(B[1][2]) == -0.147059 ); -//} -//#endif -//#endif - - -//#endif -#endif diff --git a/include/multi/adaptors/cuda.hpp b/include/multi/adaptors/cuda.hpp deleted file mode 100644 index 5537f84b0..000000000 --- a/include/multi/adaptors/cuda.hpp +++ /dev/null @@ -1,285 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4-*- -$CXX $0 -o $0x -lcudart -lboost_unit_test_framework -lboost_timer -ldl&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2019-2020 - -#ifndef MULTI_ADAPTORS_CUDA_HPP -#define MULTI_ADAPTORS_CUDA_HPP - -//#include "../memory/adaptors/cuda/allocator.hpp" -//#include "../memory/adaptors/cuda/managed/allocator.hpp" -#include "../adaptors/cuda/algorithms/copy.hpp" - -#include "../array.hpp" - -#if 0 -namespace boost{ -namespace multi{ -namespace cuda{ - - template - using allocator = multi::memory::cuda::allocator; - - template using ptr = multi::memory::cuda::ptr; - - template - using array = multi::array>; - - template - using array_ref = multi::array_ref>; - - template - using static_array = multi::static_array>; - -// template auto raw_array_cast(A&& a) -// ->decltype(static_array_cast(a))))>(std::forward(a))){ -// return static_array_cast(a))))>(std::forward(a));} - - template auto raw_array_cast(A&& a) - ->decltype(std::forward(a).template static_array_cast(a))))>()){ - return std::forward(a).template static_array_cast(a))))>();} - - namespace managed{ - template - using allocator = multi::memory::cuda::managed::allocator; - - template using ptr = multi::memory::cuda::managed::ptr; - - template - using array = multi::array>; - - template - using array_ref = multi::array>; - - template - using static_array = multi::array>; - } - -} - -/* -auto copy(const double* first, const double* last, boost::multi::array_iterator, double&> d_first){ - return copy( - boost::multi::array_iterator(first), - boost::multi::array_iterator(last), - d_first - ); -}*/ - -}} -#endif - -#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA adaptor" -#define BOOST_TEST_DYN_LINK -#include -#include - -#include -#include - -template -__attribute__((always_inline)) inline void DoNotOptimize(const T &value) { - asm volatile("" : "+m"(const_cast(value))); -} - -struct watch : private std::chrono::high_resolution_clock{ - std::string label_; time_point start_; - watch(std::string label ="") : label_{label}, start_{now()}{} - ~watch(){ - std::cerr<< label_<<": "<< std::chrono::duration(now() - start_).count() <<" sec"< A(4, 99.); - cuda::array Agpu{A}; - BOOST_REQUIRE( extensions(A) == extensions(Agpu) ); - BOOST_REQUIRE( Agpu == A ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_copy_1d){ - multi::array A(4, 99.); - cuda::array Agpu(4); - BOOST_REQUIRE( extensions(A) == extensions(Agpu) ); - Agpu({0, 4}) = A({0, 4}); - BOOST_REQUIRE( Agpu == A ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_construct_2d){ - multi::array A({4, 6}, 99.); - cuda::array Agpu{A}; - BOOST_REQUIRE( extensions(A) == extensions(Agpu) ); - BOOST_REQUIRE( Agpu == A ); - - A[1][1] = Agpu[1][1]; -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_copy_2d){ - multi::array A({4, 6}); std::iota(A.data_elements(), A.data_elements() + A.num_elements(), 1.); - cuda::array Agpu({4, 6}, 99.); - BOOST_REQUIRE( extensions(A) == extensions(Agpu) ); - Agpu({0, 4}, {1, 6}) = A({0, 4}, {1, 6}); - BOOST_REQUIRE( Agpu != A ); - Agpu = A; - BOOST_REQUIRE( Agpu == A ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_1d_initializer_list){ - cuda::array Bgpu = {1., 2., 3., 4.}; - BOOST_REQUIRE( Bgpu[1] == 2. ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_2d_initializer_list){ - cuda::array Bgpu = { - {1., 2., 3., 4.}, - {5., 6., 7., 8.}, - {9., 10., 11., 12.}, - }; - BOOST_REQUIRE( size(Bgpu) == 3 ); - BOOST_REQUIRE( Bgpu[1][1] == 6. ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_2d_initializer_list_bis){ - multi::array A({3, 4}); std::iota(A.data_elements(), A.data_elements() + A.num_elements(), 1.); - - cuda::array Agpu({3, 4}, 99.); - BOOST_REQUIRE( extensions(A) == extensions(Agpu) ); - Agpu({0, 3}, {1, 4}) = A({0, 3}, {1, 4}); - BOOST_REQUIRE( Agpu != A ); - Agpu = A; - BOOST_REQUIRE( Agpu == A ); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_copy_vs_move){ - cuda::array Agpu({30, 100, 100, 100}, 99.); - [&, _ = watch{utf::current_test_case().full_name()+" COPY"}]{ - cuda::array Agpu_cpy = Agpu; - BOOST_REQUIRE( &Agpu_cpy[1][2][3][4] != &Agpu[1][2][3][4] ); - BOOST_REQUIRE( Agpu_cpy[1][2][3][4] == Agpu[1][2][3][4] ); - }(); - [&, _ = watch{utf::current_test_case().full_name()+" MOVE"}]{ - cuda::array Agpu_mov = std::move(Agpu); - BOOST_REQUIRE( Agpu.empty() ); - BOOST_REQUIRE( Agpu_mov.size() == 30 ); - }(); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_copy_vs_move_complex){ - using complex = std::complex; - cuda::array Agpu({30, 100, 100, 100}, 99.); - [&, _ = watch{utf::current_test_case().full_name()+" COPY"}]{ - cuda::array Agpu_cpy = Agpu; - BOOST_REQUIRE( &Agpu_cpy[1][2][3][4] != &Agpu[1][2][3][4] ); - BOOST_REQUIRE( Agpu_cpy[1][2][3][4] == Agpu[1][2][3][4] ); - }(); - [&, _ = watch{utf::current_test_case().full_name()+" MOVE"}]{ - cuda::array Agpu_mov = std::move(Agpu); - BOOST_REQUIRE( Agpu.empty() ); - BOOST_REQUIRE( Agpu_mov.size() == 30 ); - }(); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_managed_double){ - cuda::managed::array A({2,3,4,5}); - cuda::managed::array B({2,3,4,5}, 0.); - cuda::managed::array C({2,3,4,5}, 5.); -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda_managed_ai3){ - using ai3 = std::array; - cuda::managed::array A({2,3,4,5}); // default initialize elements - cuda::managed::array B({2,3,4,5}, ai3{} ); // value initialize elements - cuda::managed::array C({2,3,4,5}, ai3{11, 22, 33} ); // value initialize elements -} - -BOOST_AUTO_TEST_CASE(multi_adaptor_cuda_decay){ - cuda::array A = { - {1., 2., 3., 4.}, - {5., 6., 7., 8.}, - {1., 2., 3., 4.} - }; - cuda::array A1 = A[1]; - -// cuda::array A = { -// {1. + 2.*I, 2. + 3.*I, 3. + 4.*I, 4. + 5.*I}, -// {5. + 2.*I, 6. + 3.*I, 7. + 4.*I, 8. + 5.*I}, -// {1. + 1.*I, 2. + 2.*I, 3. + 3.*I, 4. + 4.*I} -// }; -// cuda::array A({4, 5}, 99.); - cuda::array Agpu = A; - -} - -BOOST_AUTO_TEST_CASE(multi_adaptors_cuda){ - - multi::array A({4, 5}, 99.); - cuda::array Agpu = A; - assert( Agpu == A ); - - cuda::managed::array Amng = A; - assert( Amng == Agpu ); - - cuda::array_ref Rgpu(data_elements(Agpu), extensions(Agpu)); - - {std::allocator a = get_allocator(A);} - - { - cuda::ptr p; - using multi::get_allocator; - cuda::allocator a = get_allocator(p); (void)a; - } - { - cuda::managed::ptr p; - using multi::get_allocator; - cuda::managed::allocator a = get_allocator(p); (void)a; - } - { - double* p = nullptr; - using multi::get_allocator; - std::allocator a = get_allocator(p); (void)a; - } - { - multi::array arr; - std::allocator a = get_allocator(arr); - } - { - cuda::array arr; - cuda::allocator a = get_allocator(arr); (void)a; - } - { -// cuda::array arr = 45.; -// BOOST_REQUIRE( arr() == 45. ); - } - { -// cuda::managed::array arr = 45.; -// BOOST_REQUIRE( arr() == 45. ); - } - { - cuda::managed::array arr = {1.2, 3.4, 4.5}; - } - { - using complex = std::complex; - cuda::managed::array a({1000, 1000}, 99.); - BOOST_REQUIRE( size(a) == 1000 ); - cuda::managed::array b; - b = std::move(a); - BOOST_REQUIRE( size(b) == 1000 ); - BOOST_REQUIRE( size(a) == 0 ); - } -} -#endif -#endif -#endif - diff --git a/include/multi/adaptors/cuda/.idea/.gitignore b/include/multi/adaptors/cuda/.idea/.gitignore deleted file mode 100644 index 73f69e095..000000000 --- a/include/multi/adaptors/cuda/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/include/multi/adaptors/cuda/.idea/cuda.iml b/include/multi/adaptors/cuda/.idea/cuda.iml deleted file mode 100644 index bc2cd8740..000000000 --- a/include/multi/adaptors/cuda/.idea/cuda.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/include/multi/adaptors/cuda/.idea/modules.xml b/include/multi/adaptors/cuda/.idea/modules.xml deleted file mode 100644 index 7305fa097..000000000 --- a/include/multi/adaptors/cuda/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/include/multi/adaptors/cuda/.idea/vcs.xml b/include/multi/adaptors/cuda/.idea/vcs.xml deleted file mode 100644 index b2bdec2d7..000000000 --- a/include/multi/adaptors/cuda/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/include/multi/adaptors/cuda/algorithms/copy.hpp b/include/multi/adaptors/cuda/algorithms/copy.hpp deleted file mode 100644 index e4e3cfff4..000000000 --- a/include/multi/adaptors/cuda/algorithms/copy.hpp +++ /dev/null @@ -1,235 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS//-*-indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4;-*- -nvcc -D_TEST_MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY -x cu $0 -o $0x -lboost_unit_test_framework -lboost_timer&&$0x&& -clang++ -D_TEST_MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY -x cuda --cuda-gpu-arch=sm_61 -std=c++14 $0 -o $0x -lcudart -lboost_unit_test_framework -lboost_timer&&$0x&& -rm $0x; exit -#endif - -#ifndef MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY_HPP -#define MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY_HPP - -#include -//#include - -#include "../../../adaptors/cuda.hpp" -//#include "../algorithms/for_each.hpp" - -//#include "/home/correaa/prj/alf/boost/iterator/zipper.hpp" - -#ifndef HD -#if defined(__CUDACC__) -#define HD __host__ __device__ -#else -#define HD -#endif -#endif - -namespace boost{ -namespace multi{namespace cuda{ - -#if 0 -template{}> > -array_iterator copy( - array_iterator> f, - array_iterator> l, - array_iterator d -){ - assert(0); - assert(f.stride() == l.stride()); static_assert(sizeof(From) == sizeof(To), "!"); - auto n = std::distance(f, l); - if(f.stride()==1 and d.stride()==1){ - auto s = cudaMemcpy(d.data(), raw_pointer_cast(f.data()), n*sizeof(To), cudaMemcpyDeviceToHost); assert( s == cudaSuccess ); - }else{ - auto s = cudaMemcpy2D(d.data(), d.stride()*sizeof(To), raw_pointer_cast(f.data()), f.stride()*sizeof(To), sizeof(To), n, cudaMemcpyDeviceToHost); - assert( s == cudaSuccess ); - } - return d + n; -} - -template{}> > -array_iterator copy( - array_iterator> f, - array_iterator> l, - array_iterator > d -){ - assert(0); - assert(f.stride() == l.stride()); static_assert(sizeof(From) == sizeof(To), "!"); - auto n = std::distance(f, l); - if(f.stride()==1 and d.stride()==1){ - auto s = cudaMemcpy(raw_pointer_cast(d.data()), raw_pointer_cast(f.data()), n*sizeof(To), cudaMemcpyDeviceToHost); assert( s == cudaSuccess ); - }else{ - auto s = cudaMemcpy2D(raw_pointer_cast(d.data()), d.stride()*sizeof(To), raw_pointer_cast(f.data()), f.stride()*sizeof(To), sizeof(To), n, cudaMemcpyDeviceToDevice); - assert( s == cudaSuccess ); - } - return d + n; -} -#endif - -}} -} - - -#ifdef _TEST_MULTI_ADAPTORS_CUDA_ALGORITHMS_COPY -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA copy" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../../adaptors/cuda.hpp" - -#include -#include - -#include - -#if __cpp_lib_parallel_algorithm >= 201603 -#include -#endif - -namespace multi = boost::multi; -namespace cuda = multi::memory::cuda; - -template __device__ void WHAT(T&&) = delete; -template __device__ void WHAT(int) = delete; - -template T&& what(T&&) = delete; - -BOOST_AUTO_TEST_CASE(copy_by_iterator){ - auto const A_cpu = []{ - multi::array r({198, 23}); - std::generate(r.data_elements(), r.data_elements()+r.num_elements(), &std::rand); - return r; - }(); - multi::cuda::array A = A_cpu; - - multi::cuda::array B(extensions(A)); - B() = A(); -// BOOST_REQUIRE( A[13] == B[13] ); -} - -BOOST_AUTO_TEST_CASE(copy_by_pointer){ - auto const A_cpu = []{ - multi::array r({198, 23}); - std::generate(r.data_elements(), r.data_elements()+r.num_elements(), &std::rand); - return r; - }(); - multi::cuda::array A = A_cpu; - - multi::cuda::array B(extensions(A)); - B = A; -// BOOST_REQUIRE( A[13] == B[13] ); -} - - -BOOST_AUTO_TEST_CASE(cuda_copy){ - - multi::cuda::array A(1<<27); CUDA_SLOW( A[10] = 99. ); - multi::cuda::array B(size(A)); - - { - boost::timer::auto_cpu_timer t{"thrust copy_n cuda::ptr %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - thrust::copy_n(thrust::device, A.data_elements(), A.num_elements(), B.data_elements()); - } - { - boost::timer::auto_cpu_timer t{"cuda copy_n cuda::ptr copy_n %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - copy_n(A.data_elements(), A.num_elements(), B.data_elements()); - } - { - boost::timer::auto_cpu_timer t{"cuda copy_n cuda::ptr copy_n %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - multi::adl::copy_n(A.data_elements(), A.num_elements(), B.data_elements()); - } -#if 0 - { - boost::timer::auto_cpu_timer t{"cuda ptr copy_n %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - /*multi::cuda::*/copy_n(A.data_elements(), A.num_elements(), B.data_elements()); - } - { - boost::timer::auto_cpu_timer t{"indirect cuda ptr copy_n %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - B = A; - } - { - boost::timer::auto_cpu_timer t{"indirect cuda ptr uninitialized_copy_n %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - multi::cuda::array C = A; - BOOST_REQUIRE( CUDA_SLOW( C[10] == 99. ) ); - } - { - boost::timer::auto_cpu_timer t{"indirect cuda ptr uninitialized_copy_n %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - multi::cuda::array C = A;//(); - BOOST_REQUIRE( CUDA_SLOW( C[10] == 99. ) ); - } - BOOST_REQUIRE( CUDA_SLOW( B[10] == 99. ) ); - CUDA_SLOW( B[10] = 10. ); - { - boost::timer::auto_cpu_timer t{"thrust copy_n %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - thrust::copy_n(thrust::device, begin(A), size(A), begin(B)); - } - BOOST_REQUIRE( CUDA_SLOW( B[10] == 99. ) ); -#endif - -/* multi::cuda::for_each_n( - boost::iterators::zip(begin(A), begin(B)), - size(A), - []__device__(auto&& e){ - std::get<1>(e) = std::get<0>(e); - printf( "**** %f %f\n", static_cast(std::get<0>(e)), static_cast(std::get<1>(e)) ); - } - );*/ - -// auto l = -// BOOST_REQUIRE( l == end(B) ); -// std::cout << B[8] << std::endl; -// multi::cuda::array A(10, 99.); -// BOOST_REQUIRE( CUDA_SLOW( A[5] == 99. ) ); -// int uno = 1.; -// for_each(begin(A), end(A), [uno]__device__(auto&& e){e = uno;}); -// BOOST_REQUIRE( CUDA_SLOW( A[5] == 1. ) ); -} - -#if 0 -BOOST_AUTO_TEST_CASE(cuda_for_each){ - multi::cuda::array A(10, 99.); - BOOST_REQUIRE( CUDA_SLOW( A[5] == 99. ) ); - int uno = 1.; - for_each(begin(A), end(A), [uno]__device__(auto&& e){e = uno;}); - BOOST_REQUIRE( CUDA_SLOW( A[5] == 1. ) ); -} - -BOOST_AUTO_TEST_CASE(cuda_timing){ - multi::cuda::managed::array A(1<<29); //std::cout << A.size()*8 << std::endl; - { - boost::timer::auto_cpu_timer t{"cuda cold %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - multi::cuda::for_each(begin(A), end(A), []__device__(auto&& e){e = 11.;}); - } BOOST_REQUIRE( CUDA_SLOW( A[size(A) - 10] == 11.) ); - { - boost::timer::auto_cpu_timer t{"cuda %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - multi::cuda::for_each(begin(A), end(A), []__device__(auto&& e){e = 22.;}); - } BOOST_REQUIRE( CUDA_SLOW( A[size(A) - 10] == 22.) ); - { - boost::timer::auto_cpu_timer t{"thrust %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - thrust::for_each(thrust::device, begin(A), end(A), []__device__(auto&& e){e = 222.;}); - } BOOST_REQUIRE( CUDA_SLOW( A[size(A) - 10] == 222.) ); - { - std::for_each(begin(A), end(A), [](auto&& e){e = 55.;}); - } BOOST_REQUIRE( CUDA_SLOW( A[size(A) - 10] == 55.) ); -#if __cpp_lib_parallel_algorithm >= 201603 - { - boost::timer::auto_cpu_timer t{"par %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - std::for_each(std::execution::par_unseq, begin(A), end(A), [](auto&& e){e = 33.;}); - } BOOST_REQUIRE( CUDA_SLOW( A[size(A) - 10] == 33.) ); -#endif - { - boost::timer::auto_cpu_timer t{"seq %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - std::for_each(begin(A), end(A), [](auto&& e){e = 55.;}); - } BOOST_REQUIRE( CUDA_SLOW( A[size(A) - 10] == 55.) ); - { - boost::timer::auto_cpu_timer t{"cuda cold %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - multi::cuda::for_each(begin(A), end(A), []__device__(auto&& e){e = 66.;}); - } BOOST_REQUIRE( CUDA_SLOW( A[size(A) - 10] == 66.) ); - { - boost::timer::auto_cpu_timer t{"cuda %ws wall, %us user + %ss system = %ts CPU (%p%)\n"}; - multi::cuda::for_each(begin(A), end(A), []__device__(auto&& e){e = 77.;}); - } BOOST_REQUIRE( CUDA_SLOW( A[size(A) - 10] == 77.) ); -} -#endif - -#endif -#endif - diff --git a/include/multi/adaptors/cuda/cublas.hpp b/include/multi/adaptors/cuda/cublas.hpp deleted file mode 100644 index a972b20ee..000000000 --- a/include/multi/adaptors/cuda/cublas.hpp +++ /dev/null @@ -1,7 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// © Alfredo A. Correa 2021 - -#pragma once - -#include "./cublas/context.hpp" - diff --git a/include/multi/adaptors/cuda/cublas/call.hpp b/include/multi/adaptors/cuda/cublas/call.hpp deleted file mode 100644 index 0b0b28257..000000000 --- a/include/multi/adaptors/cuda/cublas/call.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef MULTI_ADAPTORS_CUDA_CUBLAS_CALL_HPP -#define MULTI_ADAPTORS_CUDA_CUBLAS_CALL_HPP - -#include "../cublas/error.hpp" - -#if defined(__NVCC__) -#include // cudaDeviceSynchronize -#else -#include // cudaDeviceSynchronize -#endif - -#if defined(__NVCC__) -#define hicup(name) cuda##name -#define HICUP(name) CU##name -#else -#define hicup(name) hip##name -#define HICUP(name) HIP##name -#endif - -namespace boost{ -namespace multi::cuda::cublas{ - -template // needs C++17 -void call(Args... args){ - auto e = static_cast(Function(args...)); - if(e != cublas::error::success) { throw std::system_error{e, "cannot call function "+ std::string{__PRETTY_FUNCTION__}}; } -} - -#define CUBLAS_(F) call - -} -} - -#undef hicup -#undef HICUP -#endif - diff --git a/include/multi/adaptors/cuda/cublas/context.hpp b/include/multi/adaptors/cuda/cublas/context.hpp deleted file mode 100644 index e9bf4d4e1..000000000 --- a/include/multi/adaptors/cuda/cublas/context.hpp +++ /dev/null @@ -1,398 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2022 Alfredo A. Correa -#pragma once - -#include -#include - -#include -#include - -#if defined(__NVCC__) -#include // for thrust::cuda::pointer -#else -#include // for thrust::cuda::pointer -#include -#endif - -#include - -#if defined(__NVCC__) -#define hicup(name) cuda##name -#define hicu(name) cu##name -#define HICU(name) CU##name -#else -#define hicup(name) hip##name -#define hicu(name) hip##name -#define HICU(name) HIP##name -#endif - -namespace boost { -namespace multi::cuda::cublas { - -class operation { - hicu(blasOperation_t) impl_; - - public: - explicit operation(char trans) : impl_{[=]{ - switch(trans) { - case 'N': return HICU(BLAS_OP_N); - case 'T': return HICU(BLAS_OP_T); - case 'C': return HICU(BLAS_OP_C); - default : assert(0); - } - return hicu(blasOperation_t){}; - }()} {} - operator hicu(blasOperation_t)() const{return impl_;} -}; - -class side { - hicu(blasSideMode_t) impl_; - - public: - explicit side(char trans) : impl_{[=] { - switch(trans) { - case 'L': return HICU(BLAS_SIDE_LEFT); - case 'R': return HICU(BLAS_SIDE_RIGHT); - } - assert(0); return hicu(blasSideMode_t){}; - }()} {} - operator hicu(blasSideMode_t)() const {return impl_;} -}; - -class filling { - hicu(blasFillMode_t) impl_; - - public: - explicit filling(char trans) : impl_{[=] { - switch(trans) { - case 'L': return HICU(BLAS_FILL_MODE_LOWER); - case 'U': return HICU(BLAS_FILL_MODE_UPPER); - } - assert(0); return hicu(blasFillMode_t){}; - }()} {} - operator hicu(blasFillMode_t)() const {return impl_;} -}; - -class diagonal { - hicu(blasDiagType_t) impl_; - - public: - explicit diagonal(char trans) : impl_{[=] { - switch(trans) { - case 'N': return HICU(BLAS_DIAG_NON_UNIT); - case 'U': return HICU(BLAS_DIAG_UNIT); - } - assert(0); return hicu(blasDiagType_t){}; - }()} {} - operator hicu(blasDiagType_t)() const {return impl_;} -}; - -using blas::is_s; -using blas::is_d; -using blas::is_c; -using blas::is_z; - -using std::is_assignable; -using std::is_assignable_v; -using std::is_convertible_v; - -enum type {S, D, C, Z}; - -template -constexpr auto type_of(T const& = {}) -> cublas::type { - static_assert(is_s{} or is_d{} or is_c{} or is_z{}); - if(is_s{}) {return S;} - else if(is_d{}) {return D;} - else if(is_c{}) {return C;} - else if(is_z{}) {return Z;} -} - -#ifdef __NVCC__ - using Complex = cuComplex; - using DoubleComplex = cuDoubleComplex; -#else - using Complex = hipblasComplex; - using DoubleComplex = hipblasDoubleComplex; -#endif - -template -constexpr auto data_cast(T* p) { - if constexpr(is_s{}) {return reinterpret_cast(p);} - else if constexpr(is_d{}) {return reinterpret_cast(p);} - else if constexpr(is_c{}) {return reinterpret_cast(p);} - else if constexpr(is_z{}) {return reinterpret_cast(p);} -} - -template -constexpr auto data_cast(T const* p) { - if constexpr(is_s{}) {return reinterpret_cast(p);} - else if constexpr(is_d{}) {return reinterpret_cast(p);} - else if constexpr(is_c{}) {return reinterpret_cast(p);} - else if constexpr(is_z{}) {return reinterpret_cast(p);} -} - -// template auto const cublas_gemv = std::enable_if_t{}; - -// template<> auto const cublas_gemv = hicu(blasSgemv); -// template<> auto const cublas_gemv = hicu(blasDgemv); -// template<> auto const cublas_gemv = hicu(blasCgemv); -// template<> auto const cublas_gemv = hicu(blasZgemv); - -#define DECLRETURN(ExpR) -> decltype(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing -#define JUSTRETURN(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing - -// template struct cublas { -// static constexpr auto gemv = cublas_gemv; -// }; - -class context : private std::unique_ptr::element_type, decltype(&hicu(blasDestroy))> { - using pimpl_t = std::unique_ptr::element_type, decltype(&hicu(blasDestroy))>; - hicup(Stream_t) stream() const {hicup(Stream_t) streamId; cuda::cublas::call(this->get(), &streamId); return streamId;} - template - void sync_call(Args... args) const { - call(const_cast(this)->get(), args...); - this->synchronize(); - } - template - void sync_call(Args... args) { - call(this->get(), args...); - this->synchronize(); - } - - public: - using pimpl_t::get; - static context& get_instance() { - thread_local context ctxt; - return ctxt; - }; - context() : pimpl_t{[] {hicu(blasHandle_t) h; hicu(blasCreate)(&h); return h;}(), &hicu(blasDestroy)} {} - using ssize_t = int; - // static int version() {int ret; cuda::cublas::call(nullptr, &ret); return ret;} // no hipblasGetVersion available - void synchronize() const { - // cudaError_t e = cudaDeviceSynchronize(); - auto s = stream(); - if(s != 0) {throw std::logic_error("CUBLAS stream expected to be zero");} - hicup(Error_t) e = hicup(StreamSynchronize)(s); - if(e != hicup(Success)) {throw std::runtime_error{"cannot synchronize stream in cublas context"};} - } - - template< - class XP, class X = typename std::pointer_traits::element_type, - class YP, class Y = typename std::pointer_traits::element_type, - class = decltype(std::swap(std::declval(), std::declval())), - std::enable_if_t>, int> = 0 - > - void swap(ssize_t n, XP x, ssize_t incx, YP y, ssize_t incy) const { - if(is_s{}) {sync_call(n, (float *)raw_pointer_cast(x), incx, (float *)raw_pointer_cast(y), incy);} - if(is_d{}) {sync_call(n, (double *)raw_pointer_cast(x), incx, (double *)raw_pointer_cast(y), incy);} - if(is_c{}) {sync_call(n, (Complex *)raw_pointer_cast(x), incx, (Complex *)raw_pointer_cast(y), incy);} - if(is_z{}) {sync_call(n, (DoubleComplex*)raw_pointer_cast(x), incx, (DoubleComplex*)raw_pointer_cast(y), incy);} - } - - template< - class XP, class X = typename std::pointer_traits::element_type, - class YP, class Y = typename std::pointer_traits::element_type, - class = decltype(std::declval() = std::declval()), - std::enable_if_t>, int> = 0 - > - void copy(ssize_t n, XP x, ssize_t incx, YP y, ssize_t incy) const { - if(is_s{}) {sync_call(n, (float const*)raw_pointer_cast(x), incx, (float *)raw_pointer_cast(y), incy);} - if(is_d{}) {sync_call(n, (double const*)raw_pointer_cast(x), incx, (double *)raw_pointer_cast(y), incy);} - if(is_c{}) {sync_call(n, (Complex const*)raw_pointer_cast(x), incx, (Complex *)raw_pointer_cast(y), incy);} - if(is_z{}) {sync_call(n, (DoubleComplex const*)raw_pointer_cast(x), incx, (DoubleComplex*)raw_pointer_cast(y), incy);} - } - - template::element_type, - class = decltype(std::declval() *= ALPHA{}), - std::enable_if_t>, int> = 0 - > - void scal(ssize_t n, ALPHA const& alpha, XP x, ssize_t incx) const { - if(is_s{}) {sync_call(n, (float const*)alpha, (float *)::thrust::raw_pointer_cast(x), incx);} - if(is_d{}) {sync_call(n, (double const*)alpha, (double *)::thrust::raw_pointer_cast(x), incx);} - if(is_c{}) {sync_call(n, (Complex const*)alpha, (Complex *)::thrust::raw_pointer_cast(x), incx);} - if(is_z{}) {sync_call(n, (DoubleComplex const*)alpha, (DoubleComplex*)::thrust::raw_pointer_cast(x), incx);} - } - - template::element_type, class YP, class Y = typename std::pointer_traits::element_type, - typename = decltype(std::declval() = ALPHA{}*X{} + Y{}), - std::enable_if_t> and std::is_convertible_v>, int> = 0 - > - void axpy(ssize_t n, ALPHA const* alpha, XP x, ssize_t incx, YP y, ssize_t incy) { - if(is_d{}) {sync_call(n, (double const*)alpha, (double const*)raw_pointer_cast(x), incx, (double *)raw_pointer_cast(y), incy);} - if(is_z{}) {sync_call(n, (DoubleComplex const*)alpha, (DoubleComplex const*)raw_pointer_cast(x), incx, (DoubleComplex*)raw_pointer_cast(y), incy);} - } - - template::element_type, class XXP, class XX = typename std::pointer_traits::element_type, class BETA, class YYP, class YY = typename std::pointer_traits::element_type, - typename = decltype(std::declval() = ALPHA{}*(AA{}*XX{} + AA{}*XX{})), - std::enable_if_t> and std::is_convertible_v> and std::is_convertible_v>, int> = 0 - > - auto gemv(char transA, ssize_t m, ssize_t n, ALPHA const* alpha, AAP aa, ssize_t lda, XXP xx, ssize_t incx, BETA const* beta, YYP yy, ssize_t incy) { - if(is_d{}) {sync_call(operation{transA}, m, n, (double const*)alpha, (double const*)::thrust::raw_pointer_cast(aa), lda, (double const*)::thrust::raw_pointer_cast(xx), incx, (double const*)beta, (double *)::thrust::raw_pointer_cast(yy), incy);} - if(is_z{}) {sync_call(operation{transA}, m, n, (DoubleComplex const*)alpha, (DoubleComplex const*)::thrust::raw_pointer_cast(aa), lda, (DoubleComplex const*)::thrust::raw_pointer_cast(xx), incx, (DoubleComplex const*)beta, (DoubleComplex*)::thrust::raw_pointer_cast(yy), incy);} - } - - template::element_type, class BBP, class BB = typename std::pointer_traits::element_type, class BETA, class CCP, class CC = typename std::pointer_traits::element_type, - typename = decltype(std::declval() = ALPHA{}*(AA{}*BB{} + AA{}*BB{})), - class = std::enable_if_t> and std::is_convertible_v> and std::is_convertible_v>> - > - void gemm(char transA, char transB, ssize_t m, ssize_t n, ssize_t k, ALPHA const* alpha, AAP aa, ssize_t lda, BBP bb, ssize_t ldb, BETA const* beta, CCP cc, ssize_t ldc) { - MULTI_MARK_SCOPE("cublasXgemm"); - if(is_d{}) {sync_call(cuda::cublas::operation{transA}, cuda::cublas::operation{transB}, m, n, k, (double const*)alpha, (double const*)::thrust::raw_pointer_cast(aa), lda, (double const*)::thrust::raw_pointer_cast(bb), ldb, (double const*)beta, (double *)::thrust::raw_pointer_cast(cc), ldc);} - if(is_z{}) {sync_call(cuda::cublas::operation{transA}, cuda::cublas::operation{transB}, m, n, k, (DoubleComplex const*)alpha, (DoubleComplex const*)::thrust::raw_pointer_cast(aa), lda, (DoubleComplex const*)::thrust::raw_pointer_cast(bb), ldb, (DoubleComplex const*)beta, (DoubleComplex*)::thrust::raw_pointer_cast(cc), ldc);} - } - - template::element_type, class BBP, class BB = typename std::pointer_traits::element_type, - std::enable_if_t< - is_z{} and is_z{} and is_assignable{} and is_assignable{} and - is_convertible_v> and is_convertible_v> - ,int> =0 - > - void trsm(char side, char ul, char transA, char diag, ssize_t m, ssize_t n, ALPHA alpha, AAP aa, ssize_t lda, BBP bb, ssize_t ldb) { - sync_call(cuda::cublas::side{side}, cuda::cublas::filling{ul}, cuda::cublas::operation{transA}, cuda::cublas::diagonal{diag}, m, n, (DoubleComplex const*)&alpha, (DoubleComplex*)raw_pointer_cast(aa), lda, (DoubleComplex*)raw_pointer_cast(bb), ldb); - } - - template::element_type, class BBP, class BB = typename std::pointer_traits::element_type, - std::enable_if_t< - is_d{} and is_d{} and is_assignable{} and is_assignable{} and - is_convertible_v> and is_convertible_v> - ,int> =0 - > - void trsm(char side, char ul, char transA, char diag, ssize_t m, ssize_t n, ALPHA alpha, AAP aa, ssize_t lda, BBP bb, ssize_t ldb) { - sync_call( - cuda::cublas::side{side}, - cuda::cublas::filling{ul}, - cuda::cublas::operation{transA}, - cuda::cublas::diagonal{diag}, - m, n, (double const*)&alpha, (double const*)raw_pointer_cast(aa), lda, (double*)raw_pointer_cast(bb), ldb - ); - } - - template< - class XXP, class XX = typename std::pointer_traits::element_type, - class YYP, class YY = typename std::pointer_traits::element_type, - class RRP, class RR = typename std::pointer_traits::element_type, - std::enable_if_t< - is_d{} and is_d{} and is_d{} and is_assignable{} and - is_convertible_v> and is_convertible_v> - and is_convertible_v - , int> =0 - > - void dot(int n, XXP xx, int incx, YYP yy, int incy, RRP rr) { - hicu(blasPointerMode_t) mode; - auto s = hicu(blasGetPointerMode)(get(), &mode); assert( s == HICU(BLAS_STATUS_SUCCESS) ); - assert( mode == HICU(BLAS_POINTER_MODE_HOST) ); - sync_call(n, ::thrust::raw_pointer_cast(xx), incx, ::thrust::raw_pointer_cast(yy), incy, rr); - } - - template< - class XXP, class XX = typename std::pointer_traits::element_type, - class YYP, class YY = typename std::pointer_traits::element_type, - class RRP, class RR = typename std::pointer_traits::element_type, - std::enable_if_t< - is_z{} and is_z{} and is_z{} and is_assignable{} and - is_convertible_v> and is_convertible_v> - and (is_convertible_v> or is_convertible_v) - , int> =0 - > - void dotc(int n, XXP xx, int incx, YYP yy, int incy, RRP rr) { - hicu(blasPointerMode_t) mode; - auto s = hicu(blasGetPointerMode)(get(), &mode); assert( s == HICU(BLAS_STATUS_SUCCESS) ); - assert( mode == HICU(BLAS_POINTER_MODE_HOST) ); - // cublasSetPointerMode(get(), CUBLAS_POINTER_MODE_DEVICE); - if constexpr(is_convertible_v>) { - sync_call(n, (DoubleComplex const*)::thrust::raw_pointer_cast(xx), incx, (DoubleComplex const*)::thrust::raw_pointer_cast(yy), incy, (DoubleComplex*)::thrust::raw_pointer_cast(rr) ); - } else { - sync_call(n, (DoubleComplex const*)::thrust::raw_pointer_cast(xx), incx, (DoubleComplex const*)::thrust::raw_pointer_cast(yy), incy, (DoubleComplex*)rr); - } - } - - template< - class XXP, class XX = typename std::pointer_traits::element_type, - class RRP, class RR = typename std::pointer_traits::element_type, - std::enable_if_t< - is_z{} and is_d{} and is_assignable{} and - is_convertible_v> and (is_convertible_v> or is_convertible_v) - , int> =0 - > - void asum(int n, XXP xx, int incx, RRP rr) { - if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_DEVICE));} - if constexpr(is_convertible_v>) { - sync_call(n, (DoubleComplex const*)::thrust::raw_pointer_cast(xx), incx, (double*)::thrust::raw_pointer_cast(rr) ); - } else { - sync_call(n, (DoubleComplex const*)::thrust::raw_pointer_cast(xx), incx, (double*) rr ); - } - if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_HOST));} - } - - template< - class XXP, class XX = typename std::pointer_traits::element_type, - class RRP, class RR = typename std::pointer_traits::element_type, - std::enable_if_t< - is_z{} and is_d{} and is_assignable{} and - is_convertible_v> and (is_convertible_v> or is_convertible_v) - , int> =0 - > - void nrm2(int n, XXP xx, int incx, RRP rr) { - if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_DEVICE));} - if constexpr(is_convertible_v>) { - sync_call(n, (DoubleComplex const*)::thrust::raw_pointer_cast(xx), incx, (double*)::thrust::raw_pointer_cast(rr) ); - } else { - sync_call(n, (DoubleComplex const*)::thrust::raw_pointer_cast(xx), incx, (double*) rr ); - } - if(is_convertible_v>) {hicu(blasSetPointerMode)(get(), HICU(BLAS_POINTER_MODE_HOST));} - } - - template< - class XXP, class XX = typename std::pointer_traits::element_type, - class YYP, class YY = typename std::pointer_traits::element_type, - class RRP, class RR = typename std::pointer_traits::element_type, - std::enable_if_t< - is_z{} and is_z{} and is_z{} and is_assignable{} and - is_convertible_v> and is_convertible_v> - and (is_convertible_v> or is_convertible_v) - , int> =0 - > - void dotu(int n, XXP xx, int incx, YYP yy, int incy, RRP rr) { - hicu(blasPointerMode_t) mode; - auto s = hicu(blasGetPointerMode)(get(), &mode); assert( s == HICU(BLAS_STATUS_SUCCESS) ); - assert( mode == HICU(BLAS_POINTER_MODE_HOST) ); - // cublasSetPointerMode(get(), CUBLAS_POINTER_MODE_DEVICE); - if constexpr(is_convertible_v>) { - sync_call(n, reinterpret_cast(::thrust::raw_pointer_cast(xx)), incx, reinterpret_cast(::thrust::raw_pointer_cast(yy)), incy, reinterpret_cast(::thrust::raw_pointer_cast(rr)) ); - } else { - sync_call(n, reinterpret_cast(::thrust::raw_pointer_cast(xx)), incx, reinterpret_cast(::thrust::raw_pointer_cast(yy)), incy, reinterpret_cast(rr)); - } - // cublasSetPointerMode(get(), CUBLAS_POINTER_MODE_HOST); - } -}; - -} // end namespace multi::cuda::cublas -} // end namespace boost - -namespace boost::multi::blas { - - template<> struct is_context : std::true_type {}; - template<> struct is_context : std::true_type {}; - - template::element_type, std::enable_if_t>{}, int> =0> - boost::multi::cuda::cublas::context* default_context_of(Ptr const&) { - namespace multi = boost::multi; - return &multi::cuda::cublas::context::get_instance(); - } - - template - boost::multi::cuda::cublas::context* - #if defined(__NVCC__) - default_context_of(::thrust::pointer const&) { - #else - default_context_of(::thrust::pointer const&) { - #endif - namespace multi = boost::multi; - return &multi::cuda::cublas::context::get_instance(); - } -} - -#undef hicup -#undef hicu -#undef HICU diff --git a/include/multi/adaptors/cuda/cublas/test/CMakeLists.txt b/include/multi/adaptors/cuda/cublas/test/CMakeLists.txt deleted file mode 100644 index 99377b81a..000000000 --- a/include/multi/adaptors/cuda/cublas/test/CMakeLists.txt +++ /dev/null @@ -1,92 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -set(CMAKE_VERBOSE_MAKEFILE ON) - -find_package(Boost REQUIRED COMPONENTS unit_test_framework) - -find_package(BLAS REQUIRED) -find_path( - BLAS_INCLUDE_DIRS - cblas.h - /usr/include - /usr/local/include - $ENV{BLAS_HOME}/include -) - -link_libraries(${BLAS_LIBRARIES}) -include_directories(${TEST_EXE} PRIVATE ${BLAS_INCLUDE_DIRS}) - -# set(CMAKE_CXX_STANDARD 17) -# set(CMAKE_CXX_STANDARD_REQUIRED ON) -# set(CMAKE_CXX_EXTENSIONS OFF) - -if(ENABLE_HIP) - enable_language(HIP) - find_package(hipBLAS REQUIRED) -endif() - -if(ENABLE_CUDA OR DEFINED CXXCUDA) - enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr -Xcudafe \"--display_error_number\"") - find_package(CUDA) -endif() - -enable_testing() -include(CTest) - -include_directories(${CMAKE_BINARY_DIR}) - -if(ENABLE_HIP) - add_executable(all.hip.x all.hip) - #set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE HIP) - target_link_libraries(all.hip.x PRIVATE multi roc::hipblas Boost::unit_test_framework) - add_test(NAME all.hip.x COMMAND ./all.hip.x) -endif() - -if(ENABLE_CUDA) -find_package(CUDA REQUIRED) - -# file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) -set(TEST_SRCS - all.cu - axpy.cu - dot.cu - gemm.cu - gemv.cu - # herk.cu - scal.cu - trsm.cu -) - -foreach(TEST_FILE ${TEST_SRCS}) - if(ENABLE_CUDA OR DEFINED CXXCUDA) - set(TEST_EXE "${TEST_FILE}.x") - add_executable(${TEST_EXE} ${TEST_FILE}) - - # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") - target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) - - target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) - target_include_directories(${TEST_EXE} PRIVATE ${CUDA_INCLUDE_DIRS}) - - target_link_libraries(${TEST_EXE} PRIVATE Boost::unit_test_framework) - - target_link_libraries(${TEST_EXE} PRIVATE ${CUDA_LIBRARIES}) - target_link_libraries(${TEST_EXE} PRIVATE -lcublas) - - target_include_directories(${TEST_EXE} PRIVATE /opt/nvidia/hpc_sdk/Linux_x86_64/22.3/math_libs/include) - - # if(NOT ENABLE_CUDA) target_compile_options (${TEST_EXE} PRIVATE $<$: -Werror -Wall -Wextra -fno-common -Wpedantic -Wformat-truncation -fstack-usage>#-Wconversion - # $<$,$>: - # -Werror -Wall -Wextra -fno-common -Wpedantic -Wmove> $<$: -Werror -Wall -Wextra -fno-common -wd161 -diag-disable=remark -Warray-bounds -Wchar-subscripts -Wcomment -Wenum-compare -Wformat -Wuninitialized - # -Wmaybe-uninitialized -Wmain -Wnarrowing -Wnonnull -Wparentheses -Wpointer-sign -Wreorder -Wno-return-type -Wsign-compare -Wsequence-point -Wtrigraphs -Wunused-function -Wunused-but-set-variable -Wunused-variable -Wwrite-strings - # -Werror -diag-error:3846 > $<$: /W4>) endif() - - set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) - target_compile_options(${TEST_EXE} PRIVATE -extended-lambda) - - add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) - endif() -endforeach() -endif() \ No newline at end of file diff --git a/include/multi/adaptors/cuda/cublas/test/all.cu b/include/multi/adaptors/cuda/cublas/test/all.cu deleted file mode 100644 index 979eca7e0..000000000 --- a/include/multi/adaptors/cuda/cublas/test/all.cu +++ /dev/null @@ -1,2006 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUBLAS all" -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - -namespace multi = boost::multi; - -using complex = thrust::complex; - -template> -auto generate_ABx() { - complex const I{0.0, 1.0}; - multi::array x = { 1.0 + I*0.0, 2.0 + I*0.0, 3.0 + I*0.0, 4.0 + I*0.0}; - - multi::array A = { - { 1.0 + I*0.0, 2.0 + I*0.0, 3.0 + I*0.0, 4.0 + I*0.0}, - { 5.0 + I*0.0, 6.0 + I*0.0, 7.0 + I*0.0, 8.0 + I*0.0}, - { 9.0 + I*0.0, 10.0 + I*0.0, 11.0 + I*0.0, 12.0 + I*0.0}, - {13.0 + I*0.0, 14.0 + I*0.0, 15.0 + I*0.0, 16.0 + I*0.0}, - }; - - multi::array B = { - { 1.0 + I*0.0, 2.0 + I*0.0, 3.0 + I*0.0, 4.0 + I*0.0}, - { 5.0 + I*0.0, 6.0 + I*0.0, 7.0 + I*0.0, 8.0 + I*0.0}, - { 9.0 + I*0.0, 10.0 + I*0.0, 11.0 + I*0.0, 12.0 + I*0.0}, - {13.0 + I*0.0, 14.0 + I*0.0, 15.0 + I*0.0, 16.0 + I*0.0}, - }; - - return std::make_tuple(std::move(x), std::move(A), std::move(B)); -} - -BOOST_AUTO_TEST_CASE(cublas_scal_complex_column) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - { - using T = complex; - auto [x, A, B] = generate_ABx >(); - auto const s = 2.0 + I*3.0; - blas::scal(s, x); // x_i <- s*x_i - - { - auto [x2, A2, B2] = generate_ABx >(); - auto xx = +x2; - blas::scal(s, xx); - BOOST_REQUIRE(xx == x); - } - { - auto [x2, A2, B2] = generate_ABx >(); - using blas::operators::operator*=; - x2 *= s; - BOOST_REQUIRE(x == x2); - } - { - auto [x2, A2, B2] = generate_ABx >(); - thrust::transform(x2.begin(), x2.end(), x2.begin(), [s] __device__ (T& e) {return s*e;}); - - BOOST_REQUIRE(x == x2); - } - { - auto [x2, A2, B2] = generate_ABx >(); - thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); - - BOOST_REQUIRE(x == x2); - } - } -} - -BOOST_AUTO_TEST_CASE(cublas_copy_complex) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { 1.0 + I*8.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - multi::array y = { 1.0 + I*9.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - - blas::copy(x, y); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); - { - thrust::copy(begin(x), end(x), begin(y)); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); - } - { - blas::copy_n(x.begin(), x.size(), y.begin()); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); - } - { - y() = blas::copy(x); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); - } - { - multi::array yy = blas::copy(x); - BOOST_REQUIRE( static_cast(yy[0]) == 1.0 + I*8.0 ); - } - { - y = blas::copy(x); - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*8.0 ); - } - { - { - using blas::operators::operator<<; - y << x; - // BOOST_REQUIRE(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< - } - BOOST_REQUIRE(( static_cast(y[0]) == 1.0 + I*8.0 )); // this can't be used with a free operator<< - } -} - -#if 1 -BOOST_AUTO_TEST_CASE(cublas_swap_complex) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array x = { 1.0 + I*8.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - multi::array y = { 1.0 + I*9.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - - blas::swap(x, y); - BOOST_REQUIRE( static_cast(x[0]) == 1.0 + I*9.0 ); - { - thrust::swap_ranges(begin(x), end(x), begin(y)); - thrust::swap_ranges(begin(x), end(x), begin(y)); - BOOST_REQUIRE( static_cast(x[0]) == 1.0 + I*9.0 ); - } - { - using blas::operator^; - (x^y); - (x^y); - BOOST_REQUIRE( static_cast(x[0]) == 1.0 + I*9.0 ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_asum_complex_column) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { 1.0 + I*8.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - - double res; - blas::asum_n(x.begin(), x.size(), &res); - { - double res2; - res2 = blas::asum(x); - BOOST_REQUIRE( res == res2 ); - } - { - double res2 = blas::asum(x); - BOOST_REQUIRE( res == res2 ); - } - { - auto res2 = std::transform_reduce( - x.begin(), x.end(), double{}, std::plus<>{}, [](T const& e) {return std::abs(e.real()) + std::abs(e.imag());} - ); - BOOST_REQUIRE( res == res2 ); - } - { - auto res2 = thrust::transform_reduce( - x.begin(), x.end(), [] __device__ (T const& e) {return std::abs(e.real()) + std::abs(e.imag());}, - double{}, thrust::plus<>{} - ); - BOOST_REQUIRE( res == res2 ); - } - { - multi::static_array> res2({}, 0.0); - res2.assign( &blas::asum(x) ); - res2 = blas::asum(x); - BOOST_REQUIRE(( res == static_cast>::element_ref>(res2) )); - BOOST_REQUIRE(( res == static_cast(res2) )); - // BOOST_REQUIRE( res == res2 ); - } - { - multi::array> res2 = blas::asum(x); - BOOST_REQUIRE(( res == static_cast>::element_ref>(res2) )); - BOOST_REQUIRE(( res == static_cast(res2) )); - // BOOST_REQUIRE( res == res2 ); - } - { - using blas::operators::operator==; - using blas::operators::operator!=; - BOOST_REQUIRE( x != 0 ); - BOOST_REQUIRE( not (x == 0) ); - } - { - using blas::operators::contains_nan; - BOOST_REQUIRE( not contains_nan(x) ); - } - { - using blas::operators::isfinite; - using blas::operators::isinf; - BOOST_REQUIRE( isfinite(x) ); - BOOST_REQUIRE( not isinf(x) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_asum_complex_nans) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { 1.0 + I*8.0, std::numeric_limits::quiet_NaN() + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - - { - using blas::operators::contains_nan; - BOOST_REQUIRE( contains_nan(x) ); - } - { - using blas::operators::operator==; - using blas::operators::operator!=; - BOOST_REQUIRE( not (x != 0) ); - BOOST_REQUIRE( not (x == 0) ); - } - { - using blas::operators::isfinite; - using blas::operators::isinf; - BOOST_REQUIRE( not isfinite(x) ); - BOOST_REQUIRE( not isinf(x) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_asum_complex_inf) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { 1.0 + I*8.0, std::numeric_limits::infinity() + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - - // double res; - { - using blas::operators::contains_nan; - BOOST_REQUIRE( not contains_nan(x) ); - } - { - using blas::operators::operator==; - using blas::operators::operator!=; - BOOST_REQUIRE( (x != 0) ); - BOOST_REQUIRE( not (x == 0) ); - } - { - using blas::operators::isfinite; - using blas::operators::isinf; - BOOST_REQUIRE( not isfinite(x) ); - BOOST_REQUIRE( isinf(x) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_nrm2_complex_column) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { 1.0 + I*8.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - - double res; - blas::nrm2(x, res); - { - double res2; - res2 = blas::nrm2(x); - BOOST_REQUIRE( res == res2 ); - } - { - auto res2 = +blas::nrm2(x); - BOOST_REQUIRE( res == res2 ); - } - { - auto res2 = sqrt(thrust::transform_reduce( - x.begin(), x.end(), [] __device__ (T const& e) {return thrust::norm(e);}, - double{}, thrust::plus<>{} - )); - BOOST_REQUIRE( res == res2 ); - } - { - multi::array> res2 = blas::nrm2(x); - BOOST_REQUIRE(( res == static_cast(res2) )); - } -} - -BOOST_AUTO_TEST_CASE(cublas_dot_complex_column) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { 1.0 + I*8.0, 2.0 + I*6.0, 3.0 + I*5.0, 4.0 + I*3.0}; - multi::array const y = { 1.0 + I*2.0, 2.0 + I*3.0, 3.0 + I*5.0, 4.0 + I*7.0}; - - { - T res; - blas::dot(x, y, res); - { - complex res2; - res2 = blas::dot(x, y); - BOOST_REQUIRE(res == res2); - } - { - multi::array res2(complex{1.0, 0.0}); - res2 = blas::dot(x, y); - BOOST_REQUIRE( static_cast(res2) == res ); - } - { - using blas::operators::operator,; - auto res2 = +(x, y); - BOOST_REQUIRE(res == res2); - } - { - auto res2 = +blas::dot(x, y); - BOOST_REQUIRE(res == res2); - } - { - // auto [x2, A2, B2] = generate_ABx >(); - // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); - auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}); - BOOST_REQUIRE(res == res2); - } - } - { - T res; - blas::dot(blas::C(x), y, res); - { - using blas::operators::operator,; - using blas::operators::operator*; - auto res2 = +(*x, y); - BOOST_REQUIRE(res == res2); - } - { - auto res2 = +blas::dot(blas::C(x), y); - BOOST_REQUIRE(res == res2); - } - { - // auto [x2, A2, B2] = generate_ABx >(); - // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); - auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__ (T const& t1, T const& t2) {return conj(t1)*t2;}); - BOOST_REQUIRE(res == res2); - } - } - { - T res; - blas::dot(x, blas::C(y), res); - { - using blas::operators::operator,; - auto res2 = +(x, blas::C(y)); - BOOST_REQUIRE(res == res2); - } - { - auto res2 = +blas::dot(x, blas::C(y)); - BOOST_REQUIRE(res == res2); - } - { - // auto [x2, A2, B2] = generate_ABx >(); - // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); - auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__ (T const& t1, T const& t2) {return t1*conj(t2);}); - BOOST_REQUIRE(res == res2); - } - { - BOOST_REQUIRE( blas::dot(blas::C(x), x) == pow(blas::nrm2(x), 2.0) ); - BOOST_REQUIRE( blas::dot(x, blas::C(x)) == pow(blas::nrm2(x), 2.0) ); - - using blas::operators::operator,; - using blas::operators::operator*; - using blas::operators::abs; - using blas::operators::norm; - using blas::operators::operator^; - - BOOST_REQUIRE( (*x, x) == pow(abs(x), 2.0) ); - BOOST_REQUIRE( (*x, x) == pow(abs(x), 2) ); - BOOST_REQUIRE( (*x, x) == norm(x) ); - - BOOST_REQUIRE( (x, *x) == pow(abs(x), 2.0) ); - BOOST_REQUIRE( (x, *x) == pow(abs(x), 2) ); - BOOST_REQUIRE( (x, *x) == norm(x) ); - - BOOST_REQUIRE( (*x, x) == (x^2) ); - } - } - { - // T res; - // blas::dot(blas::C(x), blas::C(y), res); - multi::array res({1, 1}, 0.0); - auto rr = blas::gemm( 1.0, x.partitioned(1), blas::H(y.partitioned(1)), 0.0, res)[0][0]; - // { - // using blas::operators::operator,; - // auto res2 = +(x, blas::C(y)); - // BOOST_REQUIRE(res == res2); - // } - // { - // auto res2 = +blas::dot(x, blas::C(y)); - // BOOST_REQUIRE(res == res2); - // } - // { - // // auto [x2, A2, B2] = generate_ABx >(); - // // thrust::for_each(x2.begin(), x2.end(), [s] __device__ (T& e) {return e*=s;}); - // auto res2 = thrust::inner_product(x.begin(), x.end(), y.begin(), T{}, thrust::plus<>{}, [] __device__ (T const& t1, T const& t2) {return t1*conj(t2);}); - // BOOST_REQUIRE(res == res2); - // } - // { - // BOOST_REQUIRE( blas::dot(blas::C(x), x) == pow(blas::nrm2(x), 2.0) ); - // BOOST_REQUIRE( blas::dot(x, blas::C(x)) == pow(blas::nrm2(x), 2.0) ); - - // using blas::operators::operator,; - // using blas::operators::operator*; - // using blas::operators::abs; - // using blas::operators::norm; - // using blas::operators::operator^; - - // BOOST_REQUIRE( (*x, x) == pow(abs(x), 2.0) ); - // BOOST_REQUIRE( (*x, x) == pow(abs(x), 2) ); - // BOOST_REQUIRE( (*x, x) == norm(x) ); - - // BOOST_REQUIRE( (x, *x) == pow(abs(x), 2.0) ); - // BOOST_REQUIRE( (x, *x) == pow(abs(x), 2) ); - // BOOST_REQUIRE( (x, *x) == norm(x) ); - - // BOOST_REQUIRE( (*x, x) == (x^2) ); - // } - } -} - -BOOST_AUTO_TEST_CASE(cublas_axpy_complex_one) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0}, {4.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - - blas::axpy(1.0, x, y); - std::cout << y[0] << std::endl; - BOOST_REQUIRE( static_cast(y[0]) == 3.2 + I*0.0 ); - { - multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [] __device__ (auto const& ex, auto const& ey) {return ex + ey;}); - BOOST_TEST( yy == y , boost::test_tools::per_element() ); - } - { - multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; - using blas::operators::operator+=; - yy += x; - BOOST_REQUIRE( yy == y ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_axpy_complex_mone) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0}, {4.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - - blas::axpy(-1.0, x, y); - std::cout << y[0] << std::endl; - BOOST_REQUIRE( static_cast(y[0]) == 1.0 + I*0.0 ); - { - multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [] __host__ __device__ (T ex, T ey) {return -1.0*ex + ey;}); - BOOST_TEST( yy == y , boost::test_tools::per_element() ); - } - { - multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; - using blas::operators::operator-=; - yy -= x; - BOOST_REQUIRE( yy == y ); - } - { - multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; - using blas::operators::operator-=; - yy -= x; - yy -= y; - using blas::operators::norm; - BOOST_REQUIRE( norm(yy) == 0 ); - using blas::operators::operator==; - BOOST_REQUIRE( operator==(yy, 0) ); - BOOST_REQUIRE( yy == 0 ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_axpy_complex_alpha) { - namespace blas = multi::blas; - complex const I{0.0, 1.0}; - - using T = complex; - using Alloc = thrust::cuda::allocator; - - multi::array const x = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0}, {4.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - - blas::axpy(3.0, x, y); - std::cout << y[0] << std::endl; - BOOST_REQUIRE( static_cast(y[0]) == 5.4 + I*0.0 ); - { - multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - thrust::transform(x.begin(), x.end(), yy.begin(), yy.begin(), [aa=3.0] __device__ (T ex, T ey) {return aa*ex + ey;}); - BOOST_TEST( yy == y , boost::test_tools::per_element() ); - } - { - multi::array yy = { {2.1, 0.0}, {4.1, 0.0}, {6.1, 0.0}, {11.0, 0.0} }; - using blas::operators::operator+=; - using blas::operators::operator*; - yy += 3.0*x; - BOOST_REQUIRE( yy == y ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemv_conj_complex_zero) { - namespace blas = multi::blas; - using T = complex; - complex const I{0.0, 1.0}; - using Alloc = thrust::cuda::allocator; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - { { 9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0} }, - { { 4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0} }, - { {14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0} }, - }; - multi::array const x = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0}, {4.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - blas::gemv(1.0, A, x, 0.0, y); - { - - multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - std::transform(begin(A), end(A), begin(yy), [&x] (auto const& Ac) {return blas::dot(Ac, x);}); - - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - yy = blas::gemv(1.0, A, x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - multi::array yy = blas::gemv(1.0, A, x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - - } - { - using blas::operators::operator%; - - multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - yy = A % x; - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_conj_zero) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - { 9.0 + I*0.0, 24.0 + I* 0.0, 30.0 + I* 0.0, 9.0 + I* 0.0 }, - { 4.0 + I*0.0, 10.0 + I* 0.0, 12.0 + I* 0.0, 7.0 + I* 0.0 }, - { 14.0 + I*0.0, 16.0 + I* 0.0, 36.0 + I* 0.0, 1.0 + I* 0.0 }, - }; - multi::array const x = { 1.1 + I* 0.0, 2.1 + I* 0.0, 3.1 + I* 0.0}; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - blas::gemv(1.0, blas::T(A), x, 0.0, y); - { - multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - using blas::operators::operator*; - std::transform(begin(transposed(A)), end(transposed(A)), begin(yy), [&x] (auto const& Ac) {return blas::dot(Ac, x);}); - - BOOST_REQUIRE_CLOSE( static_cast(y[0]).real(), static_cast(yy[0]).real(), 1e-7 ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - yy = blas::gemv(1.0, blas::T(A), x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - multi::array yy = blas::gemv(1.0, blas::T(A), x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - using blas::operators::operator%; - - multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - yy = ~A % x; - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } -} - -template void what(T&&) = delete; - -BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_zero) { - namespace blas = multi::blas; - using T = complex; - complex const I{0.0, 1.0}; - using Alloc = thrust::cuda::allocator; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - { { 9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0} }, - { { 4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0} }, - { {14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0} }, - }; - multi::array const x = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0}, {4.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - blas::gemv(1.0, blas::J(A), x, 0.0, y); - { - multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - std::transform(begin(A), end(A), begin(yy), [&x] (auto const& Ac) { - using blas::operators::operator*; // nvcc 11.8 needs this to be inside lambda - return blas::dot(*Ac, x);} - ); - - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - yy = blas::gemv(1.0, blas::J(A), x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - multi::array yy = blas::gemv(1.0, blas::J(A), x); - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - - } - { - using blas::operators::operator%; - using blas::operators::operator*; - - multi::array yy = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0} }; // NOLINT(readability-identifier-length) BLAS naming - yy = *A % x; - BOOST_REQUIRE( static_cast(y[0]) == static_cast(yy[0]) ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } -} - - -BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_conjtrans_zero) { - namespace blas = multi::blas; - using T = complex; - using Alloc = std::allocator; // thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - { 9.0 + I*0.0, 24.0 + I* 0.0, 30.0 + I* 0.0, 9.0 + I* 0.0 }, - { 4.0 + I*0.0, 10.0 + I* 0.0, 12.0 + I* 0.0, 7.0 + I* 0.0 }, - { 14.0 + I*0.0, 16.0 + I* 0.0, 36.0 + I* 0.0, 1.0 + I* 0.0 }, - }; - multi::array const x = { 1.1 + I* 0.0, 2.1 + I* 0.0, 3.1 + I* 0.0}; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - - // blas::gemv(1.0, blas::H(A), x, 0.0, y); - - { - multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - std::transform(begin(transposed(A)), end(transposed(A)), begin(yy), [&x] (auto const& Ac) { - using blas::operators::operator*; // nvcc 11.8 needs this to be inside lambda - return blas::dot(*Ac, x);} - ); - - BOOST_REQUIRE_CLOSE( static_cast(yy[0]).real() , 61.7, 1.e-7 ); - BOOST_REQUIRE_CLOSE( static_cast(yy[1]).real() , 97.0, 1.e-7 ); - BOOST_REQUIRE_CLOSE( static_cast(yy[2]).real() , 169.8, 1.e-7 ); - BOOST_REQUIRE_CLOSE( static_cast(yy[3]).real() , 27.7, 1.e-7 ); - - using blas::operators::operator*; - BOOST_REQUIRE_CLOSE( static_cast(yy[0]).real() , (+blas::dot(*(~A)[0], x)).real() , 1.e-7 ); - BOOST_REQUIRE_CLOSE( static_cast(yy[1]).real() , (+blas::dot(*(~A)[1], x)).real() , 1.e-7 ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemv_complex_trans_one) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - { 9.0 + I*0.0, 24.0 + I* 0.0, 30.0 + I* 0.0, 9.0 + I* 0.0 }, - { 4.0 + I*0.0, 10.0 + I* 0.0, 12.0 + I* 0.0, 7.0 + I* 0.0 }, - { 14.0 + I*0.0, 16.0 + I* 0.0, 36.0 + I* 0.0, 1.0 + I* 0.0 }, - }; - multi::array const x = { 1.1 + I* 0.0, 2.1 + I* 0.0, 3.1 + I* 0.0}; // NOLINT(readability-identifier-length) BLAS naming - multi::array y = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - blas::gemv(3.0 + I*4.0, blas::T(A), x, 1.0, y); - { - multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - // using blas::operators::operator*; - std::transform(begin(transposed(A)), end(transposed(A)), begin(yy), begin(yy), [&x,aa=3.0 + I*4.0,bb=1.0] (auto const& Ac, complex e) {return aa*blas::dot(Ac, x) + bb*e;}); - - BOOST_REQUIRE_CLOSE( static_cast(y[0]).real(), static_cast(yy[0]).real(), 1e-7 ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - yy += blas::gemv(3.0 + I*4.0, blas::T(A), x); - - BOOST_REQUIRE_CLOSE( static_cast(y[0]).real(), static_cast(yy[0]).real(), 1e-7 ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } - { - multi::array yy = { 1.1 + I* 0.0, 2.1 +I* 0.0, 3.1 + I* 0.0, 6.7 + I*0.0 }; // NOLINT(readability-identifier-length) BLAS naming - using blas::operators::operator*; - yy += (3.0 + I*4.0)* ~A % x; - - BOOST_REQUIRE_CLOSE( static_cast(y[0]).real(), static_cast(yy[0]).real(), 1e-7 ); - BOOST_REQUIRE( static_cast(y[1]) == static_cast(yy[1]) ); - BOOST_REQUIRE( static_cast(y[2]) == static_cast(yy[2]) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_none) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - blas::gemm({1.0, 0.0}, A, B, {0.0, 0.0}, C); - - // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), - // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} - // ); - std::transform(begin(A), end(A), begin(C_copy), end(C_copy), - [&B, aa=1.0, bb=0] (auto const& Ar, auto&& Cr) {return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr));} - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - C = blas::gemm(1.0 + I*0.0, A, B); - - // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), - // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} - // ); - std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa=1.0, bb=0.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); - } - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - C += blas::gemm(1.0 + I*0.0, A, B); - - std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), - [&A, aa=1.0, bb=1.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - using blas::operators::operator*; - using blas::operators::operator+=; - C += A*B; - - std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa=1.0, bb=1.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); - } - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_second) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - blas::gemm({1.0, 0.0}, A, blas::T(B), {0.0, 0.0}, C); - - std::transform(begin(B), end(B), begin(transposed(C_copy)), begin(transposed(C_copy)), - [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - C = blas::gemm(1.0 + I*0.0, A, blas::T(B)); - - // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), - // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} - // ); - std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa=1.0, bb=0.0] (auto const& Ac, auto&& Cr) { - return blas::gemv(aa, B, Ac, bb, std::move(Cr)); - }); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - C += blas::gemm(1.0 + I*0.0, A, blas::T(B)); - - std::transform(begin(B), end(B), begin(transposed(C_copy)), begin(transposed(C_copy)), - [&A, aa=1.0, bb=1.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - using blas::operators::operator*; - using blas::operators::operator+=; - C += A * ~B; - - std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa=1.0, bb=1.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, B, Ar, bb, std::move(Cr)); - } - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - using blas::operators::operator*; - using blas::operators::operator+=; - C += 2.0*(A * ~B); - - std::transform(begin(A), end(A), begin(C_copy), begin(C_copy), [&B, aa=2.0, bb=1.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, B, Ar, bb, std::move(Cr)); - } - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_first) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - blas::gemm({1.0, 0.0}, blas::T(A), B, {0.0, 0.0}, C); - - std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), - [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, blas::T(A), Bc, bb, std::move(Cc));} - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - C = blas::gemm(1.0 + I*0.0, blas::T(A), B); - - // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), - // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} - // ); - std::transform(begin(transposed(A)), end(transposed(A)), begin(C_copy), begin(C_copy), [&B, aa=1.0, bb=0.0] (auto const& Ac, auto&& Cr) { - return blas::gemv(aa, blas::T(B), Ac, bb, std::move(Cr)); - }); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - C += blas::gemm(1.0 + I*0.0, blas::T(A), B); - - std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), - [&A, aa=1.0, bb=1.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, blas::T(A), Bc, bb, std::move(Cc));} - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - using blas::operators::operator*; - using blas::operators::operator+=; - C += ~A * B; - - std::transform(begin(transposed(A)), end(transposed(A)), begin(C_copy), begin(C_copy), [&B, aa=1.0, bb=1.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); - } - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - using blas::operators::operator*; - using blas::operators::operator+=; - C += 2.0*(~A * B); - - std::transform(begin(transposed(A)), end(transposed(A)), begin(C_copy), begin(C_copy), [&B, aa=2.0, bb=1.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, blas::T(B), Ar, bb, std::move(Cr)); - } - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_both) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - blas::gemm({1.0, 0.0}, blas::T(A), blas::T(B), {0.0, 0.0}, C); - - std::transform(begin(B), end(B), begin(transposed(C_copy)), begin(transposed(C_copy)), - [&A, aa=1.0, bb=0.0] (auto const& Br, auto&& Cc) {return blas::gemv(aa, blas::T(A), Br, bb, std::move(Cc));} - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - C = blas::gemm(1.0 + I*0.0, blas::T(A), blas::T(B)); - - // std::transform(begin(transposed(B)), end(transposed(B)), begin(transposed(C_copy)), begin(transposed(C_copy)), - // [&A, aa=1.0, bb=0.0] (auto const& Bc, auto&& Cc) {return blas::gemv(aa, A, Bc, bb, std::move(Cc));} - // ); - std::transform(begin(transposed(A)), end(transposed(A)), begin(C_copy), begin(C_copy), [&B, aa=1.0, bb=0.0] (auto const& Ac, auto&& Cr) { - return blas::gemv(aa, B, Ac, bb, std::move(Cr)); - }); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - C += blas::gemm(1.0 + I*0.0, blas::T(A), blas::T(B)); - - std::transform(begin(B), end(B), begin(transposed(C_copy)), begin(transposed(C_copy)), - [&A, aa=1.0, bb=1.0] (auto const& Br, auto&& Cc) {return blas::gemv(aa, blas::T(A), Br, bb, std::move(Cc));} - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - using blas::operators::operator*; - using blas::operators::operator+=; - C += ~A * ~B; - - std::transform(begin(transposed(A)), end(transposed(A)), begin(C_copy), begin(C_copy), [&B, aa=1.0, bb=1.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, B, Ar, bb, std::move(Cr)); - } - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto C_copy = C; - using blas::operators::operator*; - using blas::operators::operator+=; - C += 2.0*(~A * ~B); - - std::transform(begin(transposed(A)), end(transposed(A)), begin(C_copy), begin(C_copy), [&B, aa=2.0, bb=1.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, B, Ar, bb, std::move(Cr)); - } - ); - - BOOST_REQUIRE( static_cast(C_copy[1][0]) == static_cast(C[1][0]) ); - BOOST_REQUIRE( static_cast(C_copy[0][1]) == static_cast(C[0][1]) ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_second) { - namespace blas = multi::blas; - using T = complex; - using Alloc = std::allocator; // thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto CC = C; - auto C_copy = CC; - // blas::gemm({1.0, 0.0}, A, blas::J(B), {0.0, 0.0}, C); - blas::gemm({1.0, 0.0}, blas::T(B), blas::H(A), {0.0, 0.0}, C_copy); - { - auto const [is, js] = C.extensions(); - for(auto i : is) { - for(auto j : js) { - C[i][j] *= 0.0; - for(auto k : B.extension()) { - C[i][j] += A[i][k]*conj(B[k][j]); - } - } - } - } - { - std::transform(begin(A), end(A), begin(CC), begin(CC), [BT = transposed(B)](auto const& Ar, auto&& Cr) { - return std::transform( - begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& Bc, auto&& Ce) { - return 1.0*blas::dot(Ar, blas::C(Bc)) + 0.0*Ce; - } - ), std::move(Cr); - }); - } - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(C_copy[1][0]).real() == +static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(C_copy[1][0]).imag() == -static_cast(C[0][1]).imag() ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_first) { - namespace blas = multi::blas; - using T = complex; - using Alloc = std::allocator; // thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto CC = C; - auto C_copy = CC; - // blas::gemm({1.0, 0.0}, blas::J(A), B, {0.0, 0.0}, C); - // blas::gemm({1.0, 0.0}, blas::T(B), blas::H(A), {0.0, 0.0}, C_copy); - // { - // auto const [is, js] = C.extensions(); - // for(auto i : is) { - // for(auto j : js) { - // C[i][j] *= 0.0; - // for(auto k : B.extension()) { - // C[i][j] += A[i][k]*conj(B[k][j]); - // } - // } - // } - // } - // { - // std::transform(begin(A), end(A), begin(CC), begin(CC), [BT = transposed(B)](auto const& Ar, auto&& Cr) { - // return std::transform( - // begin(BT), end(BT), begin(Cr), begin(Cr), [&Ar](auto const& BCr, auto&& Ce) { - // return 1.0*blas::dot(Ar, blas::C(BCr)) + 0.0*Ce; - // } - // ), std::move(Cr); - // }); - // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(C_copy[1][0]).real() == +static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(C_copy[1][0]).imag() == -static_cast(C[0][1]).imag() ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_both) { - namespace blas = multi::blas; - using T = complex; - using Alloc = std::allocator; // thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - { - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - auto CC = C; - auto C_copy = CC; - // blas::gemm({1.0, 0.0}, blas::J(A), blas::J(B), {0.0, 0.0}, C); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_second) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm({1.0, 0.0}, A, blas::H(B), {0.0, 0.0}, C); - { - multi::array CC({2, 2}, {3.0, 0.0}); - - std::transform( - begin(A), end(A), begin(CC), begin(CC), - [&B, aa = 1.0, bb = 0.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, blas::J(B), Ar, bb, std::move(Cr)); - } - ); - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - { - multi::array CC({2, 2}, {3.0, 0.0}); - CC = blas::gemm({1.0, 0.0}, A, blas::H(B)); - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - { - multi::array CC({2, 2}, {3.0, 0.0}); - using blas::operators::operator*; - using blas::operators::operator~; - CC = A* ~*B; - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_second_plus) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm({1.0, 0.0}, A, blas::H(B), {1.0, 0.0}, C); - { - multi::array CC({2, 2}, {3.0, 0.0}); - - std::transform( - begin(A), end(A), begin(CC), begin(CC), - [&B, aa = 1.0, bb = 1.0] (auto const& Ar, auto&& Cr) { - return blas::gemv(aa, blas::J(B), Ar, bb, std::move(Cr)); - } - ); - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - { - multi::array CC({2, 2}, {3.0, 0.0}); - CC += blas::gemm({1.0, 0.0}, A, blas::H(B)); - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - { - multi::array CC({2, 2}, {3.0, 0.0}); - using blas::operators::operator*; - using blas::operators::operator~; - CC += A* ~*B; - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_first) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm({1.0, 0.0}, blas::H(A), B, {0.0, 0.0}, C); - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // auto const [is, js] = CC.extensions(); - // for(auto i : is) { - // for(auto j : js) { - // CC[i][j] = 0.0; - // for(auto k : A.extension()) { - // CC[i][j] += 1.0*conj(A[k][i])*B[k][j] ; - // } - // } - // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - { - multi::array CC({2, 2}, {3.0, 0.0}); - - std::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BT=transposed(B)](auto const& Ac, auto&& Cr) { - std::transform(begin(BT), end(BT), begin(Cr), begin(Cr), [&Ac](auto const& Bc, auto&& c) { - return blas::dot(blas::C(Ac), Bc, std::move(c)); - }); - return std::move(Cr); - }); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - { - multi::array CC({2, 2}, {3.0, 0.0}); - CC = blas::gemm({1.0, 0.0}, blas::H(A), B); - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - { - multi::array CC({2, 2}, {3.0, 0.0}); - using blas::operators::operator*; - using blas::operators::operator~; - CC = ~*A *B; - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_both) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm({1.0, 0.0}, blas::H(A), blas::H(B), {0.0, 0.0}, C); - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // auto const [is, js] = CC.extensions(); - // for(auto i : is) { - // for(auto j : js) { - // CC[i][j] = 0.0; - // for(auto k : A.extension()) { - // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; - // } - // } - // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - - // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { - // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { - // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); - // // return conj(+blas::dot(Ac, Bc, std::move(c))); - // }); - // return std::move(Cr); - // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - { - multi::array CC({2, 2}, {3.0, 0.0}); - - CC = blas::gemm({1.0, 0.0}, blas::H(A), blas::H(B)); - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - { - multi::array CC({2, 2}, {3.0, 0.0}); - using blas::operators::operator*; - using blas::operators::operator~; - CC = ~*A * ~*B; - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_trans_herm) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B), {0.0, 0.0}, C); - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // auto const [is, js] = CC.extensions(); - // for(auto i : is) { - // for(auto j : js) { - // CC[i][j] = 0.0; - // for(auto k : A.extension()) { - // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; - // } - // } - // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - - // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { - // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { - // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); - // // return conj(+blas::dot(Ac, Bc, std::move(c))); - // }); - // return std::move(Cr); - // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - { - multi::array CC({2, 2}, {3.0, 0.0}); - - CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } - { - multi::array CC({2, 2}, {3.0, 0.0}); - using blas::operators::operator*; - using blas::operators::operator~; - CC = ~A * ~*B; - - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_trans) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - //blas::gemm({1.0, 0.0}, blas::H(A), blas::T(B), {0.0, 0.0}, C); - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // auto const [is, js] = CC.extensions(); - // for(auto i : is) { - // for(auto j : js) { - // CC[i][j] = 0.0; - // for(auto k : A.extension()) { - // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; - // } - // } - // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - - // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { - // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { - // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); - // // return conj(+blas::dot(Ac, Bc, std::move(c))); - // }); - // return std::move(Cr); - // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - - // CC = blas::gemm({1.0, 0.0}, blas::H(A), blas::T(B)); - - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // using blas::operators::operator*; - // using blas::operators::operator~; - // CC = ~*A * ~B; - - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_conj_herm) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - // blas::gemm({1.0, 0.0}, blas::J(A), blas::H(B), {0.0, 0.0}, C); - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // auto const [is, js] = CC.extensions(); - // for(auto i : is) { - // for(auto j : js) { - // CC[i][j] = 0.0; - // for(auto k : A.extension()) { - // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; - // } - // } - // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - - // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { - // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { - // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); - // // return conj(+blas::dot(Ac, Bc, std::move(c))); - // }); - // return std::move(Cr); - // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - - // CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); - - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // using blas::operators::operator*; - // using blas::operators::operator~; - // CC = ~A * ~*B; - - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } -} - -BOOST_AUTO_TEST_CASE(cublas_one_gemm_complex_herm_conj) { - namespace blas = multi::blas; - using T = complex; - using Alloc = thrust::cuda::allocator; - complex const I{0.0, 1.0}; - - // NOLINT(readability-identifier-length) BLAS naming - multi::array const A = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - - multi::array const B = { - {3.0 - 4.0 * I, 19.0 - 1.0 * I}, - {1.0 + 5.0 * I, 8.0 - 8.0 * I}, - }; - multi::array C({2, 2}, {3.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - //blas::gemm({1.0, 0.0}, blas::H(A), blas::J(B), {0.0, 0.0}, C); - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // auto const [is, js] = CC.extensions(); - // for(auto i : is) { - // for(auto j : js) { - // CC[i][j] = 0.0; - // for(auto k : A.extension()) { - // CC[i][j] += 1.0*conj(A[k][i])*conj(B[j][k]) ; - // } - // } - // } - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - - // thrust::transform(begin(transposed(A)), end(transposed(A)), begin(CC), begin(CC), [BP = &B] __device__ (multi::array::reference const& Ac, multi::array::reference&& Cr) { - // thrust::transform(begin(*BP), end(*BP), begin(Cr), begin(Cr), [APc = &Ac] __device__ (multi::array::reference const& Bc, complex&& c) { - // return conj(thrust::inner_product(begin(*APc), end(*APc), begin(Bc), 0.0*c, std::plus<>{}, [] __device__ (complex const& a, complex const& b) {return a*b;})); - // // return conj(+blas::dot(Ac, Bc, std::move(c))); - // }); - // return std::move(Cr); - // }); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - - // CC = blas::gemm({1.0, 0.0}, blas::T(A), blas::H(B)); - - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } - // { - // multi::array CC({2, 2}, {3.0, 0.0}); - // using blas::operators::operator*; - // using blas::operators::operator~; - // CC = ~A * ~*B; - - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).real() == static_cast(C[1][0]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[1][0]).imag() == static_cast(C[1][0]).imag() ); - - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).real() == static_cast(C[0][1]).real() ); - // BOOST_TEST_REQUIRE( static_cast(CC[0][1]).imag() == static_cast(C[0][1]).imag() ); - // } -} - -BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - using Alloc = thrust::cuda::allocator; - - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - using multi::blas::trsm; - - blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(A), blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† - BOOST_REQUIRE_CLOSE( static_cast(B[1][2]).imag() , -0.147059 , 0.001); -} - -BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const_UTH) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - using Alloc = thrust::cuda::allocator; - - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const A = { - { 1.0 + 4.0*I, 0.0 + 0.0*I, 0.0 - 0.0*I}, - { 3.0 + 0.0*I, 7.0 - 3.0*I, 0.0 + 0.0*I}, - { 4.0 - 10.0*I, 1.0 + 0.0*I, 8.0 - 2.0*I}, - }; - - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array B = { - {1.0 + 1.0*I, 2.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I}, - {3.0 + 1.0*I, 1.0 - 1.0*I}, - }; - - using multi::blas::trsm; - - blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(blas::H(A)), B); - BOOST_REQUIRE_CLOSE( static_cast(B[1][1]).imag(), -0.0811359, 0.001); - BOOST_REQUIRE_CLOSE( static_cast(B[2][1]).imag(), -0.147059, 0.001); -} - -BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - using Alloc = thrust::cuda::allocator; - - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const A = { - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array B = { - {1.0 + 1.0*I, 2.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I}, - {3.0 + 1.0*I, 1.0 - 1.0*I}, - }; - - using multi::blas::trsm; - - blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(A), B); // B←A⁻¹.B, B†←A⁻¹.B† - BOOST_REQUIRE_CLOSE( static_cast(B[2][1]).imag() , -0.0882353, 0.001); -} - -// BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_conj_second) { -// namespace blas = multi::blas; -// using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit -// using Alloc = thrust::cuda::allocator; - -// // NOLINTNEXTLINE(readability-identifier-length) BLAS naming -// multi::array const A = { -// { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, -// { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, -// { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, -// }; - -// // NOLINTNEXTLINE(readability-identifier-length) BLAS naming -// multi::array B = { -// {1.0 + 1.0*I, 2.0 + 1.0*I}, -// {5.0 + 3.0*I, 9.0 + 3.0*I}, -// {3.0 + 1.0*I, 1.0 - 1.0*I}, -// }; - -// using multi::blas::trsm; - -// blas::trsm(blas::side::left, {1.0, 0.0}, blas::U(A), blas::J(B)); // B*←A⁻¹.B*, B^T←A⁻¹.B^T -// BOOST_REQUIRE_CLOSE( static_cast(B[2][1]).imag() , -0.0882353, 0.001); -// } - -BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_operator) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - using Alloc = thrust::cuda::universal_allocator; - - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + 1.0*I, 2.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I}, - {3.0 + 1.0*I, 1.0 - 1.0*I}, - }; - - using blas::operators::operator|=; - using blas::operators::U; - B |= U(A); // B←A⁻¹.B, B†←A⁻¹.B† - BOOST_REQUIRE_CLOSE( static_cast(B[2][1]).imag() , -0.0882353, 0.001); -} - -BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_right) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - using Alloc = thrust::cuda::allocator; - - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - using multi::blas::trsm; - - blas::trsm(blas::side::right, {1.0, 0.0}, blas::U(A), B); // B←B.A⁻¹, B←B/A, B†←A⁻¹†.B† - BOOST_REQUIRE_CLOSE( static_cast(B[1][2]).imag(), 1.60142, 0.001); -} - -BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_right_LT) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - using Alloc = thrust::cuda::allocator; - - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const A = { - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array B = { - { 1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - { 5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - using multi::blas::trsm; - - blas::trsm(blas::side::right, {1.0, 0.0}, blas::L(blas::T(A)), B); // B←B.Aᵀ⁻¹, B←B/Aᵀ, B†←Aᵀ⁻¹†.B†, Bᵀ←A⁻¹.Bᵀ, Bᵀ←Bᵀ\A - BOOST_REQUIRE_CLOSE( static_cast(B[1][2]).imag(), -0.0882353, 0.001); -} - -// BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_right_LH) { -// namespace blas = multi::blas; -// using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit -// using Alloc = thrust::cuda::allocator; - -// // NOLINTNEXTLINE(readability-identifier-length) BLAS naming -// multi::array const A = { -// { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, -// { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, -// { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, -// }; - -// // NOLINTNEXTLINE(readability-identifier-length) BLAS naming -// multi::array B = { -// { 1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, -// { 5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, -// }; - -// using multi::blas::trsm; - -// blas::trsm(blas::side::right, {1.0, 0.0}, blas::U(blas::J(A)), B); // B←B.A*⁻¹, B←B/A*, B*←B*.A⁻¹ -// BOOST_REQUIRE_CLOSE( static_cast(B[1][2]).imag(), -0.0882353, 0.001); -// } - -BOOST_AUTO_TEST_CASE(UTA_blas_trsm_complex_nonsquare_default_diagonal_gemm_check_no_const_right_operator) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - using Alloc = thrust::cuda::allocator; - - multi::array const A = { // NOLINT(readability-identifier-length) BLAS naming - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - multi::array B = { // NOLINT(readability-identifier-length) BLAS naming - {1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - using multi::blas::trsm; - - using blas::operators::operator/=; - B /= blas::U(A); - BOOST_REQUIRE_CLOSE( static_cast(B[1][2]).imag(), 1.60142, 0.001); -} - -#endif diff --git a/include/multi/adaptors/cuda/cublas/test/gemm.cu b/include/multi/adaptors/cuda/cublas/test/gemm.cu deleted file mode 100644 index 8b626d581..000000000 --- a/include/multi/adaptors/cuda/cublas/test/gemm.cu +++ /dev/null @@ -1,96 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUBLAS gemm" -#include - -#include - -#include -#include -#include -#include - -#include - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_blas_gemv_complex) { - namespace blas = multi::blas; - using complex = thrust::complex; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::thrust::cuda::array const M_gpu = { - { { 9.0, 0.0}, {24.0, 0.0}, {30.0, 0.0}, {9.0, 0.0} }, - { { 4.0, 0.0}, {10.0, 0.0}, {12.0, 0.0}, {7.0, 0.0} }, - { {14.0, 0.0}, {16.0, 0.0}, {36.0, 0.0}, {1.0, 0.0} }, - }; - - multi::thrust::cuda::array const X_gpu = { {1.1, 0.0}, {2.1, 0.0}, {3.1, 0.0}, {4.1, 0.0} }; - - multi::thrust::cuda::array Y_gpu = { {4.0, 0.0}, {5.0, 0.0}, {6.0, 0.0} }; - - blas::gemv(/*alpha*/ 1.1, M_gpu, X_gpu, /*beta*/ 1.2, Y_gpu); // y = a*M*x + b*y - - multi::array const Y_copy = Y_gpu; - - using blas::operators::operator-; - BOOST_REQUIRE_SMALL( +blas::nrm2(Y_copy - multi::array{ {214.02, 0.0}, {106.43, 0.0}, {188.37, 0.0} }) , 1e-13); -} - -BOOST_AUTO_TEST_CASE(cublas_gemv_real) { - namespace blas = multi::blas; - using T = double; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::thrust::cuda::array const M_gpu = { - { 9.0, 24.0, 30.0, 9.0 }, - { 4.0, 10.0, 12.0, 7.0 }, - { 14.0, 16.0, 36.0, 1.0 }, - }; - - multi::thrust::cuda::array const X_gpu = { 1.1, 2.1, 3.1, 4.1 }; - - multi::thrust::cuda::array Y_gpu = { 4.0, 5.0, 6.0 }; - - blas::gemv(/*alpha*/ 1.1, M_gpu, X_gpu, /*beta*/ 1.2, Y_gpu); // y = a*M*x + b*y - - multi::array const Y_copy = Y_gpu; - - using blas::operators::operator-; - BOOST_REQUIRE_SMALL( +blas::nrm2(Y_copy - multi::array{ 214.02, 106.43, 188.37 }) , 1e-13); -} - -BOOST_AUTO_TEST_CASE(cublas_gemm_nh) { - namespace blas = multi::blas; - - using complex = thrust::complex; - complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit - - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::thrust::cuda::array const a = { - {1.0 - 2.0 * I, 9.0 - 1.0 * I}, - {2.0 + 3.0 * I, 1.0 - 2.0 * I}, - }; - { - multi::thrust::cuda::array c({2, 2}, {9999.0, 0.0}); // NOLINT(readability-identifier-length) conventional BLAS naming - blas::gemm({1.0, 0.0}, a, a, {0.0, 0.0}, c); // c=aa†, c†=aa† - - multi::array const c_copy = c; - BOOST_REQUIRE( c_copy[1][0] == 16.0 - 2.0*I ); - BOOST_REQUIRE( c_copy[0][1] == 14.0 - 38.0*I ); - } - { - auto const c = +blas::gemm(complex{1.0, 0.0}, a, a); // c=aa†, c†=aa† - - multi::array const c_copy = c; - BOOST_REQUIRE( c_copy[1][0] == 16.0 - 2.0*I ); - BOOST_REQUIRE( c_copy[0][1] == 14.0 - 38.0*I ); - } - { - multi::thrust::cuda::array c({2, 2}, {0.0, 0.0}); - c += blas::gemm(complex{1.0, 0.0}, a, a); // c=aa†, c†=aa† - - multi::array const c_copy = c; - BOOST_REQUIRE( c_copy[1][0] == 16.0 - 2.0*I ); - BOOST_REQUIRE( c_copy[0][1] == 14.0 - 38.0*I ); - } -} \ No newline at end of file diff --git a/include/multi/adaptors/cuda/cublas/test/herk.cu b/include/multi/adaptors/cuda/cublas/test/herk.cu deleted file mode 100644 index c6822e39d..000000000 --- a/include/multi/adaptors/cuda/cublas/test/herk.cu +++ /dev/null @@ -1,39 +0,0 @@ -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUBLAS herk" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../../../adaptors/cuda.hpp" // multi::cuda ns -#include "../../../../adaptors/blas/herk.hpp" - - -namespace multi = boost::multi; -using complex = std::complex; -complex const I{0, 1}; - -BOOST_AUTO_TEST_CASE(multi_blas_herk){ - multi::array const a = { - { 1. + 3.*I, 3.- 2.*I, 4.+ 1.*I}, - { 9. + 1.*I, 7.- 8.*I, 1.- 3.*I} - }; - multi::cuda::array const a_gpu = a; - namespace blas = multi::blas; - { - multi::array c({2, 2}, 9999.); - blas::herk(1., a, c); - BOOST_REQUIRE( c[1][0] == complex(50., -49.) ); - BOOST_REQUIRE( c[0][1] == complex(50., +49.) ); - - multi::array const c_copy = blas::herk(1., a); - BOOST_REQUIRE( c == c_copy ); - } - { - multi::array c({3, 3}, 9999.); - blas::herk(1., blas::H(a), c); - BOOST_REQUIRE( c[2][1] == complex(41, +2) ); - BOOST_REQUIRE( c[1][2] == complex(41, -2) ); - - multi::array const c_copy = blas::herk(1., blas::H(a)); - BOOST_REQUIRE( c_copy == c ); - } -} - diff --git a/include/multi/adaptors/cuda/cublas/test/trsm.cu b/include/multi/adaptors/cuda/cublas/test/trsm.cu deleted file mode 100644 index 75a25a3be..000000000 --- a/include/multi/adaptors/cuda/cublas/test/trsm.cu +++ /dev/null @@ -1,125 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUBLAS trsm" -#include - -#include -#include - -#include - -#include - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(unit_trsm_multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const A = { - { 1.0 + 0.0*I, 3.0 + 1.2*I, 5.0 - 12.0*I}, - { 0.0 + 0.0*I, 1.0 + 0.0*I, 2.1 + 1.1*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 1.0 + 0.0*I}, - }; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array B = { - {1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - multi::thrust::cuda::array const A_gpu = A; - multi::thrust::cuda::array B_gpu = B; - - using multi::blas::trsm; - using multi::blas::filling; - using multi::blas::hermitized; - - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::unit, complex{1.0, 0.0}, A , blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† - BOOST_REQUIRE_CLOSE( B[1][0].real() , -43.439999999999998 , 0.001); - BOOST_REQUIRE_CLOSE( B[1][0].imag() , -13.000000000000002 , 0.001); - - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::unit, complex{1.0, 0.0}, A_gpu, blas::H(B_gpu)); - multi::array B_cpy = B_gpu; - - BOOST_REQUIRE_CLOSE( B_cpy[1][0].real() , -43.439999999999998 , 0.001); - BOOST_REQUIRE_CLOSE( B_cpy[1][0].imag() , -13.000000000000002 , 0.001); -} - -BOOST_AUTO_TEST_CASE(trsm_multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const A = { - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array B = { - {1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - multi::thrust::cuda::array const A_gpu = A; - multi::thrust::cuda::array B_gpu = B; - - using multi::blas::trsm; - using multi::blas::filling; - using multi::blas::hermitized; - - // B = ConjugateTranspose[Inverse[A] . ConjugateTranspose[B]] - // ConjugateTranspose[B] = Inverse[A] . ConjugateTranspose[B] - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::non_unit, complex{1.0, 0.0}, A , blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† - BOOST_REQUIRE_CLOSE( B[1][0].real(), -0.72562939983295538 , 0.001); - BOOST_REQUIRE_CLOSE( B[1][0].imag(), 0.046772461520104877, 0.001); - - BOOST_REQUIRE_CLOSE( real(blas::H(B)[0][1]), -0.72562939983295538 , 0.001); - BOOST_REQUIRE_CLOSE( imag(blas::H(B)[0][1]), -0.046772461520104877, 0.001); - - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::non_unit, complex{1.0, 0.0}, A_gpu, blas::H(B_gpu)); - cudaDeviceSynchronize(); - - multi::array B_cpy = B_gpu; - BOOST_REQUIRE_CLOSE( B_cpy[1][0].real() , -0.72562939983295538 , 0.001); - BOOST_REQUIRE_CLOSE( B_cpy[1][0].imag() , 0.046772461520104877, 0.001); -} - -BOOST_AUTO_TEST_CASE(default_param_unit_trsm_multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_no_const) { - namespace blas = multi::blas; - using complex = thrust::complex; complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array const A = { - { 1.0 + 4.0*I, 3.0 + 0.0*I, 4.0 - 10.0*I}, - { 0.0 + 0.0*I, 7.0 - 3.0*I, 1.0 + 0.0*I}, - { 0.0 + 0.0*I, 0.0 + 0.0*I, 8.0 - 2.0*I}, - }; - // NOLINTNEXTLINE(readability-identifier-length) BLAS naming - multi::array B = { - {1.0 + 1.0*I, 2.0 + 1.0*I, 3.0 + 1.0*I}, - {5.0 + 3.0*I, 9.0 + 3.0*I, 1.0 - 1.0*I}, - }; - - multi::thrust::cuda::array const A_gpu = A; - multi::thrust::cuda::array B_gpu = B; - - using multi::blas::trsm; - using multi::blas::filling; - using multi::blas::hermitized; - - // B = ConjugateTranspose[Inverse[A] . ConjugateTranspose[B]] - // ConjugateTranspose[B] = Inverse[A] . ConjugateTranspose[B] - blas::trsm(blas::side::left, blas::filling::upper, complex{1.0, 0.0}, A , blas::H(B)); // B†←A⁻¹.B†, B←B.A⁻¹†, B←(A⁻¹.B†)† - BOOST_REQUIRE_CLOSE( B[1][0].real(), -0.72562939983295538 , 0.001); - BOOST_REQUIRE_CLOSE( B[1][0].imag(), 0.046772461520104877, 0.001); - - BOOST_REQUIRE_CLOSE( real(blas::H(B)[0][1]), -0.72562939983295538 , 0.001); - BOOST_REQUIRE_CLOSE( imag(blas::H(B)[0][1]), -0.046772461520104877, 0.001); - - blas::trsm(blas::side::left, blas::filling::upper, blas::diagonal::non_unit, complex{1.0, 0.0}, A_gpu, blas::H(B_gpu)); - cudaDeviceSynchronize(); - - multi::array B_cpy = B_gpu; - BOOST_REQUIRE_CLOSE( B_cpy[1][0].real() , -0.72562939983295538 , 0.001); - BOOST_REQUIRE_CLOSE( B_cpy[1][0].imag() , 0.046772461520104877, 0.001); -} diff --git a/include/multi/adaptors/cuda/test/array.cu b/include/multi/adaptors/cuda/test/array.cu deleted file mode 100644 index 61fd43450..000000000 --- a/include/multi/adaptors/cuda/test/array.cu +++ /dev/null @@ -1,8 +0,0 @@ -#include "../../adaptors/thrust.hpp" - -int main(){ - - - -} - diff --git a/include/multi/adaptors/cuda/tests/array.cpp b/include/multi/adaptors/cuda/tests/array.cpp deleted file mode 100644 index bf3dcd7bb..000000000 --- a/include/multi/adaptors/cuda/tests/array.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS//-*-indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4;-*- -$CXX $0 -o $0x -lcudart -lboost_timer -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020 -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuda adaptor" -#define BOOST_TEST_DYN_LINK -#include -namespace utf = boost::unit_test; -#include - -#include "../../../adaptors/cuda.hpp" - -#include - -namespace boost::multi::memory::cuda{ -template -void copy(array_iterator>, array_iterator>, array_iterator>){ - assert(0); -} -//std::copy, boost::multi::memory::cuda::ref >, boost::multi::array_iterator, boost::multi::memory::cuda::ref > > -} - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(cudart_double, *utf::tolerance(0.00001)*utf::timeout(10)){ - - auto const in = []{ - multi::array r({32, 90, 98, 96}); - std::generate(data_elements(r), data_elements(r)+num_elements(r), &std::rand); - return r; - }(); - std::cout<<"memory size "<< in.num_elements()*sizeof(decltype(in)::element)/1e6 <<" MB\n"; - - { - boost::timer::auto_cpu_timer t{"%ws wall, CPU (%p%)\n"}; - multi::cuda::array const in_gpu = in; - - multi::array const in_cpy = in_gpu; - BOOST_REQUIRE( in == in_cpy ); - } - { - boost::timer::auto_cpu_timer t{"%ws wall, CPU (%p%)\n"}; - multi::cuda::array const in_gpu = in; - } - { - multi::cuda::array const in_gpu = in; - multi::cuda::array out_gpu = in; - boost::timer::auto_cpu_timer t{"copy assign gpu____ %ws wall, CPU (%p%)\n"}; - out_gpu = in_gpu; - auto c = static_cast(out_gpu[1][2][3][4]); (void)c; - - (out_gpu << 1) = (in_gpu << 1); - } - { - multi::cuda::managed::array const in_mng = in; - multi::cuda::managed::array out_mng = in; - { - boost::timer::auto_cpu_timer t{"copy assign mng____ %ws wall, CPU (%p%)\n"}; - out_mng = in_mng; - auto c = static_cast(out_mng[1][2][3][4]); (void)c; - } - { - boost::timer::auto_cpu_timer t{"copy assign mng_hot %ws wall, CPU (%p%)\n"}; - out_mng = in_mng; - auto c = static_cast(out_mng[1][2][3][4]); (void)c; - } - { - boost::timer::auto_cpu_timer t{"copy assign mng loop %ws wall, CPU (%p%)\n"}; - out_mng() = in_mng(); - auto c = static_cast(out_mng[1][2][3][4]); (void)c; - } - } -} - -BOOST_AUTO_TEST_CASE(cudart_complex, *utf::tolerance(0.00001)*utf::timeout(10)){ - - using complex = std::complex; - - auto const in = []{ - multi::array r({32, 90, 98, 96}); - std::generate(data_elements(r), data_elements(r)+num_elements(r), &std::rand); - return r; - }(); - std::cout<<"memory size "<< in.num_elements()*sizeof(decltype(in)::element)/1e6 <<" MB\n"; - - { - boost::timer::auto_cpu_timer t{"%ws wall, CPU (%p%)\n"}; - multi::cuda::array const in_gpu = in; - } - { - boost::timer::auto_cpu_timer t{"%ws wall, CPU (%p%)\n"}; - multi::cuda::array const in_gpu = in; - } - -} diff --git a/include/multi/adaptors/cufft.hpp b/include/multi/adaptors/cufft.hpp deleted file mode 100644 index 0ae273129..000000000 --- a/include/multi/adaptors/cufft.hpp +++ /dev/null @@ -1,453 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_CUFFTW_HPP -#define MULTI_ADAPTORS_CUFFTW_HPP - -#include "../adaptors/../utility.hpp" -#include "../adaptors/../array.hpp" -#include "../adaptors/../config/NODISCARD.hpp" - -#include "../adaptors/cuda.hpp" - -#include -#include - -// #include "../complex.hpp" - -#include -#include - -namespace boost{ -namespace multi{ -namespace cufft{ - -// cuFFT API errors -static char const* _cudaGetErrorEnum(cufftResult error) { - switch (error) { - case CUFFT_SUCCESS: return "CUFFT_SUCCESS"; - case CUFFT_INVALID_PLAN: return "CUFFT_INVALID_PLAN"; - case CUFFT_ALLOC_FAILED: return "CUFFT_ALLOC_FAILED"; - case CUFFT_INVALID_TYPE: return "CUFFT_INVALID_TYPE"; - case CUFFT_INVALID_VALUE: return "CUFFT_INVALID_VALUE"; - case CUFFT_INTERNAL_ERROR: return "CUFFT_INTERNAL_ERROR"; - case CUFFT_EXEC_FAILED: return "CUFFT_EXEC_FAILED"; - case CUFFT_SETUP_FAILED: return "CUFFT_SETUP_FAILED"; - case CUFFT_INVALID_SIZE: return "CUFFT_INVALID_SIZE"; - case CUFFT_UNALIGNED_DATA: return "CUFFT_UNALIGNED_DATA"; - } - return ""; -} - -#define cufftSafeCall(err) __cufftSafeCall(err, __FILE__, __LINE__) -inline void __cufftSafeCall(cufftResult err, const char *file, const int line) { - if( CUFFT_SUCCESS != err) { - std::cerr <<"CUFFT error in file "<< __FILE__ <<", line "<< __LINE__ <<"\nerror "<< err <<": "<<_cudaGetErrorEnum(err)<<"\n"; - //fprintf(stderr, "CUFFT error in file '%s', line %d\n %s\nerror %d: %s\nterminating!\n", __FILE__, __LINE__, err, - // _cudaGetErrorEnum(err)); - cudaDeviceReset(); assert(0); - } -} - -class sign { - int impl_ = 0; - - public: - sign() = default; - constexpr sign(int i) : impl_{i} {} - constexpr operator int() const {return impl_;} -}; - -constexpr sign forward{CUFFT_FORWARD}; -constexpr sign none{0}; -constexpr sign backward{CUFFT_INVERSE}; - -static_assert(forward != none and none != backward and backward != forward, "!"); - -template -struct plan { - Alloc alloc_; - ::size_t workSize_ = 0; - void* workArea_; - - using complex_type = cufftDoubleComplex; - cufftHandle h_; - std::array, DD + 1> which_iodims_{}; - int first_howmany_; - -public: - using allocator_type = Alloc; - - plan(plan&& other) : - h_{std::exchange(other.h_, {})}, - which_iodims_{other.which_iodims_}, - first_howmany_{other.first_howmany_} - {} - - template< - class ILayout, class OLayout, dimensionality_type D = std::decay_t::rank::value, - class=std::enable_if_t::rank::value> - > - plan(std::array which, ILayout const& in, OLayout const& out, allocator_type const& alloc = {}) : alloc_{alloc} { - - assert(in.sizes() == out.sizes()); - - auto const sizes_tuple = in.sizes(); - auto const istride_tuple = in.strides(); - auto const ostride_tuple = out.strides(); - - using boost::multi::detail::get; - auto which_iodims = std::apply([](auto... elems) { - return std::array, sizeof...(elems) + 1>{ // TODO(correaa) added one element to avoid problem with gcc 13 static analysis (out-of-bounds) - std::pair{ - get<0>(elems), - fftw_iodim64{get<1>(elems), get<2>(elems), get<3>(elems)} - }..., - std::pair{} - }; - }, boost::multi::detail::tuple_zip(which, sizes_tuple, istride_tuple, ostride_tuple)); - - std::stable_sort(which_iodims.begin(), which_iodims.end() - 1, [](auto const& a, auto const& b){return get<1>(a).is > get<1>(b).is;}); - - auto const part = std::stable_partition(which_iodims.begin(), which_iodims.end() - 1, [](auto elem) {return std::get<0>(elem);}); - - std::array dims{}; - auto const dims_end = std::transform(which_iodims.begin(), part, dims.begin(), [](auto elem) {return elem.second;}); - - std::array howmany_dims{}; - auto const howmany_dims_end = std::transform(part, which_iodims.end() -1, howmany_dims.begin(), [](auto elem) {return elem.second;}); - - which_iodims_ = which_iodims; - first_howmany_ = part - which_iodims.begin(); - - //////////////////////////////////////////////////////////////////////// - - std::array istrides{}; - std::array ostrides{}; - std::array ion{}; - - auto const istrides_end = std::transform(dims.begin(), dims_end, istrides.begin(), [](auto elem) {return elem.is;}); - auto const ostrides_end = std::transform(dims.begin(), dims_end, ostrides.begin(), [](auto elem) {return elem.os;}); - auto const ion_end = std::transform(dims.begin(), dims_end, ion.begin(), [](auto elem) {return elem.n;}); - - int istride = *(istrides_end -1); - auto inembed = istrides; inembed.fill(0); - int ostride = *(ostrides_end -1); - auto onembed = ostrides; onembed.fill(0); - - for(std::size_t i = 1; i != ion_end - ion.begin(); ++i) { - assert(ostrides[i-1] >= ostrides[i]); - assert(ostrides[i-1]%ostrides[i]==0); - onembed[i]=ostrides[i-1]/ostrides[i]; - assert(istrides[i-1]%istrides[i]==0); - inembed[i]=istrides[i-1]/istrides[i]; - } - - if(dims_end == dims.begin()) {throw std::runtime_error{"no ffts in any dimension is not supported"};} - - while(first_howmany_ < D - 1) { - int nelems = 1; - // for(int i = D - 1; i != first_howmany_ + 1; --i) { - // nelems *= which_iodims_[i].second.n; - // if( - // which_iodims_[i - 1].second.is == nelems and - // which_iodims_[i - 1].second.os == nelems - // ) { - // for(int j = i - 1; j != first_howmany_; --j) { - // which_iodims_[j].second.n *= which_iodims_[j + 1].second.n; - // } - // } - - // } - for(int i = first_howmany_ + 1; i != D; ++i) {nelems *= which_iodims_[i].second.n;} - if( - which_iodims_[first_howmany_].second.is == nelems and - which_iodims_[first_howmany_].second.os == nelems - ) { - which_iodims_[first_howmany_ + 1].second.n *= which_iodims_[first_howmany_].second.n; - ++first_howmany_; - } else { - break; - } - } - - if(first_howmany_ == D) { - if constexpr(std::is_same_v) { - cufftSafeCall(::cufftPlanMany( - /*cufftHandle *plan*/ &h_, - /*int rank*/ dims_end - dims.begin(), - /*int *n*/ ion.data(), - /*int *inembed*/ inembed.data(), - /*int istride*/ istride, - /*int idist*/ 1, //stride(first), - /*int *onembed*/ onembed.data(), - /*int ostride*/ ostride, - /*int odist*/ 1, //stride(d_first), - /*cufftType type*/ CUFFT_Z2Z, - /*int batch*/ 1 //BATCH - )); - } else { - cufftSafeCall(cufftCreate(&h_)); - cufftSafeCall(cufftSetAutoAllocation(h_, false)); - cufftSafeCall(cufftMakePlanMany( - /*cufftHandle *plan*/ h_, - /*int rank*/ dims_end - dims.begin(), - /*int *n*/ ion.data(), - /*int *inembed*/ inembed.data(), - /*int istride*/ istride, - /*int idist*/ 1, //stride(first), - /*int *onembed*/ onembed.data(), - /*int ostride*/ ostride, - /*int odist*/ 1, //stride(d_first), - /*cufftType type*/ CUFFT_Z2Z, - /*int batch*/ 1, //BATCH - /*size_t **/ &workSize_ - )); - cufftSafeCall(cufftGetSize(h_, &workSize_)); - workArea_ = raw_pointer_cast(alloc_.allocate(workSize_)); - // auto s = cudaMalloc(&workArea_, workSize_); - // if(s != cudaSuccess) {throw std::runtime_error{"L212"};} - cufftSafeCall(cufftSetWorkArea(h_, workArea_)); - } - if(not h_) {throw std::runtime_error{"cufftPlanMany null"};} - return; - } - - std::sort(which_iodims_.begin() + first_howmany_, which_iodims_.begin() + D, [](auto const& a, auto const& b){return get<1>(a).n > get<1>(b).n;}); - - if(first_howmany_ <= D - 1) { - if constexpr(std::is_same_v) { - cufftSafeCall(::cufftPlanMany( - /*cufftHandle *plan*/ &h_, - /*int rank*/ dims_end - dims.begin(), - /*int *n*/ ion.data(), - /*int *inembed*/ inembed.data(), - /*int istride*/ istride, - /*int idist*/ which_iodims_[first_howmany_].second.is, - /*int *onembed*/ onembed.data(), - /*int ostride*/ ostride, - /*int odist*/ which_iodims_[first_howmany_].second.os, - /*cufftType type*/ CUFFT_Z2Z, - /*int batch*/ which_iodims_[first_howmany_].second.n - )); - } else { - cufftSafeCall(cufftCreate(&h_)); - cufftSafeCall(cufftSetAutoAllocation(h_, false)); - cufftSafeCall(cufftMakePlanMany( - /*cufftHandle *plan*/ h_, - /*int rank*/ dims_end - dims.begin(), - /*int *n*/ ion.data(), - /*int *inembed*/ inembed.data(), - /*int istride*/ istride, - /*int idist*/ which_iodims_[first_howmany_].second.is, - /*int *onembed*/ onembed.data(), - /*int ostride*/ ostride, - /*int odist*/ which_iodims_[first_howmany_].second.os, - /*cufftType type*/ CUFFT_Z2Z, - /*int batch*/ which_iodims_[first_howmany_].second.n, - /*size_t **/ &workSize_ - )); - cufftSafeCall(cufftGetSize(h_, &workSize_)); - workArea_ = raw_pointer_cast(alloc_.allocate(workSize_)); - cufftSafeCall(cufftSetWorkArea(h_, workArea_)); - } - if(not h_) {throw std::runtime_error{"cufftPlanMany null"};} - ++first_howmany_; - return; - } - // throw std::runtime_error{"cufft not implemented yet"}; - } - - private: - plan() = default; - plan(plan const&) = delete; - void ExecZ2Z(complex_type const* idata, complex_type* odata, int direction) const{ - cufftSafeCall(::cufftExecZ2Z(h_, const_cast(idata), odata, direction)); - // cudaDeviceSynchronize(); - } - - public: - template - void execute(IPtr idata, OPtr odata, int direction) { - if(first_howmany_ == DD) { - ExecZ2Z((complex_type const*)::thrust::raw_pointer_cast(idata), (complex_type*)::thrust::raw_pointer_cast(odata), direction); - return; - } - if(first_howmany_ == DD - 1) { - if( which_iodims_[first_howmany_].first) {throw std::runtime_error{"logic error"};} - for(int i = 0; i != which_iodims_[first_howmany_].second.n; ++i) { - ::cufftExecZ2Z( - h_, - const_cast((complex_type const*)::thrust::raw_pointer_cast(idata + i*which_iodims_[first_howmany_].second.is)), - (complex_type *)::thrust::raw_pointer_cast(odata + i*which_iodims_[first_howmany_].second.os) , - direction - ); - } - return; - } - if(first_howmany_ == DD - 2) { - if( which_iodims_[first_howmany_ + 0].first) {throw std::runtime_error{"logic error0"};} - if( which_iodims_[first_howmany_ + 1].first) {throw std::runtime_error{"logic error1"};} - if(idata == odata) {throw std::runtime_error{"complicated inplace 2"};} - for(int i = 0; i != which_iodims_[first_howmany_].second.n; ++i) { - for(int j = 0; j != which_iodims_[first_howmany_ + 1].second.n; ++j) { - ::cufftExecZ2Z( - h_, - const_cast((complex_type const*)::thrust::raw_pointer_cast(idata + i*which_iodims_[first_howmany_].second.is + j*which_iodims_[first_howmany_ + 1].second.is)), - (complex_type *)::thrust::raw_pointer_cast(odata + i*which_iodims_[first_howmany_].second.os + j*which_iodims_[first_howmany_ + 1].second.os) , - direction - ); - } - } - return; - } - throw std::runtime_error{"error2"}; - } - - template - void operator()(IPtr idata, OPtr odata, int direction) const { - ExecZ2Z((complex_type const*)::thrust::raw_pointer_cast(idata), (complex_type*)::thrust::raw_pointer_cast(odata), direction); - } - template - O&& execute_dft(I&& i, O&& o, int direction) const { - ExecZ2Z( - const_cast(reinterpret_cast(base(i))), - const_cast(reinterpret_cast(base(o))), - direction - ); - return std::forward(o); - } - - ~plan() { - if constexpr(not std::is_same_v) { - alloc_.deallocate(typename std::allocator_traits::pointer((char*)workArea_), workSize_); - } - if(h_) {cufftSafeCall(cufftDestroy(h_));} - } - using size_type = int; - using ssize_type = int; -}; - -template -struct cached_plan { - inline static std::map, multi::layout_t, multi::layout_t>, plan > cache; - typename std::map, multi::layout_t, multi::layout_t>, plan >::iterator it; - - cached_plan(cached_plan const&) = delete; - cached_plan(cached_plan&&) = delete; - - cached_plan(std::array which, boost::multi::layout_t in, boost::multi::layout_t out, Alloc const& alloc = {}) { - it = cache.find(std::tuple, multi::layout_t, multi::layout_t>{which, in, out}); - if(it == cache.end()) {it = cache.insert(std::make_pair(std::make_tuple(which, in, out), plan(which, in, out, alloc))).first;} - } - template - void execute(IPtr idata, OPtr odata, int direction) { - assert(it != cache.end()); - it->second.execute(idata, odata, direction); - } -}; - -template::value, int> =0> -auto dft(std::array which, In const& i, Out&& o, int s) -->decltype(cufft::cached_plan{which, i.layout(), o.layout()}.execute(i.base(), o.base(), s), std::forward(o)) { - return cufft::cached_plan{which, i.layout(), o.layout()}.execute(i.base(), o.base(), s), std::forward(o); } - -template::value, int> =0> -auto dft(std::array which, In const& i, Out&& o, int s) -->decltype(cufft::cached_plan::rebind_alloc*/ >{which, i.layout(), o.layout()/*, i.get_allocator()*/}.execute(i.base(), o.base(), s), std::forward(o)) { - return cufft::cached_plan::rebind_alloc*/ >{which, i.layout(), o.layout()/*, i.get_allocator()*/}.execute(i.base(), o.base(), s), std::forward(o); } - -template//, std::enable_if_t::value, int> =0> -auto dft_forward(std::array which, In const& i, Out&& o) -> Out&& { -//->decltype(cufft::plan{which, i.layout(), o.layout()}.execute(i.base(), o.base(), cufft::forward), std::forward(o)) { - return cufft::cached_plan{which, i.layout(), o.layout()}.execute(i.base(), o.base(), cufft::forward), std::forward(o); } - -// template::value, int> =0> -// auto dft_forward(std::array which, In const& i, Out&& o) -> Out&& { -// //->decltype(cufft::plan::rebind_alloc >{which, i.layout(), o.layout(), i.get_allocator()}.execute(i.base(), o.base(), cufft::backward), std::forward(o)) { -// return cufft::cached_plan::rebind_alloc*/>{which, i.layout(), o.layout()/*, i.get_allocator()*/}.execute(i.base(), o.base(), cufft::forward), std::forward(o); } - -template//, std::enable_if_t::value, int> =0> -auto dft_backward(std::array which, In const& i, Out&& o) -> Out&& { -//->decltype(cufft::plan{which, i.layout(), o.layout()}.execute(i.base(), o.base(), cufft::backward), std::forward(o)) { - return cufft::cached_plan{which, i.layout(), o.layout()}.execute(i.base(), o.base(), cufft::backward), std::forward(o); } - -// template::value, int> =0> -// auto dft_backward(std::array which, In const& i, Out&& o) -> Out&& { -// //->decltype(cufft::plan::rebind_alloc >{which, i.layout(), o.layout(), i.get_allocator()}.execute(i.base(), o.base(), cufft::backward), std::forward(o)) { -// return cufft::cached_plan::rebind_alloc*/>{which, i.layout(), o.layout()/*, i.get_allocator()*/}.execute(i.base(), o.base(), cufft::backward), std::forward(o); } - -template()))>> -NODISCARD("when first argument is const") -R dft(In const& i, int s) { - static_assert(std::is_trivially_default_constructible{}); - R ret(extensions(i), get_allocator(i)); - cufft::dft(i, ret, s); - // if(cudaDeviceSynchronize() != cudaSuccess) throw std::runtime_error{"Cuda error: Failed to synchronize"}; - return ret; -} - -template -constexpr auto array_tail_impl(Array const& t, std::index_sequence) { - return std::array{} - 1>{std::get(t)...}; -} - -template -constexpr auto array_tail(Array const& t) -->decltype(array_tail_impl(t, std::make_index_sequence{} - 1>())) { - return array_tail_impl(t, std::make_index_sequence{} - 1>()); } - -// template1), int> = 0> -// auto dft_forward(std::array which, In const& i, Out&& o) -// ->decltype(dft(which, i, std::forward(o), cufft::forward)) { -// return dft(which, i, std::forward(o), cufft::forward); } - -// template1), int> = 0> -// auto dft_backward(std::array which, In const& i, Out&& o) -// ->decltype(dft(which, i, std::forward(o), cufft::backward)) { -// return dft(which, i, std::forward(o), cufft::backward); } - -template -NODISCARD("when passing a const argument") -auto dft(std::array which, In const& i, int sign)->std::decay_t{return -dft(which, i, typename In::decay_type(extensions(i), get_allocator(i)), sign);} - -template -auto dft(std::array which, In&& i, int sign) -->decltype(dft(which, i, i, sign), std::forward(i)){ - return dft(which, i, i, sign), std::forward(i);} - -template NODISCARD("when passing a const argument") -auto dft_forward(Array arr, A const& a) -->decltype(cufft::dft(arr, a, cufft::forward)){ - return cufft::dft(arr, a, cufft::forward);} - -// template NODISCARD("when passing a const argument") -// auto dft_forward(Array arr, multi::cuda::array, D>&& a) -// ->decltype(cufft::dft(arr, a, cufft::forward), multi::cuda::array, D>{}){//assert(0); -// return cufft::dft(arr, a, cufft::forward), std::move(a);} - -template NODISCARD("when passing a const argument") -auto dft_forward(A const& a) -->decltype(cufft::dft(a, cufft::forward)){ - return cufft::dft(a, cufft::forward);} - -template auto dft_backward(A&&... a) -->decltype(cufft::dft(std::forward(a)..., cufft::backward)){ - return cufft::dft(std::forward(a)..., cufft::backward);} - -template NODISCARD("when passing a const argument") -auto dft_backward(Array arr, A const& a) -->decltype(cufft::dft(arr, a, cufft::backward)){ - return cufft::dft(arr, a, cufft::backward);} - -template NODISCARD("when passing a const argument") -auto dft_backward(A const& a) -->decltype(cufft::dft(a, cufft::backward)){ - return cufft::dft(a, cufft::backward);} - -} - -}} -#endif diff --git a/include/multi/adaptors/cufft/test/CMakeLists.txt b/include/multi/adaptors/cufft/test/CMakeLists.txt deleted file mode 100644 index 432b1c519..000000000 --- a/include/multi/adaptors/cufft/test/CMakeLists.txt +++ /dev/null @@ -1,130 +0,0 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- -#[=[Multi Test suite can be run like this: - mkdir -p build - cd build - cmake .. [-DENABLE_CUDA=1] - make -j - ctest -j --output-on-error [-T memcheck] - exit -#]=] -cmake_minimum_required(VERSION 3.11) - -set(CMAKE_VERBOSE_MAKEFILE ON) - -project( - boost-multi-adaptors-blas-test - VERSION 0.1 - LANGUAGES CXX -) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -if(NOT - CMAKE_CXX_COMPILER_ID - STREQUAL - "PGI" -) - find_package(Boost REQUIRED COMPONENTS unit_test_framework) -else() - find_package(Boost REQUIRED) # cmake cannot detect this component with pgi compiler -endif() -link_libraries("-lboost_unit_test_framework") - -if(ENABLE_CUDA OR DEFINED CXXCUDA) - enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --extended-lambda") - find_package(CUDA QUIET) - if(CUDA_FOUND) - message("CUDA found") - include_directories(${CUDA_INCLUDE_DIRS}) - set(INQ_CUDA_LIBRARIES ${CUDA_CUFFT_LIBRARIES} ${CUDA_cusolver_LIBRARY} ${CUDA_CUBLAS_LIBRARIES}) - link_libraries(${INQ_CUDA_LIBRARIES}) - else() - message("CUDA not found") - endif() -endif() - -# FFTW has to go before blas to avoid unscrupulous (i.e. MKL) blas implementations that include FFTW and don't implement it properly -find_package(PkgConfig REQUIRED) -pkg_search_module( - FFTW - REQUIRED - fftw3 - IMPORTED_TARGET -) -include_directories(PkgConfig::FFTW) -link_libraries(PkgConfig::FFTW) - -enable_testing() -list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # needs cmake 3.17 -include(CTest) - -include_directories(${CMAKE_BINARY_DIR}) - -# file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) -set(TEST_SRCS cufft.cpp) - -foreach(TEST_FILE ${TEST_SRCS}) - set(TEST_EXE "${TEST_FILE}.x") - add_executable(${TEST_EXE} ${TEST_FILE}) - if(ENABLE_CUDA OR DEFINED CXXCUDA) - set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) - target_compile_options(${TEST_EXE} PRIVATE -std=c++17) - endif() - # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") # needed by Boost.Test and NVCC - target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) - target_include_directories(${TEST_EXE} PRIVATE ${Boost_INCLUDE_DIRS}) - target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) - target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) - target_link_libraries(${TEST_EXE} PRIVATE multi) - - if(NOT ENABLE_CUDA) - target_compile_options( - ${TEST_EXE} - PRIVATE -Werror - -Wall - $<$: - -Wextra - -Wpedantic - -Wformat-truncation> # -Wconversion - $<$,$>: - -Wextra - -Wpedantic - -Wmove> - $<$: - -Wextra - -wd161 - -diag-disable=remark - -Warray-bounds - -Wchar-subscripts - -Wcomment - -Wenum-compare - -Wformat - -Wuninitialized - -Wmaybe-uninitialized - -Wmain - -Wnarrowing - -Wnonnull - -Wparentheses - -Wpointer-sign - -Wreorder - -Wno-return-type - -Wsign-compare - -Wsequence-point - -Wtrigraphs - -Wunused-function - -Wunused-but-set-variable - -Wunused-variable - -Wwrite-strings - -diag-error:3846> - $<$: # PGI 20 doesn't recognize the -Wextra option - > - $<$: - /W4> - ) - endif() - add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) -endforeach() diff --git a/include/multi/adaptors/cufft/test/cufft.cpp b/include/multi/adaptors/cufft/test/cufft.cpp deleted file mode 100644 index 37dda0a94..000000000 --- a/include/multi/adaptors/cufft/test/cufft.cpp +++ /dev/null @@ -1,633 +0,0 @@ -// © Alfredo A. Correa 2020-2023 - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuFFT adaptor" -#include - -#include - -// #include "../../../adaptors/cuda.hpp" -#include "../../../adaptors/fft.hpp" -#include "../../../adaptors/fftw.hpp" -#include "../../../adaptors/cufft.hpp" -#include "../../../adaptors/thrust.hpp" - -#include -#include "../../../complex.hpp" - -#include // cudaDeviceSynchronize - -#include -#include -#include -#include - -namespace multi = boost::multi; -using complex = thrust::complex; -namespace utf = boost::unit_test; -complex const I{0.0, 1.0}; - -template<> -constexpr bool multi::force_element_trivial_default_construction> = true; - -template -__attribute__((always_inline)) inline void DoNotOptimize(const T &value) { - asm volatile("" : "+m"(const_cast(value))); -} - -struct watch : private std::chrono::high_resolution_clock{ - std::string label_; time_point start_; - watch(std::string label ="") : label_{label}, start_{}{ - cudaDeviceSynchronize(); - start_ = now(); - } - ~watch(){ - cudaDeviceSynchronize(); - auto const count = std::chrono::duration(now() - start_).count(); - std::cerr<< label_<<": "<< count <<" sec"<; // this can't be std::complex in the gpu - - auto const in_cpu = multi::array{ - { 1.0 + 2.0*I, 9.0 - 1.0*I, 2.0 + 4.0*I}, - { 3.0 + 3.0*I, 7.0 - 4.0*I, 1.0 + 9.0*I}, - { 4.0 + 1.0*I, 5.0 + 3.0*I, 2.0 + 4.0*I}, - { 3.0 - 1.0*I, 8.0 + 7.0*I, 2.0 + 1.0*I}, - { 31.0 - 1.0*I, 18.0 + 7.0*I, 2.0 + 10.0*I} - }; - - { - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft({true, true}, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); - - BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); - BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); - - multi::cufft::plan<2>({true, true}, in_gpu.layout(), fw_gpu.layout()) - .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); - - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).imag() == 0.0 ); - } - { - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft({false, true}, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); - - BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); - BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); - - multi::cufft::plan<2>({false, true}, in_gpu.layout(), fw_gpu.layout()) - .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); - - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).imag() == 0.0 ); - } - { - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft({false, true}, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); - - BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); - BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); - - for(int i = 0; i != in_gpu.size(); ++i) { - multi::cufft::plan<1>({true}, in_gpu[i].layout(), fw_gpu[i].layout()) - .execute(in_gpu[i].base(), fw_gpu[i].base(), multi::cufft::forward); - } - - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).imag() == 0.0 ); - } - { - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft({false, true}, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); - auto fw_gpu2 = multi::thrust::cuda::array(extensions(in_gpu)); - auto fw_gpu3 = multi::thrust::cuda::array(extensions(in_gpu)); - - BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); - BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); - - for(int i = 0; i != in_gpu.size(); ++i) { - multi::cufft::plan<1>({true}, in_gpu[i].layout(), fw_gpu[i].layout()) - .execute(in_gpu[i].base(), fw_gpu[i].base(), multi::cufft::forward); - } - - multi::cufft::plan<2>({false, true}, in_gpu.layout(), fw_gpu2.layout()) - .execute(in_gpu.base(), fw_gpu2.base(), multi::cufft::forward); - - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).imag() == 0.0 ); - - BOOST_TEST( (complex(fw_gpu[3][2]) - complex(fw_gpu2[3][2])).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2]) - complex(fw_gpu2[3][2])).imag() == 0.0 ); - } - { - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft({false, true}, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto const fw_gpu = multi::cufft::dft({false, true}, in_gpu, multi::cufft::forward); - - BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); - BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); - - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).imag() == 0.0 ); - - BOOST_TEST( (complex(fw_gpu[2][3]) - fw_cpu[2][3]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[2][3]) - fw_cpu[2][3]).imag() == 0.0 ); - } - { - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft({true, false}, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto const fw_gpu = multi::cufft::dft({true, false}, in_gpu, multi::cufft::forward); - - BOOST_TEST( fw_cpu[3][2].real() != 0.0 ); - BOOST_TEST( fw_cpu[3][2].imag() != 0.0 ); - - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2]) - fw_cpu[3][2]).imag() == 0.0 ); - - BOOST_TEST( (complex(fw_gpu[2][3]) - fw_cpu[2][3]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[2][3]) - fw_cpu[2][3]).imag() == 0.0 ); - } -} - -BOOST_AUTO_TEST_CASE(cufft_1D_combinations, *boost::unit_test::tolerance(0.0001)){ - - using complex = thrust::complex; // this can't be std::complex in the gpu - - auto const in_cpu = std::invoke([]{ - multi::array ret({128}, complex{}); - std::default_random_engine generator; - std::uniform_real_distribution distribution(1.0, 88.0); - - std::generate( - reinterpret_cast(ret.data_elements()), - reinterpret_cast(ret.data_elements() + ret.num_elements()), [&]{return distribution(generator);} - ); - return ret; - }); - - for(auto c : std::vector>{ - {true} //, - // {false}, - }){ - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft(c, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); - - BOOST_TEST( fw_cpu[31].real() != 0.0 ); - BOOST_TEST( fw_cpu[31].imag() != 0.0 ); - - multi::cufft::plan<1>(c, in_gpu.layout(), fw_gpu.layout()) - .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); - - BOOST_TEST( complex(fw_gpu[31]).real() == fw_cpu[31].real() ); - BOOST_TEST( complex(fw_gpu[31]).imag() == fw_cpu[31].imag() ); - } -} - -BOOST_AUTO_TEST_CASE(cufft_2D_combinations, *boost::unit_test::tolerance(0.0001)){ - - using complex = thrust::complex; // this can't be std::complex in the gpu - - auto const in_cpu = std::invoke([]{ - multi::array ret({10, 20}); - std::default_random_engine generator; - std::uniform_real_distribution distribution(-1.0, 1.0); - - std::generate( - reinterpret_cast(ret.data_elements()), - reinterpret_cast(ret.data_elements() + ret.num_elements()), [&]{return distribution(generator);} - ); - return ret; - }); - - for(auto c : std::vector>{ - {true , true }, - {true , false}, - {false, true }//, - // {false, false} - }){ - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft(c, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); - - BOOST_TEST( fw_cpu[2][1].real() != 0.0 ); - BOOST_TEST( fw_cpu[2][1].imag() != 0.0 ); - - multi::cufft::plan<2>(c, in_gpu.layout(), fw_gpu.layout()) - .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); - - BOOST_TEST( (complex(fw_gpu[2][1]) - fw_cpu[2][1]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[2][1]) - fw_cpu[2][1]).imag() == 0.0 ); - } -} - -BOOST_AUTO_TEST_CASE(cufft_2D_combinations_inplace, *boost::unit_test::tolerance(0.0001)){ - - using complex = thrust::complex; // this can't be std::complex in the gpu - - auto const in_cpu = std::invoke([]{ - multi::array ret({10, 20}); - std::default_random_engine generator; - std::uniform_real_distribution distribution(-1.0, 1.0); - - std::generate( - reinterpret_cast(ret.data_elements()), - reinterpret_cast(ret.data_elements() + ret.num_elements()), [&]{return distribution(generator);} - ); - return ret; - }); - - for(auto c : std::vector>{ - {true , true }, - {true , false}, - {false, true }//, - // {false, false} - }){ - auto fw_cpu = in_cpu; - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - - multi::fftw::dft(c, fw_cpu, multi::fftw::forward); - - auto fw_gpu = in_gpu; - - BOOST_TEST( fw_cpu[2][1].real() != 0.0 ); - BOOST_TEST( fw_cpu[2][1].imag() != 0.0 ); - - multi::cufft::plan<2>(c, fw_gpu.layout(), fw_gpu.layout()) - .execute(fw_gpu.base(), fw_gpu.base(), multi::cufft::forward); - - BOOST_TEST( (complex(fw_gpu[2][1]) - fw_cpu[2][1]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[2][1]) - fw_cpu[2][1]).imag() == 0.0 ); - } -} - -BOOST_AUTO_TEST_CASE(cufft_3D, *boost::unit_test::tolerance(0.0001)){ - - using complex = thrust::complex; // this can't be std::complex in the gpu - - auto const in_cpu = std::invoke([]{ - multi::array ret({10, 20, 30}); - std::default_random_engine generator; - std::uniform_real_distribution distribution(-1.0, 1.0); - - std::generate( - reinterpret_cast(ret.data_elements()), - reinterpret_cast(ret.data_elements() + ret.num_elements()), [&]{return distribution(generator);} - ); - return ret; - }); - - for(auto c : std::vector>{ - {true , true , true }, - {true , true , false}, - {true , false, true }, - {true , false, false}, - {false, true , true }, - {false, true , false}, - {false, false, true }//, - // {false, false, false} - }){ - auto fw_cpu = multi::array(extensions(in_cpu)); - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - - multi::fftw::dft(c, in_cpu, fw_cpu, multi::fftw::forward); - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); - - multi::cufft::dft(c, in_gpu, fw_gpu, multi::cufft::forward); - - BOOST_TEST( fw_cpu[3][2][1].real() != 0.0 ); - BOOST_TEST( fw_cpu[3][2][1].imag() != 0.0 ); - - BOOST_TEST( (complex(fw_gpu[3][2][1]) - fw_cpu[3][2][1]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2][1]) - fw_cpu[3][2][1]).imag() == 0.0 ); - } -} - -BOOST_AUTO_TEST_CASE(cufft_3D_inplace, *boost::unit_test::tolerance(0.0001)){ - - using complex = thrust::complex; // this can't be std::complex in the gpu - - auto const in_cpu = std::invoke([]{ - multi::array ret({10, 20, 30}); - std::default_random_engine generator; - std::uniform_real_distribution distribution(-1.0, 1.0); - - std::generate( - reinterpret_cast(ret.data_elements()), - reinterpret_cast(ret.data_elements() + ret.num_elements()), [&]{return distribution(generator);} - ); - return ret; - }); - - for(auto c : std::vector>{ - {true , true , true }, - {true , true , false}, - {true , false, true }, - {true , false, false}, - {false, true , true }, - {false, true , false}, - {false, false, true }//, - // {false, false, false} - }){ - auto fw_cpu = in_cpu; - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - - multi::fftw::dft(c, fw_cpu, multi::fftw::forward); - auto fw_gpu = in_gpu; - - multi::cufft::plan<3>(c, fw_gpu.layout(), fw_gpu.layout()) - .execute(fw_gpu.base(), fw_gpu.base(), multi::cufft::forward); - - BOOST_TEST( fw_cpu[3][2][1].real() != 0.0 ); - BOOST_TEST( fw_cpu[3][2][1].imag() != 0.0 ); - - std::cerr << "case " << c[0] << " " << c[1] << " " << c[2] << std::endl; - - BOOST_TEST( (complex(fw_gpu[3][2][1]) - fw_cpu[3][2][1]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[3][2][1]) - fw_cpu[3][2][1]).imag() == 0.0 ); - } -} - -BOOST_AUTO_TEST_CASE(cufft_4D, *boost::unit_test::tolerance(0.0001)){ - - using complex = thrust::complex; // this can't be std::complex in the gpu - - auto const in_cpu = std::invoke([]{ - multi::array ret({10, 20, 30, 40}); - std::default_random_engine generator; - std::uniform_real_distribution distribution(-1.0, 1.0); - - std::generate( - reinterpret_cast(ret.data_elements()), - reinterpret_cast(ret.data_elements() + ret.num_elements()), [&]{return distribution(generator);} - ); - return ret; - }); - - for(auto c : std::vector>{ - // {true , true , true , true }, - {true , true , true , false}, - {true , true , false, true }, - {true , true , false, false}, - {true , false, true , true }, - {true , false, true , false}, - {true , false, false, true }, - {true , false, false, false}, - {false, true , true , true }, - {false, true , true , false}, - {false, true , false, true }, - {false, true , false, false}, - {false, false, true , true }, - {false, false, true , false}, - {false, false, false, true }//, - // {false, false, false, false} - }){ - auto fw_cpu = multi::array(extensions(in_cpu)); - multi::fftw::dft(c, in_cpu, fw_cpu, multi::fftw::forward); - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu)); - - BOOST_TEST( fw_cpu[4][3][2][1].real() != 0.0 ); - BOOST_TEST( fw_cpu[4][3][2][1].imag() != 0.0 ); - - multi::cufft::plan<4>(c, in_gpu.layout(), fw_gpu.layout()) - .execute(in_gpu.base(), fw_gpu.base(), multi::cufft::forward); - - BOOST_TEST( (complex(fw_gpu[4][3][2][1]) - fw_cpu[4][3][2][1]).real() == 0.0 ); - BOOST_TEST( (complex(fw_gpu[4][3][2][1]) - fw_cpu[4][3][2][1]).imag() == 0.0 ); - } -} - -BOOST_AUTO_TEST_CASE(cufft_3D_timing, *boost::unit_test::tolerance(0.0001)){ - - auto x = multi::extensions_t<3>{300, 300, 300}; - { - auto const in_cpu = multi::array(x, 10.0); - BOOST_ASSERT( in_cpu.num_elements()*sizeof(complex) < 2e9 ); - auto fw_cpu = multi::array(extensions(in_cpu), 99.0); - { - // boost::timer::auto_cpu_timer t; // 1.041691s wall, 1.030000s user + 0.000000s system = 1.030000s CPU (98.9%) - multi::fftw::dft_forward({true, true}, in_cpu, fw_cpu); - BOOST_TEST( fw_cpu[8][9][10] != 99.0 ); - } - - auto const in_gpu = multi::thrust::cuda::array{in_cpu}; // (x, 10.0); - cudaDeviceSynchronize(); - { - auto fw_gpu = multi::thrust::cuda::array(extensions(in_gpu), 99.0); - cudaDeviceSynchronize(); - // boost::timer::auto_cpu_timer t; // 0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%) - boost::multi::cufft::dft({true, true}, in_gpu, fw_gpu, multi::cufft::forward); - cudaDeviceSynchronize(); - BOOST_TEST( (static_cast(fw_gpu[8][9][10]) - fw_cpu[8][9][10]).real() == 0.0 ); - BOOST_TEST( (static_cast(fw_gpu[8][9][10]) - fw_cpu[8][9][10]).imag() == 0.0 ); - } - { - // boost::timer::auto_cpu_timer t; // 0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%) - auto const fw_gpu2 = boost::multi::cufft::dft({true, true}, in_gpu, multi::cufft::forward); - cudaDeviceSynchronize(); - BOOST_TEST( (static_cast(fw_gpu2[8][9][10]) - fw_cpu[8][9][10]).real() == 0.0 ); - BOOST_TEST( (static_cast(fw_gpu2[8][9][10]) - fw_cpu[8][9][10]).imag() == 0.0 ); - } - } - -#if 1 - { - multi::thrust::cuda::universal_array const in_gpu(x, 10.); - multi::thrust::cuda::universal_array fw_gpu(extensions(in_gpu), 99.); - - // multi::cuda::managed::array const in_gpu(x, 10.); - // multi::cuda::managed::array fw_gpu(extensions(in_gpu), 99.); - { - // boost::timer::auto_cpu_timer t; // 0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%) - multi::cufft::dft({true, true}, in_gpu, fw_gpu, multi::cufft::forward); - // BOOST_TEST( fw_gpu[8][9][10].operator complex() != 99. ); - } - { - // boost::timer::auto_cpu_timer t; // 0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%) - multi::cufft::dft({true, true}, in_gpu, fw_gpu, multi::cufft::forward); - // BOOST_TEST( fw_gpu[8][9][10].operator complex() != 99. ); - } - } -#endif -} - -#if 0 - -BOOST_AUTO_TEST_CASE(cufft_combinations, *utf::tolerance(0.00001)){ - - auto const in = []{ - multi::array ret({32, 90, 98, 96}); - std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), - [](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};} - ); - return ret; - }(); - std::clog<<"memory size "<< in.num_elements()*sizeof(complex)/1e6 <<" MB\n"; - - multi::thrust::cuda::universal_array const in_gpu = in; - multi::thrust::cuda::universal_array const in_mng = in; - - using std::clog; - for(auto c : std::vector>{ - {false, true , true , true }, - {false, true , true , false}, - {true , false, false, false}, - {true , true , false, false}, - {false, false, true , false}, - {false, false, false, false}, - }){ - std::clog<<"case "; copy(begin(c), end(c), std::ostream_iterator{std::clog,", "}); std::clog< out = in; - multi::array in_rw = in; - [&, _ = watch{"cpu_opl "}]{ - multi::fftw::dft_forward(c, in, out); - }(); - [&, _ = watch{"cpu_ipl "}]{ - multi::fftw::dft(c, in_rw, multi::fftw::forward); - // BOOST_TEST( abs( static_cast>(in_rw[5][4][3][1]) - multi::complex(out[5][4][3][1]) ) == 0. ); - }(); - { - multi::array in_rw2 = in; - [&, _ = watch{"cpu_mov "}]{ - multi::array const out_mov = multi::fftw::dft_forward(c, std::move(in_rw2)); - // what(out_mov); - // BOOST_TEST( abs( static_cast>(out_mov[5][4][3][1]) - multi::complex(out[5][4][3][1]) ) == 0. ); - BOOST_REQUIRE( is_empty(in_rw2) ); - BOOST_REQUIRE( extensions(out_mov) == extensions(in) ); - }(); - } - - - [&, _ = watch{"cpu_new "}]{ - auto const out_cpy = multi::fftw::dft_forward(c, in); - BOOST_TEST( abs( static_cast>(out_cpy[5][4][3][1]) - std::complex(out[5][4][3][1]) ) == 0. ); - }(); - multi::thrust::cuda::array out_gpu(extensions(in_gpu)); - [&, _ = watch{"gpu_opl "}]{ - multi::cufft::dft(c, in_gpu , out_gpu, multi::cufft::forward); - BOOST_TEST( abs( static_cast(out_gpu[5][4][3][1]) - out[5][4][3][1] ) == 0. ); - }(); - { - multi::thrust::cuda::array in_rw_gpu = in_gpu; - [&, _ = watch{"gpu_ipl "}]{ - multi::cufft::dft(c, in_rw_gpu, multi::cufft::forward); - BOOST_TEST( abs( static_cast(in_rw_gpu[5][4][3][1]) - out[5][4][3][1] ) == 0. ); - }(); - } - { - multi::thrust::cuda::array in_rw_gpu = in_gpu; - [&, _ = watch{"gpu_mov "}]{ - multi::thrust::cuda::array const out_mov = multi::cufft::dft_forward(c, std::move(in_rw_gpu)); - // BOOST_REQUIRE( in_rw_gpu.empty() ); - // BOOST_TEST( abs( static_cast(out_mov[5][4][3][1]) - out[5][4][3][1] ) == 0. ); - }(); - } - { - multi::thrust::cuda::array in_rw_gpu = in_gpu; - [&, _ = watch{"gpu_mov "}]{ - multi::thrust::cuda::array out_mov = std::move(in_rw_gpu); - multi::cufft::dft(c, out_mov, multi::cufft::forward); - // BOOST_REQUIRE( in_rw_gpu.empty() ); - // BOOST_TEST( abs( static_cast(out_mov[5][4][3][1]) - out[5][4][3][1] ) == 0. ); - }(); - } - cudaDeviceSynchronize(); - [&, _ = watch{"gpu_new "}]{ - multi::thrust::cuda::array const out_cpy = multi::cufft::dft(c, in_gpu, multi::cufft::forward); - }(); - multi::thrust::cuda::universal_array out_mng(extensions(in_mng)); - [&, _ = watch{"mng_cld "}]{ - multi::cufft::dft(c, in_mng, out_mng, multi::cufft::forward); - BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. ); - }(); - [&, _ = watch{"mng_hot "}]{ - multi::cufft::dft(c, in_mng , out_mng, multi::cufft::forward); - BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. ); - }(); - [&, _ = watch{"mng_new "}]{ - auto const out_mng = multi::cufft::dft(c, in_mng, multi::cufft::forward); - BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. ); - }(); - } - // std::clog<<"cache size " - // << multi::cufft::plan::cache<1>().size() <<' ' - // << multi::cufft::plan::cache<2>().size() <<' ' - // << multi::cufft::plan::cache<3>().size() <<' ' - // << multi::cufft::plan::cache<4>().size() <<' ' - // < ret({45, 18, 32, 16}); - std::generate( - ret.data_elements(), ret.data_elements() + ret.num_elements(), - [](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};} - ); - return ret; - }(); - - multi::thrust::cuda::array const in = in_cpu; - multi::thrust::cuda::array out(extensions(in)); - -#if 0 - multi::cufft::many_dft(begin(unrotated(in)), end(unrotated(in)), begin(unrotated(out)), +1); - - multi::array out_cpu(extensions(in)); - multi::fft::many_dft(begin(unrotated(in_cpu)), end(unrotated(in_cpu)), begin(unrotated(out_cpu)), +1); - - BOOST_TEST( imag( static_cast(out[5][4][3][2]) - out_cpu[5][4][3][2]) == 0. ); -#endif -} - -#if 0 -BOOST_AUTO_TEST_CASE(cufft_4D, *utf::tolerance(0.00001) ){ - auto const in = []{ - multi::array ret({10, 10, 10}); - std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), - [](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};} - ); - return ret; - }(); - - multi::array out(extensions(in)); -// multi::fftw::dft({true, false, true}, in, out, multi::fftw::forward); - multi::fftw::many_dft(begin(in.rotated()), end(in.rotated()), begin(out.rotated()), multi::fftw::forward); - - multi::thrust::cuda::array in_gpu = in; - multi::thrust::cuda::array out_gpu(extensions(in)); - -// multi::cufft::dft({true, false, true}, in_gpu, out_gpu, multi::fft::forward);//multi::cufft::forward); - // multi::cufft::many_dft(begin(in_gpu.rotated()), end(in_gpu.rotated()), begin( out_gpu.rotated() ), multi::fftw::forward); - // BOOST_TEST( ( static_cast(out_gpu[5][4][3]) - out[5][4][3]).imag() == 0. ); -} -#endif - -#endif diff --git a/include/multi/adaptors/fft.hpp b/include/multi/adaptors/fft.hpp deleted file mode 100644 index 38c6cc2d7..000000000 --- a/include/multi/adaptors/fft.hpp +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2020-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_FFT_HPP -#define MULTI_ADAPTORS_FFT_HPP - -#include "../adaptors/fftw.hpp" -#include "../adaptors/cufft.hpp" - -namespace boost{ -namespace multi{ -namespace fft{ - - static constexpr int forward = fftw::forward;//FFTW_FORWARD; - static constexpr int none = 0; - static constexpr int backward = fftw::backward;//FFTW_BACKWARD; - - static_assert( forward != none and none != backward and backward != forward, "!"); - - template struct priority : std::conditional_t>{}; - - template auto dft_aux_(priority<0>, Args&&... args) DECLRETURN( fftw::dft_backward(std::forward(args)...)) - template auto dft_aux_(priority<1>, Args&&... args) DECLRETURN(cufft ::dft_backward(std::forward(args)...)) - template auto dft(Args&&... args) DECLRETURN(dft_backward_aux_(priority<1>{}, std::forward(args)...)) - template auto dft(std::array::dimensionality> which, In const& in, Args&&... args) -> decltype(auto) {return dft_aux_(priority<1>{}, which, in, std::forward(args)...);} - - template auto dft_forward_aux_(priority<0>, Args&&... args) DECLRETURN( fftw::dft_forward(std::forward(args)...)) - template auto dft_forward_aux_(priority<1>, Args&&... args) DECLRETURN(cufft ::dft_forward(std::forward(args)...)) - template auto dft_forward(std::array which, In const& in, Args&&... args) -> decltype(auto) {return dft_forward_aux_(priority<1>{}, which, in, std::forward(args)...);} - - template auto dft_backward_aux_(priority<0>, Args&&... args) DECLRETURN( fftw::dft_backward(std::forward(args)...)) - template auto dft_backward_aux_(priority<1>, Args&&... args) DECLRETURN(cufft ::dft_backward(std::forward(args)...)) - template auto dft_backward(std::array which, In const& in, Args&&... args) -> decltype(auto) {return dft_backward_aux_(priority<1>{}, which, in, std::forward(args)...);} - -}}} - -#if not __INCLUDE_LEVEL__ - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFT adaptor" -#define BOOST_TEST_DYN_LINK -#include - -#include -#include - -namespace utf = boost::unit_test; - -using complex = std::complex; -namespace multi = boost::multi; - -using std::cout; - -BOOST_AUTO_TEST_CASE(fft_combinations, *utf::tolerance(0.00001)){ - cout<< "# threads is " << multi::fftw::plan::with_nthreads() <<"\n"; - cout<<"=========================================================\n"; - cout<< BOOST_PLATFORM <<' '<< BOOST_COMPILER <<' '<< __DATE__<<'\n'; - - auto const in = []{ - multi::array ret({32, 90, 98, 96}); - std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), - [](){return complex{std::rand()/1./RAND_MAX, std::rand()/1./RAND_MAX};} - ); - return ret; - }(); - std::cout<<"memory size "<< in.num_elements()*sizeof(complex)/1e6 <<" MB\n"; - - multi::cuda::array const in_gpu = in; - multi::cuda::managed::array const in_mng = in; - - std::vector> cases = { - {false, true , true , true }, - {false, true , true , false}, - {true , false, false, false}, - {true , true , false, false}, - {false, false, true , false}, - {false, false, false, false}, - }; - - for(auto c : cases){ - cout<<"case: "<{cout,", "}); cout<<"\n"; - - multi::array out(extensions(in)); - { - cout<<"flops "<< multi::fftw::plan(c, in, out, multi::fft::forward).flops() <<"\n"; - boost::timer::auto_cpu_timer t{"cpu____ %ws wall, CPU (%p%)\n"}; - multi::fft::dft(c, in, out, multi::fft::forward); - } - { - boost::timer::auto_cpu_timer t{"cpu_hot %ws wall, CPU (%p%)\n"}; - multi::fft::dft(c, in, out, multi::fft::forward); - } - multi::cuda::array out_gpu(extensions(in_gpu)); - { - boost::timer::auto_cpu_timer t{"gpu_cld %ws wall, CPU (%p%)\n"}; - multi::fft::dft(c, in_gpu , out_gpu , multi::fft::forward); - BOOST_TEST( abs( static_cast(out_gpu[5][4][3][1]) - out[5][4][3][1] ) == 0. ); - } - { - boost::timer::auto_cpu_timer t{"gpu_hot %ws wall, CPU (%p%)\n"}; - multi::fft::dft(c, in_gpu , out_gpu , multi::fft::forward); -// BOOST_TEST( abs( static_cast(out_gpu[5][4][3][1]) - out[5][4][3][1] ) == 0. ); - } - multi::cuda::managed::array out_mng(extensions(in_mng)); - { - boost::timer::auto_cpu_timer t{"mng_cld %ws wall, CPU (%p%)\n"}; - multi::fft::dft(c, in_mng , out_mng , multi::fft::forward); - cudaDeviceSynchronize(); - BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. ); - } - { - /// boost::timer::auto_cpu_timer t{"mng_hot %ws wall, CPU (%p%)\n"}; - multi::fft::dft(c, in_mng() , out_mng() , multi::fft::forward); - cudaDeviceSynchronize(); - BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. ); - } - } - -} -#endif -#endif diff --git a/include/multi/adaptors/fftw.hpp b/include/multi/adaptors/fftw.hpp deleted file mode 100644 index 7038a0b25..000000000 --- a/include/multi/adaptors/fftw.hpp +++ /dev/null @@ -1,876 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_FFTW_HPP -#define MULTI_ADAPTORS_FFTW_HPP -#pragma once - -#include "../adaptors/../array.hpp" - -#include "../detail/tuple_zip.hpp" - -#include // sort -#include -#include // accumulate - -#if HAVE_FFTW3_THREADS -#include -#endif - -#include // external fftw3 library - -namespace boost::multi { - -namespace fftw { -// template auto alignment_of(T* p){return ::fftw_alignment_of((double*)p);} -#if __cpp_lib_as_const >= 201510 -using std::as_const; -#else -template constexpr std::add_const_t& as_const(T& t) noexcept{return t;} -#endif - -struct flags { - using underlying_type = decltype(FFTW_PRESERVE_INPUT); // NOLINT(hicpp-signed-bitwise) : macro definition in external library - - private: - underlying_type underlying_; - - public: - constexpr explicit flags(underlying_type underlying) : underlying_{underlying}{} - constexpr explicit operator underlying_type() const{return underlying_;} - friend constexpr auto operator|(flags f1, flags f2){return flags{f1.underlying_ | f2.underlying_};} -}; - - -constexpr flags estimate {FFTW_ESTIMATE }; // NOLINT(hicpp-signed-bitwise) : defined in an external lib 1U << 6 -constexpr flags measure {FFTW_MEASURE }; - -constexpr flags preserve_input{FFTW_PRESERVE_INPUT}; // NOLINT(hicpp-signed-bitwise) : defined in an external lib 1U << 4 -// // NOLINT(): this is a defect in FFTW https://github.com/FFTW/fftw3/issues/246 - -} // end namespace fftw - -#if 0 -template -auto fftw_plan_dft_1d( - Size N, - std::complex const* in, std::complex* out, int sign, - unsigned flags = FFTW_ESTIMATE -){ -#ifndef NDEBUG - auto check = in[N/3]; // check that const data will not been overwritten -#endif - assert( fftw::alignment_of(in) == fftw::alignment_of(out) ); - auto ret=::fftw_plan_dft_1d(N, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT ); - assert(check == in[N/3]); // check that const data has not been overwritten - return ret; -} - -template -auto fftw_plan_dft_1d( - Size N, - std::complex* in, std::complex* out, int sign, - unsigned flags = FFTW_ESTIMATE -){ - assert( fftw::alignment_of(in) == fftw::alignment_of(out) ); - return ::fftw_plan_dft_1d(N, (fftw_complex*)in, (fftw_complex*)out, sign, flags); -} - -template -auto fftw_plan_dft_2d( - Size N1, Size N2, - std::complex const* in, std::complex* out, int sign, - unsigned flags = FFTW_ESTIMATE -){ - assert( fftw::alignment_of(in) == fftw::alignment_of(out) ); -#ifndef NDEBUG - auto check = in[N1*N2/3]; // check that const data will not been overwritten -#endif - auto ret = ::fftw_plan_dft_2d(N1, N2, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT); - assert( check == in[N1*N2/3] ); // check that const data has not been overwritten - return ret; -} - -template -auto fftw_plan_dft_2d( - Size N1, Size N2, - std::complex* in, std::complex* out, int sign, - unsigned flags = FFTW_ESTIMATE -){ - assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out)); - return ::fftw_plan_dft_2d(N1, N2, (fftw_complex*)in, (fftw_complex*)out, sign, flags); -} - -template -auto fftw_plan_dft_3d( - Size N1, Size N2, Size N3, - std::complex* in, std::complex* out, int sign, - unsigned flags = FFTW_ESTIMATE -){ - assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out)); - return ::fftw_plan_dft_3d(N1, N2, N3, (fftw_complex*)in, (fftw_complex*)out, sign, flags); -} -template -auto fftw_plan_dft_3d( - Size N1, Size N2, Size N3, - std::complex const* in, std::complex* out, int sign, - unsigned flags = FFTW_ESTIMATE -){ - assert( flags & FFTW_PRESERVE_INPUT ); - assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out)); - return ::fftw_plan_dft_3d(N1, N2, N3, (fftw_complex*)in, (fftw_complex*)out, sign, flags | FFTW_PRESERVE_INPUT); -} -#endif - -#if 0 -template -auto fftw_plan_dft( - Rank r, int* ns, - std::complex* in, std::complex* out, - int sign, unsigned flags = FFTW_ESTIMATE -){ - assert(fftw_alignment_of((double*)in) == fftw_alignment_of((double*)out)); - return ::fftw_plan_dft(r, ns, (fftw_complex*)in, (fftw_complex*)out, sign, flags); -} -template -auto fftw_plan_dft( - RankType r, int* ns, - std::complex const* in, std::complex* out, - int sign, unsigned flags = FFTW_ESTIMATE | FFTW_PRESERVE_INPUT -){ - assert( flags & FFTW_PRESERVE_INPUT ); - assert(fftw::alignment_of(in) == fftw::alignment_of(out)); -#ifndef NDEBUG - size_t ne = 1; for(RankType i = 0; i != r; ++i) ne*=ns[i]; - auto check = in[ne/3]; // check that const data will not been overwritten -#endif - auto ret=::fftw_plan_dft(r, ns, (fftw_complex*)in, (fftw_complex*)out, sign, flags); - assert(check == in[ne/3]); // check that const data has not been overwritten - return ret; -} -#endif - -#if 0 -template -auto fftw_plan_dft_1d( - In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE -){ - static_assert(in.dimensionality == 1, "!"); assert(size(in) == size(out)); - assert( in.is_compact() ); assert( out.is_compact() ); - return multi::fftw_plan_dft_1d(size(in), data_elements(in), data_elements(out), sign, flags); -} - -template -auto fftw_plan_dft_2d( - In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE -){ - static_assert(in.dimensionality == 2, "!"); assert(in.sizes() == out.sizes()); - assert( in.is_compact() ); assert( out.is_compact() ); - return multi::fftw_plan_dft_2d( - sizes(in)[0], sizes(in)[1], - data_elements(in), data_elements(out), sign, flags - ); -} - -template -auto fftw_plan_dft_3d( - In&& in, Out&& out, int sign, unsigned flags = FFTW_ESTIMATE -){ - static_assert(in.dimensionality == 3, "!"); assert(in.sizes() == out.sizes()); - assert( in.is_compact() ); assert( out.is_compact() ); - return multi::fftw_plan_dft_3d( - sizes(in)[0], sizes(in)[1], sizes(in)[2], - data(in), data(out), - sign, flags - ); -} -#endif - -template -constexpr auto to_array(Tpl const& tpl) { - return std::apply( - [](auto const&... elems) {return std::array::value>{static_cast(elems)...};}, - tpl - ); -} - -#if 0 -#if(__cpp_if_constexpr>=201606) -//https://stackoverflow.com/a/35110453/225186 -template constexpr auto _constx(T&&t) -> std::remove_reference_t{return t;} -#define logic_assert(C, M) \ - if constexpr(noexcept(_constx(C))) static_assert((C), M); else assert((C)&&(M)); -#else -#define logic_assert(ConditioN, MessagE) assert(ConditioN && MessagE); -#endif -#endif - -template< - typename It1, class It2, - std::enable_if_t{} or std::is_convertible*>{}, int> =0 -> -auto fftw_plan_many_dft(It1 first, It1 last, It2 d_first, int sign, fftw::flags flags) --> fftw_plan { - - static_assert( sizeof(*base( first)) == sizeof((*base( first)).real()) + sizeof((*base( first)).imag()), "input must have complex pod layout"); - static_assert( sizeof(*base( first)) == sizeof(fftw_complex) , "input must have complex pod layout"); - static_assert( sizeof(*base(d_first)) == sizeof((*base(d_first)).real()) + sizeof((*base(d_first)).imag()), "output must have complex pod layout"); - static_assert( sizeof(*base(d_first)) == sizeof(fftw_complex) , "output must have complex pod layout"); - - assert(strides(*first) == strides(*last)); - assert(sizes(*first)==sizes(*d_first)); - - auto const ssn_tuple = multi::detail::tuple_zip(strides(*first ), strides(*d_first), sizes(*first)); - auto ssn = std::apply([](auto... ssn) { - using boost::multi::detail::get; - return std::array, sizeof...(ssn)>{ - boost::multi::detail::mk_tuple(static_cast(get<0>(ssn)), static_cast(get<1>(ssn)), static_cast(get<2>(ssn)))... - }; - }, ssn_tuple); - std::sort(ssn.begin(), ssn.end(), std::greater<>{}); - - auto const istrides = [&]() { - std::array::rank::value> istrides{}; - using boost::multi::detail::get; - std::transform(ssn.begin(), ssn.end(), istrides.begin(), [](auto elem) {return get<0>(elem);}); - return istrides; - }(); - - auto const ostrides = [&]() { - std::array::rank::value> ostrides{}; - using boost::multi::detail::get; - std::transform(ssn.begin(), ssn.end(), ostrides.begin(), [](auto elem) {return get<1>(elem);}); - return ostrides; - }(); - assert( std::is_sorted(ostrides.begin(), ostrides.end(), std::greater<>{}) ); // otherwise ordering is incompatible - - auto const ion = [&]() { - std::array::rank::value> ion {}; - using boost::multi::detail::get; - std::transform(ssn.begin(), ssn.end(), ion .begin(), [](auto elem) {return get<2>(elem);}); - return ion; - }(); - - auto const inembed = [&]() { - std::array::rank::value + 1> inembed{}; - std::adjacent_difference( - istrides.rbegin(), istrides.rend(), inembed.rbegin(), [](auto alpha, auto omega) {assert(omega != 0 and alpha%omega == 0); return alpha/omega;} - ); - return inembed; - }(); - - auto const onembed = [&]() { - std::array::rank::value + 1> onembed{}; - std::adjacent_difference( - ostrides.rbegin(), ostrides.rend(), onembed.rbegin(), [](auto alpha, auto omega) {assert(omega != 0 and alpha%omega == 0); return alpha/omega;} - ); - return onembed; - }(); - - auto ret = ::fftw_plan_many_dft( - /*int rank */ ion.size(), - /*const int* n */ ion.data(), - /*int howmany */ last - first, - /*fftw_complex* in */ reinterpret_cast(const_cast*>(static_cast const*>(base(first)))), // NOLINT(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast) input data - /*const int* inembed */ inembed.data(), - /*int istride */ istrides.back(), - /*int idist */ stride(first), - /*fftw_complex* out */ reinterpret_cast(static_cast*>(base(d_first))), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) adapt types - /*const int* onembed */ onembed.data(), - /*int ostride */ ostrides.back(), - /*int odist */ stride(d_first), - /*int */ sign, - /*unsigned */ static_cast(flags) - ); - assert(ret); // if you get null here it could be because your library doesn't support this fftw call mode - return ret; -} - -template< - typename It1, class It2, - std::enable_if_t{} or std::is_convertible*>{}, int> = 0 -> -auto fftw_plan_many_dft(It1 first, It1 last, It2 d_first, int sign) -->fftw_plan { - return fftw_plan_many_dft(first, last, d_first, sign, fftw::estimate); -} - -template< - class In, class Out, dimensionality_type D = std::decay_t::rank::value, - class=std::enable_if_t::rank::value>, - class=decltype(reinterpret_cast(/*static_cast *>*/(base(std::declval())))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) interact with legacy code -> -auto fftw_plan_dft(std::array which, In&& in, Out&& out, int sign, fftw::flags flags) -> fftw_plan { - static_assert( sizeof(*base(in )) == sizeof((*base(in )).real()) + sizeof((*base(in)).imag()) and sizeof(*base(in)) == sizeof(fftw_complex), - "input must have complex pod layout" ); - static_assert( sizeof(*base(out)) == sizeof((*base(out)).real()) + sizeof((*base(in)).imag()) and sizeof(*base(out)) == sizeof(fftw_complex), - "output must have complex pod layout" ); - - assert(in.sizes() == out.sizes()); - - auto const sizes_tuple = in.sizes(); - auto const istride_tuple = in.strides(); - auto const ostride_tuple = out.strides(); - - using boost::multi::detail::get; - auto which_iodims = std::apply([](auto... elems) { - return std::array, sizeof...(elems) + 1>{ // TODO(correaa) added one element to avoid problem with gcc 13 static analysis (out-of-bounds) - std::pair{ - get<0>(elems), - fftw_iodim64{get<1>(elems), get<2>(elems), get<3>(elems)} - }..., - std::pair{} - }; - }, boost::multi::detail::tuple_zip(which, sizes_tuple, istride_tuple, ostride_tuple)); - auto const part = std::stable_partition(which_iodims.begin(), which_iodims.end() - 1, [](auto elem) {return std::get<0>(elem);}); - - std::array dims{}; - auto const dims_end = std::transform(which_iodims.begin(), part, dims.begin(), [](auto elem) {return elem.second;}); - - std::array howmany_dims{}; - auto const howmany_dims_end = std::transform(part, which_iodims.end() -1, howmany_dims.begin(), [](auto elem) {return elem.second;}); - - assert( in .base() ); - assert( out.base() ); - - assert( in.extensions() == out.extensions() ); - - assert( (sign == -1) or (sign == +1) ); - - fftw_plan ret = fftw_plan_guru64_dft( - /*int rank */ dims_end - dims.begin(), - /*const fftw_iodim64 *dims */ dims.data(), - /*int howmany_rank */ howmany_dims_end - howmany_dims.begin(), - /*const fftw_iodim *howmany_dims */ howmany_dims.data(), - /*fftw_complex *in */ const_cast(reinterpret_cast(/*static_cast const *>*/(in.base()))), // NOLINT(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast) FFTW is taken as non-const while it is really not touched - /*fftw_complex *out */ reinterpret_cast(/*static_cast *>*/(out.base())), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - sign, static_cast(flags) // | FFTW_ESTIMATE - ); - - assert(ret &&"fftw lib returned a null plan, if you are using MKL check the limitations of their fftw interface"); - //https://software.intel.com/content/www/us/en/develop/documentation/mkl-developer-reference-c/top/appendix-d-fftw-interface-to-intel-math-kernel-library/fftw3-interface-to-intel-math-kernel-library/using-fftw3-wrappers.html - return ret; -} - -template< - class In, class Out, dimensionality_type D = std::decay_t::rank::value, - class=std::enable_if_t::rank::value>, - class=decltype(reinterpret_cast(/*static_cast *>*/(base(std::declval())))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : interact with legacy code -> -auto fftw_plan_dft(std::array which, In&& in, Out&& out, int sign) -> fftw_plan{ - return fftw_plan_dft(which, std::forward(in), std::forward(out), sign, fftw::estimate); -} - -template(multi::implicit_cast*>(std::declval())))> // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : interact with legacy code -auto fftw_plan_dft(multi::layout_t const& in_layout, PtrIn in_base, multi::layout_t const& out_layout, PtrOut out_base, int dir, fftw::flags flags) { - using multi::sizes; using multi::strides; - - assert( in_layout.sizes() == out_layout.sizes() ); - - auto const dims = std::apply([](auto... elems){ - using boost::multi::detail::get; - return std::array{ - fftw_iodim64{get<0>(elems), get<1>(elems), get<2>(elems)} - ... - }; - }, boost::multi::detail::tuple_zip(in_layout.sizes(), in_layout.strides(), out_layout.strides())); - - auto ret = fftw_plan_guru64_dft( - /*int rank */ dir?D:0, - /*const fftw_iodim64 *dims */ dims.data(), - /*int howmany_rank */ 0, - /*const fftw_iodim *howmany_dims */ nullptr, //howmany_dims.data(), - /*fftw_complex *in */ const_cast(reinterpret_cast( static_cast const*>(in_base ))), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) : interact with legacy code - /*fftw_complex *out */ reinterpret_cast(multi::implicit_cast *>(out_base)) , // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : interact with legacy code - dir, static_cast(flags) - ); - assert(ret); - return ret; -} - -template //, typename = decltype(reinterpret_cast(multi::implicit_cast*>(std::declval())))> // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : interact with legacy code -auto fftw_plan_dft(multi::layout_t const& in_layout, multi::layout_t const& out_layout, int dir, fftw::flags flags) { - return fftw_plan_dft(in_layout, nullptr, out_layout, nullptr, dir, flags | fftw::estimate); -} - -template(multi::implicit_cast*>(base(std::declval()))))> // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : interact with legacy code -auto fftw_plan_dft(In const& in, Out&& out, int dir, fftw::flags flags) { - return fftw_plan_dft(in.layout(), in.base(), out.layout(), out.base(), dir, flags); -} - -template(multi::implicit_cast*>(base(std::declval()))))> // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : interact with legacy code -auto fftw_plan_dft(In const& in, Out&& out, int dir) { - return fftw_plan_dft(in, out, dir, fftw::estimate); -} - -namespace fftw { - -#if HAVE_FFTW3_THREADS -inline void initialize_threads(){int good = fftw_init_threads(); assert(good); (void)good;} -#else -inline void initialize_threads(){} -#endif - -inline void cleanup(){fftw_cleanup();} - -struct environment{ - environment() = default; - environment(environment const&) = delete; - environment(environment&&) = delete; - auto operator=(environment const&) = delete; - auto operator=(environment&&) = delete; - ~environment(){fftw_cleanup();} -}; - -class plan { - plan() : impl_{nullptr, &fftw_destroy_plan} {} - std::unique_ptr, decltype(&fftw_destroy_plan)> impl_; - - public: - plan(plan const&) = delete; - plan(plan&&) = default; - ~plan() = default; - - template()...)) - > - explicit plan(As&&... args) : impl_{fftw_plan_dft(std::forward(args)...), &fftw_destroy_plan} { - assert(impl_); - } - template - static auto many(As&&... args) - ->std::decay_t(args)...) , std::declval())> { - plan ret; ret.impl_.reset(fftw_plan_many_dft(std::forward(args)...)); return ret; // this produces a compilation error in icc++17 - } - -private: - void execute() const {fftw_execute(impl_.get());} //TODO(correaa): remove const - template - void execute_dft(I&& in, O&& out) const { - ::fftw_execute_dft(impl_.get(), const_cast(reinterpret_cast(static_cast const*>(base(in)))), reinterpret_cast(static_cast*>(base(out)))); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) : to interface with legacy fftw - } - template void execute(I&& in, O&& out) const {execute_dft(std::forward(in), std::forward(out));} - friend void execute(plan const& self) {self.execute();} - -public: - auto operator=(plan &&) -> plan& = default; - auto operator=(plan const&) -> plan& = delete; - - template - void operator()(I&& in, O&& out) const {execute(std::forward(in), std::forward(out));} - void operator()() const {execute();} // http://www.fftw.org/fftw3_doc/Thread-safety.html#Thread-safety - - [[nodiscard]] auto cost() const -> double {return fftw_cost(impl_.get());} - [[nodiscard]] auto flops() const { - struct ret_t{ - double add = 0.; - double mul = 0.; - double fma = 0.; - // explicit operator double() const{return add + mul + 2*fma;} - } ret{}; - fftw_flops(impl_.get(), &ret.add, &ret.mul, &ret.fma); - return ret; - } - - //std::string string_print() const{ - // return std::unique_ptr{fftw_sprint_plan(impl_.get())}.get(); - //} - //friend std::ostream& operator<<(std::ostream& os, plan const& p){return os< bool{return false;} - static constexpr auto nthreads() -> bool{return true;} - static constexpr auto with_nthreads() -> int{return 1;} -#endif -}; - -#if HAVE_FFTW3_THREADS -bool plan::is_thread_safe_ = (plan::make_thread_safe(), true); -int plan::nthreads_ = (initialize_threads(), with_nthreads()); -#endif - -enum sign : decltype(FFTW_FORWARD) {backward = FFTW_BACKWARD, none = 0, forward = FFTW_FORWARD}; - -static_assert( forward != none and none != backward and backward != forward, "!"); - -//enum strategy: decltype(FFTW_ESTIMATE){ estimate = FFTW_ESTIMATE, measure = FFTW_MEASURE }; - -template -auto dft(In const& in, Out&& out, int dir) -->decltype(fftw::plan{in, out, dir}(), std::forward(out)) { - return fftw::plan{in, out, dir}(), std::forward(out); } - -using std::decay_t; - -template -auto dft(std::array which, In const& in, Out&& out, sign dir) -->decltype(plan{which, in, out, dir}(), std::forward(out)) { - return plan{which, in, out, dir}(), std::forward(out); } - -template::rank::value> -auto dft(std::array which, In const& in, Out&& out) { - std::array fwd{}; - std::transform(begin(which), end(which), begin(fwd), [](auto elem) {return elem == FFTW_FORWARD ;}); - dft(fwd, in, out, fftw::forward); - - std::array bwd{}; - std::transform(begin(which), end(which), begin(bwd), [](auto elem) {return elem == FFTW_BACKWARD;}); - if(std::accumulate(begin(bwd), end(bwd), false)) {dft(bwd, out, out, static_cast(FFTW_BACKWARD));} - - return std::forward(out); -} - -template -auto many_dft(It1 first, It1 last, It2 d_first, int sign) -->decltype(plan::many(first, last, d_first, sign)(), d_first + (last - first)) { - return plan::many(first, last, d_first, sign)(), d_first + (last - first); } - -template -[[nodiscard]] // ("when first argument is const") -auto dft(In const& in, sign dir) -->std::decay_t { - return dft(in, R(extensions(in), get_allocator(in)), dir);} - -template -auto rotate(multi::array& inout) -> decltype(auto) { - multi::array_ref::element_ptr> before(data_elements(inout), extensions(inout)); - inout.reshape(extensions(rotated(before) )); - fftw::dft(before, inout, fftw::none); - return inout; -} - -template().base()), typename std::decay_t::element>, int> =0 -> -[[nodiscard]] // ("when first argument is const") -auto dft(std::array which, In const& in, sign dir) -->std::decay_t { - return fftw::dft(which, in, R(extensions(in), get_allocator(in)), dir);} - -template::rank::value, - std::enable_if_t().base()), typename std::decay_t::element>, int> =0 -> -auto dft(std::array which, In&& in, sign dir) -->decltype(dft(which, in, in, dir), std::forward(in)) { - return dft(which, in, in, dir), std::forward(in); } - -template -void dft(std::array which, In const& in) = delete; - -template -[[nodiscard]] // ("when second argument is const") -auto dft(In const& in, sign dir) -> R { - static_assert( Rank <= In::rank_v, "!" ); - return dft(in, R(extensions(in), get_allocator(in)), dir); -} - -template auto dft_forward(A&&... array) -->decltype(fftw::dft(std::forward(array)..., fftw::forward)) { - return fftw::dft(std::forward(array)..., fftw::forward); } - -template -[[nodiscard]] // ("when input argument is read only") -auto dft_forward(BoolArray which, A const& array) -->decltype(fftw::dft(which, array, fftw::forward)) { - return fftw::dft(which, array, fftw::forward); } - -template -[[nodiscard]] // ("when input argument is read only") -auto dft_forward(std::array which, A const& array) -->decltype(fftw::dft(which, array, fftw::forward)) { - return fftw::dft(which, array, fftw::forward); } - -template -auto dft_forward(std::array which, A const& in, O&& out) -->decltype(fftw::dft(which, in, std::forward(out), fftw::forward)) { - return fftw::dft(which, in, std::forward(out), fftw::forward); } - -template -[[nodiscard]] // ("when input argument is read only") -auto dft_backward(std::array which, A const& array) -->decltype(fftw::dft(which, array, fftw::backward)) { - return fftw::dft(which, array, fftw::backward); } - -template -auto dft_backward(std::array which, A const& in, O&& out) -->decltype(fftw::dft(which, in, std::forward(out), fftw::backward)) { - return fftw::dft(which, in, std::forward(out), fftw::backward); } - -template -[[nodiscard]] // ("when input argument is read only") -auto dft_forward(A const& array) -->decltype(fftw::dft(array, fftw::forward)) { - return fftw::dft(array, fftw::forward); } - -template auto dft_backward(A&&... args) -->decltype(dft(std::forward(args)..., fftw::backward)) { - return dft(std::forward(args)..., fftw::backward); } - -template auto dft_inplace(In&& in, sign direction) -> In&& { - fftw::plan{in, in, static_cast(direction)}(); - return std::forward(in); -} - -template -auto copy(In const& in, Out&& out) -->decltype(dft(std::array{}, in, std::forward(out), fftw::forward)) { - return dft(std::array{}, in, std::forward(out), fftw::forward); } - -template -[[nodiscard]] // ("when input argument is const")]] -auto copy(In const& in) -> R -{//->decltype(copy(i, R(extensions(i), get_allocator(i))), R()){ - return copy(in, R(extensions(in), get_allocator(in)));} - -#if 0 -template::decay_type> -auto move(In&& in) { - if(in.is_compact()) { - multi::array_ref Ref( - in.base(), extensions(in) - ); - copy(in, Ref); - return R( - multi::array_ref>(std::make_move_iterator(in.mbase()), ((in.mbase()=0), extensions(Ref))) - ); - } - return copy(std::forward(in)); -} -#endif - -template -struct allocator { - using value_type = T; - using size_type = std::size_t; - - auto allocate(size_type n) -> T* { - if(n == 0) {return nullptr;} - if (n > max_size()) {throw std::length_error("multi::fftw::allocator::allocate() overflow.");} - void* ptr = fftw_malloc(sizeof(T) * n); - if(ptr == nullptr) {throw std::bad_alloc{};} - return static_cast(ptr); - } - void deallocate(T* ptr, size_type n) {if(n != 0) {fftw_free(ptr);}} - - constexpr auto operator==(allocator const& /*other*/) const -> bool {return true ;} - constexpr auto operator!=(allocator const& /*other*/) const -> bool {return false;} - - private: - static constexpr auto max_size() {return (static_cast(0) - static_cast(1)) / sizeof(T);} -}; - -// template -// using allocator = std::allocator; - -template -using static_array = ::boost::multi::static_array>; - -template -using array = ::boost::multi::array>; - -template> -auto copy(multi::subarray>&& array) -> R { - if(array.is_compact()) { - return - fftw::copy( - array.template static_array_cast(), - multi::array_ref(array.base().base(), array.extensions()) - ).template static_array_cast>() - ; - } - return fftw::copy(array.template static_array_cast()); -} - -template -auto transpose(Array& array) -->decltype(fftw::copy(transposed(array), array.reshape(extensions(layout(array).transpose())))) { - multi::array_ref const ref(array.base(), extensions(array)); - return fftw::copy(ref.transposed(), array.reshape(layout(array).transpose().extensions())); -} - -#if 0 -// TODO(correaa) investigate why this doesn't work as expected -template -auto rotate(Array& a) -->decltype(fftw::copy(rotated(a), a.reshape(extensions(layout(a).transpose())))){ - multi::array_ref r(a.base(), extensions(a)); - auto&& ro = r.rotated(); - return fftw::copy(ro, a.reshape(layout(a).rotate().extensions())); -} -#endif - -} // end namespace fftw -} // end namespace boost::multi - -namespace boost::multi::fftw { - -template -class fft_iterator { - MDIterator base_; - std::array which_ = {}; - - public: - using iterator_type = MDIterator; - - using difference_type = typename std::iterator_traits::difference_type; - using value_type = typename std::iterator_traits::value_type; - using pointer = void*; - class reference { - typename MDIterator::reference::extensions_type x_; - explicit reference(typename MDIterator::reference const& ref) : x_{ref.extensions()} {} - friend class fft_iterator; - - public: - using extensions_type = typename MDIterator::reference::extensions_type; - auto extensions() const -> extensions_type {return x_;} - }; - - using iterator_category = std::random_access_iterator_tag; // using iterator_category = std::input_iterator_tag; - - explicit fft_iterator(iterator_type base, std::array which) noexcept : base_{std::move(base)}, which_{which} {} - - friend auto operator-(fft_iterator const& self, fft_iterator const& other) -> difference_type { - return self.base_ - other.base_; - } - - template - friend auto copy(fft_iterator first, fft_iterator last, ItOut d_first) { - assert(first.which_ == last.which_); - fftw::dft( - first.which_, - multi::ref(first.base_, last.base_), - multi::ref(d_first, d_first + (last.base_ - first.base_)) - ); - - return d_first + (last.base_ - first.base_); - } - template - friend auto uninitialized_copy(fft_iterator first, fft_iterator last, ItOut d_first) { - return copy(first, last, d_first); - } - - auto operator*() const {return reference{*base_};} -}; - -template -class fft_range { - Origin origin_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - Array ref_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - using which_type = std::array::rank::value>; - which_type which_; - - public: - using iterator_type = typename std::decay_t::const_iterator; - - using size_type = typename std::decay_t::size_type; - using iterator = fft_iterator; - - using decay_type = typename std::decay_t::decay_type; - - explicit fft_range(Origin&& origin, Array&& in, which_type which) - : origin_{std::forward(origin)}, ref_{std::forward(in)}, which_{which} {} - - operator decay_type() && { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - if constexpr(std::is_same_v) { - decay_type the_ret{std::forward(origin_)}; - the_ret.reshape(this->extensions()); - - fftw::dft( - which_, - ref_, - the_ret - ); - - return the_ret; - } else { - return decay_type{this->begin(), this->end()}; - } - } - - auto operator+() const& {return static_cast( *this );} - auto operator+() && {return static_cast(std::move(*this));} - - auto begin() const {return iterator{ref_.begin(), which_};} - auto end() const {return iterator{ref_.end() , which_};} - - auto size() const {return ref_.size();} - auto extensions() const {return ref_.extensions();} - auto num_elements() const {return ref_.num_elements();} - - auto base() const {return ref_.base();} - - auto rotated() const { - auto new_which = which_; - std::rotate(new_which.begin(), new_which.begin() + 1, new_which.end()); - return fft_range>{origin_, ref_.rotated(), new_which}; - } - auto unrotated() const { - auto new_which = which_; - std::rotate(new_which.rbegin(), new_which.rbegin() + 1, new_which.rend()); - return fft_range>{origin_, ref_.unrotated(), new_which}; - } - auto transposed() const { - auto new_which = which_; - std::swap(std::get<0>(new_which), std::get<1>(new_which)); - return fft_range>{std::forward(origin_), ref_.transposed(), new_which}; - } - - template - auto operator()(FBNs... fbns) const { - static_assert( sizeof...(fbns) <= std::decay_t::rank::value , "too many arguments"); - auto new_which = which_; - std::array fbna{fbns...}; - std::transform(fbna.begin(), fbna.end(), new_which.begin(), new_which.begin(), - [](auto fbn, auto nw) { - if(fbn == fftw::none) {return nw;} - assert(nw == fftw::none); - return fbn; - } - ); - return fft_range>{std::forward(origin_), ref_(), new_which}; - } -}; - -template -auto ref(Array&& in) { - return fft_range { - std::forward(in), - std::forward(in), {} - }; -} - -template auto move(Array& in) {return fftw::ref(std::move(in));} - -template -auto fft(Array&& in) { - std::array::rank::value> which{}; - std::fill_n(which.begin(), ND, fftw::forward); - return fft_range{std::forward(in), std::forward(in), which}; -} - -template -auto ifft(Array&& in) { - std::array which{}; - std::fill_n(which.begin(), ND, fftw::backward); - return fft_range{in, which}; -} - -} // end namespace boost::multi::fftw - -#endif diff --git a/include/multi/adaptors/fftw/CMakeLists.txt b/include/multi/adaptors/fftw/CMakeLists.txt deleted file mode 100644 index 6d98366cb..000000000 --- a/include/multi/adaptors/fftw/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -set(CMAKE_VERBOSE_MAKEFILE ON) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - -#project( -# boost-multi-adaptors-fftw -# VERSION 0.1 -# LANGUAGES CXX -#) - -find_package(PkgConfig) - -if(PKG_CONFIG_FOUND) - pkg_search_module( - FFTW - fftw3 - IMPORTED_TARGET - ) - if(FFTW_FOUND) - include_directories(PkgConfig::FFTW) - link_libraries(PkgConfig::FFTW) - - include_directories(${CMAKE_BINARY_DIR}) - - add_subdirectory(test) -# add_subdirectory(mpi) - else() - message(WARNING "Cannot find FFTW, FFTW-adaptor will not be tested. If you want this feature install FFTW, for example please run:") - message(WARNING " sudo apt install pkg-config libfftw3-dev") - message(WARNING " sudo dnf install fftw-devel # in Fedora") - endif() -else() - message(WARNING "Cannot find PkgConfig and/or FFTW, FFTW-adaptor will not be tested. If you want this feature install PkgConfig and FFTW, for example please run:") - message(WARNING " sudo apt install pkg-config libfftw3-dev") - message(WARNING " sudo dnf install fftw-devel # in Fedora") -endif() diff --git a/include/multi/adaptors/fftw/memory.hpp b/include/multi/adaptors/fftw/memory.hpp deleted file mode 100644 index fcbec40cb..000000000 --- a/include/multi/adaptors/fftw/memory.hpp +++ /dev/null @@ -1,174 +0,0 @@ -#if COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXXX $CXXFLAGS $0 -o $0x -lfftw3 -lfftw3_mpi&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020 -// apt-get install libfftw3-mpi-dev -// compile with: mpicc simple_mpi_example.c -Wl,-rpath=/usr/local/lib -lfftw3_mpi -lfftw3 -o simple_mpi_example */ - -#ifndef MULTI_ADAPTOR_FFTW_MEMORY_HPP -#define MULTI_ADAPTOR_FFTW_MEMORY_HPP - -#include - -#include "../../config/NODISCARD.hpp" - -#include -#include -#include -#include -#include -#include - -namespace boost{ -namespace multi{ -namespace fftw{ - -template -class allocator{ -public: - using value_type = T; - -#if 1 - using pointer = value_type*; - using const_pointer = typename std::pointer_traits::template - rebind; - using void_pointer = typename std::pointer_traits::template - rebind; - using const_void_pointer = typename std::pointer_traits::template - rebind; - using difference_type = typename std::pointer_traits::difference_type; - using size_type = std::make_unsigned_t; - - template struct rebind {typedef allocator other;}; -#endif - - allocator() noexcept {} // not required, unless used - template allocator(allocator const&) noexcept {} - - NODISCARD("to avoid memory leak") - value_type* allocate(std::size_t n) const{return static_cast(fftw_malloc(sizeof(T)*n));} - -// value_type* // Use pointer if pointer is not a value_type* -// allocate(std::size_t n){return static_cast(::operator new (n*sizeof(value_type)));} - - void deallocate(value_type* p, std::size_t){fftw_free(p);} -// void deallocate(value_type* p, std::size_t) noexcept // Use pointer if pointer is not a value_type* -// {::operator delete(p);} - - static int alignment_of(value_type* p){return fftw_alignment_of((double*)p);} - -#if 1 - value_type* allocate(std::size_t n, const_void_pointer){return allocate(n);} - - template - void construct(U* p, Args&& ...args){::new(p) U(std::forward(args)...);} - - template void destroy(U* p) noexcept{p->~U();} - - std::size_t max_size() const noexcept{return std::numeric_limits::max();} - - allocator select_on_container_copy_construction() const{return *this;} - - using propagate_on_container_copy_assignment = std::false_type; - using propagate_on_container_move_assignment = std::false_type; - using propagate_on_container_swap = std::false_type; - using is_always_equal = std::is_empty; -#endif -}; - -template -bool operator==(allocator const&, allocator const&) noexcept{return true;} - -template -bool operator!=(allocator const& x, allocator const& y) noexcept{ - return !(x == y); -} - -#if 0 -template -struct allocator{ - using value_type = T; - using pointer = value_type*; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using propagate_on_container_move_assignment = std::true_type; -// NODISCARD("to avoid memory leak") - pointer allocate(size_type n) const{return static_cast(fftw_malloc(sizeof(T)*n));} - void deallocate(pointer data, size_type){fftw_free(data);} -}; -#endif - -//template<> allocator>::pointer allocator>::allocate(size_type n){return reinterpret_cast*>(fftw_alloc_complex(n));} -//template<> allocator< double >::pointer allocator< double >::allocate(size_type n){return fftw_alloc_real(n) ;} - -#if 0 -template<> -struct allocator>{ - using value_type = std::complex; - using pointer = value_type*; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using propagate_on_container_move_assignment = std::true_type; - NODISCARD("to avoid memory leak") - pointer allocate(size_type n){return reinterpret_cast*>(fftw_alloc_complex(n));} - void deallocate(pointer data, size_type){fftw_free(data);} -}; - -template<> -struct allocator{ - using value_type = double; - using pointer = value_type*; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - using propagate_on_container_move_assignment = std::true_type; - NODISCARD("to avoid memory leak") - pointer allocate(size_type n){return fftw_alloc_real(n);} - void deallocate(pointer data, size_type){fftw_free(data);} -}; -#endif - -}}} - - -#if 0//__NVCC__ -namespace std{ - -template struct allocator_traits> : std::allocator_traits>{ - using base = std::allocator_traits>; - template using rebind_alloc = boost::multi::fftw::allocator; - template - static auto allocate(A& a, typename base::size_type n){return a.allocate(n);} -}; - -} -#endif - -#if 0 //def __NVCC__ -namespace std{ -template struct allocator_traits> : std::allocator_traits>{ - template using rebind_alloc = boost::multi::fftw::allocator; -}; -} -#endif - -#if not __INCLUDE_LEVEL__ - -#include "../../array.hpp" - -#include - -namespace multi = boost::multi; - -int main(){ - { - std::vector> v(100); - multi::array arr({10, 20}); - } - { - std::vector, multi::fftw::allocator>> v(100); - multi::array, 2> arr({10, 20}); - } -} -#endif -#endif - diff --git a/include/multi/adaptors/fftw/test/CMakeLists.txt b/include/multi/adaptors/fftw/test/CMakeLists.txt deleted file mode 100644 index 2771c0153..000000000 --- a/include/multi/adaptors/fftw/test/CMakeLists.txt +++ /dev/null @@ -1,185 +0,0 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- -#[=[Multi Test suite can be run like this: - mkdir -p build - cd build - cmake .. [-DENABLE_CUDA=1] - make -j - ctest -j --output-on-error [-T memcheck] - exit -#]=] -cmake_minimum_required(VERSION 3.11) - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") - -set(CMAKE_VERBOSE_MAKEFILE ON) - -#project( -# boost-multi-adaptors-fftw-test -# VERSION 0.1 -# LANGUAGES CXX -#) - -if((NOT - CMAKE_CXX_COMPILER_ID - STREQUAL - "PGI" - ) - AND (NOT - DART_COMPILER_NAME - STREQUAL - "nvcc" - ) -) -# find_package(Boost REQUIRED COMPONENTS unit_test_framework timer) -else() - link_libraries("-lboost_unit_test_framework") # -lboost_timer") -endif() - -# find_package(PkgConfig REQUIRED) -# pkg_search_module( -# FFTW -# REQUIRED -# fftw3 -# IMPORTED_TARGET -# ) - -# include_directories(PkgConfig::FFTW) -# link_libraries(PkgConfig::FFTW) -# cmake_print_properties(TARGETS PkgConfig::FFTW PROPERTIES INTERFACE_LINK_LIBRARIES INTERFACE_INCLUDE_DIRECTORIES) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -if(ENABLE_CUDA OR DEFINED CXXCUDA) - - enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") - -# include(FindCUDA/select_compute_arch) -# cuda_detect_installed_gpus(INSTALLED_GPU_CCS_1) -# string(STRIP "${INSTALLED_GPU_CCS_1}" INSTALLED_GPU_CCS_2) -# string( -# REPLACE " " -# ";" -# INSTALLED_GPU_CCS_3 -# "${INSTALLED_GPU_CCS_2}" -# ) -# string( -# REPLACE "." -# "" -# CUDA_ARCH_LIST -# "${INSTALLED_GPU_CCS_3}" -# ) -# set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCH_LIST}) - -endif() - -#find_package(CUDA QUIET) - -#if(CUDA_FOUND) -# message("CUDA found") -# include_directories(${CUDA_INCLUDE_DIRS}) -#else() -# message("CUDA not found") -#endif() - -enable_testing() -list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # needs cmake 3.17 -include(CTest) - -# configure_file("config.hpp.in" ${CMAKE_BINARY_DIR}/config.hpp) - -include_directories(${CMAKE_BINARY_DIR}) - -# file(GLOB TEST_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) -set(TEST_SRCS - combinations.cpp -# copy.cpp - core.cpp - moved.cpp - shift.cpp - so_shift.cpp -# thrust.cpp - transpose.cpp - transpose_square.cpp -) - -foreach(TEST_FILE ${TEST_SRCS}) - set(TEST_EXE "${TEST_FILE}.x") - add_executable(${TEST_EXE} ${TEST_FILE}) - if(ENABLE_CUDA OR DEFINED CXXCUDA) - set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) - # set_property(TARGET ${TEST_EXE} PROPERTY "${CUDA_ARCH_LIST}") - target_compile_options(${TEST_EXE} PRIVATE -std=c++17) - endif() - # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") - target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) - - target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) - target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) - - target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) - target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) - if((NOT ENABLE_CUDA) - AND (NOT - DART_COMPILER_NAME - STREQUAL - "nvcc" - ) - ) - if(NOT DEFINED ENABLE_CIRCLE) - target_compile_options( - ${TEST_EXE} - PRIVATE -Werror - -Wall - -Wextra - $<$: - -fno-common - -Wpedantic - -Wformat-truncation - -fstack-usage> # -Wconversion - $<$,$>: - -fno-common - -Wpedantic - -Wmove - -Wno-error=\#warnings - > - $<$: - -wd161 - -diag-disable=remark - -Warray-bounds - -Wchar-subscripts - -Wcomment - -Wenum-compare - -Wformat - -Wuninitialized - -Wmaybe-uninitialized - -Wmain - -Wnarrowing - -Wnonnull - -Wparentheses - -Wpointer-sign - -Wreorder - -Wno-return-type - -Wsign-compare - -Wsequence-point - -Wtrigraphs - -Wunused-function - -Wunused-but-set-variable - -Wunused-variable - -Wwrite-strings - -Werror - -diag-error:3846 - > - $<$: - /W4> - ) - else() - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_TEST_DYN_LINK=1") - endif() - endif() - add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) - target_link_libraries(${TEST_EXE} PRIVATE PkgConfig::FFTW) -endforeach() diff --git a/include/multi/adaptors/fftw/test/combinations.cpp b/include/multi/adaptors/fftw/test/combinations.cpp deleted file mode 100644 index ea811d8c8..000000000 --- a/include/multi/adaptors/fftw/test/combinations.cpp +++ /dev/null @@ -1,210 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW adaptor" -#include - -#include - -#include -#include -#include -#include - -namespace multi = boost::multi; - -template<> -inline constexpr bool multi::force_element_trivial_default_construction> = true; - -namespace utf = boost::unit_test::framework; - -using namespace std::string_literals; // NOLINT(build/namespaces) for ""s - -using fftw_fixture = multi::fftw::environment; -BOOST_TEST_GLOBAL_FIXTURE( fftw_fixture ); - -class watch : private std::chrono::high_resolution_clock { - std::string label; - time_point start = now(); - - public: - template - explicit watch(String&& label) : label{std::forward(label)} {} // NOLINT(fuchsia-default-arguments-calls) - - watch(watch const&) = delete; - watch(watch&&) = delete; - - auto operator=(watch const&) = delete; - auto operator=(watch&&) = delete; - - auto elapsed_sec() const {return std::chrono::duration(now() - start).count();} - ~watch() { std::cerr<< label <<": "<< elapsed_sec() <<" sec"< using marray = multi::array; -constexpr auto exts = multi::extensions_t<4>({6, 12, 24, 12}); - -BOOST_AUTO_TEST_CASE(fft_combinations, *boost::unit_test::tolerance(0.00001) ) { - using complex = std::complex; - - auto const in = [] { - marray ret(exts); - std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), - [eng = std::default_random_engine {std::random_device {}()}, - uniform_01 = std::uniform_real_distribution<>{}]() mutable { - return complex{uniform_01(eng), uniform_01(eng)}; - }); - return ret; - }(); - - std::vector> const which_cases = { // std::vector NOLINT(fuchsia-default-arguments-calls) - {false, true , true , true }, - {false, true , true , false}, - {true , false, false, false}, - {true , true , false, false}, - {false, false, true , false}, - {false, false, false, false}, - }; - - using std::cout; - for(auto which : which_cases) { - cout<<"case "; - copy(begin(which), end(which), std::ostream_iterator{cout, ", "}); - cout<<"\n"; - - marray out = in; - { - watch const unnamed{"cpu_oplac %ws wall, CPU (%p%)\n"s}; - multi::fftw::dft_forward(which, in, out); - } - { - multi::fftw::plan const pln{which, in, out, multi::fftw::forward}; - watch const unnamed{"cpu_oplac planned %ws wall, CPU (%p%)\n"s}; - pln(); - } - { - auto in_rw = in; - watch const unnamed{"cpu_iplac %ws wall, CPU (%p%)\n"s}; - multi::fftw::dft_forward(which, in_rw); - } - { - auto in_rw = in; - multi::fftw::plan const pln{which, in_rw, in_rw, multi::fftw::forward}; - watch const unnamed{"cpu_iplac planned %ws wall, CPU (%p%)\n"s}; - pln(); - } - { - auto in_rw = in; - multi::fftw::plan const pln{which, in_rw, in_rw, multi::fftw::forward}; - watch const unnamed{"cpu_iplac planned measured %ws wall, CPU (%p%)\n"s}; - pln(); - } - { - watch const unnamed{"cpu_alloc %ws wall, CPU (%p%)\n"s}; - auto out_cpy = multi::fftw::dft_forward(which, in); - BOOST_TEST(abs(out_cpy[5][4][3][1] - out[5][4][3][1]) == 0.); - } - { - auto in_rw = in; - watch const unnamed{"cpu_move %ws wall, CPU (%p%)\n"s}; - auto out_cpy = multi::fftw::dft_forward(which, std::move(in_rw)); - BOOST_TEST(abs(out_cpy[5][4][3][1] - out[5][4][3][1]) == 0.); - } - } -} - -BOOST_AUTO_TEST_CASE(fftw_4D_power_benchmark, *boost::unit_test::enabled() ) { - using complex = std::complex; - namespace fftw = multi::fftw; - - marray in(exts); - std::iota(in.data_elements(), in.data_elements() + in.num_elements(), 1.2); - - BOOST_REQUIRE(in[0][0][0][0] == 1.2); - std::array which = {false, true, true, true}; - [&, unnamed = watch{utf::current_test_case().full_name()+" inplace FTTT"s}] { - fftw::dft(which, in, fftw::forward); - }(); - [&, unnamed = watch{utf::current_test_case().full_name()+" inplace FTTT"s}] { - fftw::dft(which, in, fftw::forward); - }(); - auto in0000 = in[0][0][0][0]; - BOOST_REQUIRE(in0000 != 1.2); - - marray out(exts); - [&, unnamed = watch{utf::current_test_case().full_name()+" outofplace FTTT"s}] { - fftw::dft(which, in, out, fftw::forward); - }(); - [&, unnamed = watch{utf::current_test_case().full_name()+" outofplace FTTT"s}] { - fftw::dft(which, in, out, fftw::forward); - }(); - [&, unnamed = watch{utf::current_test_case().full_name()+" outofplace FTTT"s}] { - fftw::dft(which, in, out, fftw::forward); - }(); - [&, unnamed = watch{utf::current_test_case().full_name()+" outofplace+alloc FTTT"s}] { - marray out2(exts); - fftw::dft(which, in, out2, fftw::forward); - }(); - [&, unnamed = watch{utf::current_test_case().full_name()+" outofplace+alloc FTTT"s}] { - marray out2(exts); - fftw::dft(which, in, out2, fftw::forward); - }(); - BOOST_REQUIRE(in0000 == in[0][0][0][0]); -} - -BOOST_AUTO_TEST_CASE(fftw_4D_power_benchmark_syntax) { - std::vector> const which_cases = { // std::vector NOLINT(fuchsia-default-arguments-calls) - {false, true , true , true }, - {false, true , true , false}, - {true , false, false, false}, - {true , true , false, false}, - {false, false, true , false}, - {false, false, false, false}, - }; - using complex = std::complex; - - auto const in = [] { - marray ret(exts); - std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), - [eng = std::default_random_engine {std::random_device {}()}, - uniform_01 = std::uniform_real_distribution<>{}]() mutable{ - return complex{uniform_01(eng), uniform_01(eng)}; - }); - return ret; - }(); - - auto io = in; (void)io; - BOOST_REQUIRE( io.extensions() == in.extensions() ); - - namespace fftw = multi::fftw; - using clock = std::chrono::high_resolution_clock; - { - auto const tick = clock::now(); - marray out(exts); - out = multi::fftw::ref(in)(fftw::none, fftw::forward, fftw::forward, fftw::forward); - BOOST_REQUIRE( out.extensions() == in.extensions() ); - auto time = std::chrono::duration(clock::now() - tick); - std::cout<<"allocate and copy assign (out-of-place fft) : "<< time.count() <(clock::now() - tick); - std::cout<<"copy construct (out-of-place fft) : "<< time.count() <(clock::now() - tick); - std::cout<<"self copy assign (in-place fft) : "<< time.count() < const out = multi::fftw::move(io)(fftw::none, fftw::forward, fftw::forward, fftw::forward); - BOOST_REQUIRE( io.is_empty() ); - auto time = std::chrono::duration(clock::now() - tick); - std::cout<<"move construct (in-place fft) : "<< time.count() < - -#include -#include - -#include // for std::transform_reduce - -namespace multi = boost::multi; - -template auto power(M const& array) { - return std::accumulate(array.elements().begin(), array.elements().end(), 0.0, [](auto e1, auto e2) {return std::move(e1) + std::norm(e2);}); -// return std::transform_reduce(array.elements().begin(), array.elements().end(), 0.0, std::plus<>{}, [](auto zee) { return std::norm(zee); }); -} - -using fftw_fixture = multi::fftw::environment; -BOOST_TEST_GLOBAL_FIXTURE(fftw_fixture); - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_fft_move) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array in = { - {100.0 + 2.0 * I, 9. - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7. - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5. + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8. + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18. + 7.0 * I, 2.0 + 10.0 * I}, - }; - - auto const in_copy = in; - // auto* const in_base = in.base(); - - multi::array in2(in.extensions()); - - in2 = multi::fftw::fft(std::move(in)); - - BOOST_REQUIRE( power(in2)/num_elements(in2) - power(in_copy) < 1e-8 ); - // BOOST_REQUIRE( in2.base() == in_base ); - // BOOST_REQUIRE( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_move) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - BOOST_REQUIRE( in[1][1] == 7.0 - 4.0*I ); - - auto const in_copy = in; - auto* const in_base = in.base(); - BOOST_REQUIRE( in_base == in.base() ); - - in = multi::fftw::ref(in); - - BOOST_REQUIRE( in == in_copy ); - BOOST_REQUIRE( in_base == in.base() ); // prove no allocation -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - BOOST_REQUIRE( in[1][1] == 7.0 - 4.0*I ); - - auto const in_copy = in; - auto* const in_base = in.base(); - BOOST_REQUIRE( in_base == in.base() ); - BOOST_REQUIRE( in.size() == 5 ); - - in = multi::fftw::ref(in).transposed(); - - BOOST_REQUIRE( in.size() == 3 ); - BOOST_REQUIRE( in == in_copy.transposed() ); // prove correctness - BOOST_REQUIRE( in_base == in.base() ); // prove no allocation -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_naive) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - BOOST_REQUIRE( in[1][1] == 7.0 - 4.0*I ); - - auto const in_copy = in; - auto* const in_base = in.base(); - BOOST_REQUIRE( in_base == in.base() ); - BOOST_REQUIRE( in.size() == 5 ); - - in = in.transposed(); // this is UB - - BOOST_REQUIRE( in.size() == 3 ); - // BOOST_REQUIRE( in != in_copy.transposed() ); // prove it is incorrect - BOOST_REQUIRE( in_base == in.base() ); // prove no allocation -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_naive_copy) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - BOOST_REQUIRE( in[1][1] == 7.0 - 4.0*I ); - - auto const in_copy = in; - auto* const in_base = in.base(); - BOOST_REQUIRE( in_base == in.base() ); - BOOST_REQUIRE( in.size() == 5 ); - - in = +in.transposed(); - - BOOST_REQUIRE( in.size() == 3 ); - BOOST_REQUIRE( in == in_copy.transposed() ); // prove correctness - BOOST_REQUIRE( in_base != in.base() ); // prove no allocation -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_fft_copy) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - - auto const in_copy = in; - auto* const in_base = in.base(); - - multi::array in2 = multi::fftw::fft(in); - - BOOST_REQUIRE( power(in2)/num_elements(in2) - power(in_copy) < 1e-8 ); - BOOST_REQUIRE( in2.base() != in_base ); - BOOST_REQUIRE( not in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_copyconstruct) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - - auto const in_copy = in; - auto* const in_base = in.base(); - - multi::array in2 = multi::fftw::ref(in).transposed(); - - BOOST_REQUIRE( in2 == in_copy.transposed() ); - BOOST_REQUIRE( in2.base() != in_base ); - BOOST_REQUIRE( in .base() == in_base ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_moveconstruct) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - - auto const in_copy = in; - auto* const in_base = in.base(); - - multi::array in2 = multi::fftw::ref(std::move(in)).transposed(); - - BOOST_REQUIRE( in2 == in_copy.transposed() ); - BOOST_REQUIRE( in2.base() == in_base ); - BOOST_REQUIRE( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_moveconstruct_implicit) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - -#if not defined(__INTEL_COMPILER) // TODO(correaa) problem with icpc 2022.3.0.8751 - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - - auto const in_copy = in; - auto* const in_base = in.base(); - - auto in2 = +multi::fftw::ref(std::move(in)).transposed(); - - BOOST_REQUIRE( in2 == in_copy.transposed() ); -#if not defined(__NVCOMPILER) // these tests fail with nvc++ 22.9, 23.1 - BOOST_REQUIRE( in2.base() == in_base ); - BOOST_REQUIRE( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing -#endif -#endif -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_moveassign_from_temp) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - -#if not defined(__INTEL_COMPILER) // TODO(correaa) problem with icpc 2022.3.0.8751 - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - - auto const in_copy = in; - auto* const in_base = in.base(); - - multi::array in2; - in2 = static_cast>(multi::fftw::ref(std::move(in)).transposed()); - - BOOST_REQUIRE( in2 == in_copy.transposed() ); -#if not defined(__NVCOMPILER) // these tests fail with nvc++ 22.9, 23.1 - BOOST_REQUIRE( in2.base() == in_base ); - BOOST_REQUIRE( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing -#endif -#endif -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_moveassign) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - -#if not defined(__INTEL_COMPILER) // TODO(correaa) problem with icpc 2022.3.0.8751 - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - - auto const in_copy = in; - auto* const in_base = in.base(); - - multi::array in2; - in2 = multi::fftw::ref(std::move(in)).transposed(); - - BOOST_REQUIRE( in2 == in_copy.transposed() ); -#if not defined(__NVCOMPILER) // these tests fail with nvc++ 22.9, 23.1 - BOOST_REQUIRE( in2.base() == in_base ); - BOOST_REQUIRE( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing -#endif -#endif -} - -BOOST_AUTO_TEST_CASE(fftw_2D_const_range_transposed_fftwmove) { - using complex = std::complex; [[maybe_unused]] auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imag unit - -#if not defined(__INTEL_COMPILER) // TODO(correaa) problem with icpc 2022.3.0.8751 - multi::array in = { - {100.0 + 2.0 * I, 9.0 - 1.0 * I, 2.0 + 4.0 * I}, - { 3.0 + 3.0 * I, 7.0 - 4.0 * I, 1.0 + 9.0 * I}, - { 4.0 + 1.0 * I, 5.0 + 3.0 * I, 2.0 + 4.0 * I}, - { 3.0 - 1.0 * I, 8.0 + 7.0 * I, 2.0 + 1.0 * I}, - { 31.0 - 1.0 * I, 18.0 + 7.0 * I, 2.0 + 10.0 * I}, - }; - - auto const in_copy = in; - auto* const in_base = in.base(); - - multi::array in2; - in2 = multi::fftw::move(in).transposed(); - - BOOST_REQUIRE( in2 == in_copy.transposed() ); -#if not defined(__NVCOMPILER) // these tests fail with nvc++ 22.9, 23.1 - BOOST_REQUIRE( in2.base() == in_base ); - BOOST_REQUIRE( in.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing -#endif -#endif -} diff --git a/include/multi/adaptors/fftw/test/shift.cpp b/include/multi/adaptors/fftw/test/shift.cpp deleted file mode 100644 index d9f9a8d92..000000000 --- a/include/multi/adaptors/fftw/test/shift.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW shift" -#include - -#include "../../fftw.hpp" - -#include // NOLINT(build/c++11) -#include - -template -class n_random_complex { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - std::size_t n_ = 0; - mutable std::mt19937 gen_{std::random_device{}()}; // NOLINT(whitespace/braces) cpplint 1.6 bug - mutable std::uniform_real_distribution<> dist_{-1., 1.}; - - public: - n_random_complex(n_random_complex const&) = delete; - explicit n_random_complex(std::size_t n) : n_{n} {} - - class iterator : public boost::multi::random_access_iterator, std::complex, void> { - n_random_complex const* ptr_; - std::size_t n_; - - public: // NOLINT(whitespace/indent) cpplint 1.6 bug - iterator(n_random_complex const* ptr, std::size_t n) : ptr_{ptr}, n_{n} {} - - auto operator*() const {return std::complex{ptr_->dist_(ptr_->gen_), ptr_->dist_(ptr_->gen_)};} - auto operator++() -> iterator& {++n_; return *this;} - - friend auto operator==(iterator const& self, iterator const& other) {return self.n_ == other.n_;} - friend auto operator!=(iterator const& self, iterator const& other) {return self.n_ != other.n_;} - - auto operator-(iterator const& other) const {return n_ - other.n_;} - }; - auto begin() const {return iterator{this, 0 };} - auto end () const {return iterator{this, n_};} - - auto size() const {return n_;} -}; - -namespace multi = boost::multi; -namespace fftw = multi::fftw; - -using fftw_fixture = fftw::environment; -BOOST_TEST_GLOBAL_FIXTURE( fftw_fixture ); - -BOOST_AUTO_TEST_CASE(fftw_shift) { - class watch : std::chrono::steady_clock { - time_point start_ = now(); - - public: // NOLINT(whitespace/indent) cpplint 1.6 bug - auto elapsed_sec() const {return std::chrono::duration(now() - start_).count();} - }; - - multi::array, 1> const arr = n_random_complex(19586); BOOST_REQUIRE(arr.size() == 19586); - multi::array, 1> res(arr.extensions()); BOOST_REQUIRE(res.size() == 19586); - - fftw::plan fdft{arr, res, multi::fftw::forward}; - - [&, unnamed = watch{}] { - auto const repeat = 40; - std::for_each( - multi::extension_t{0, repeat}.begin(), multi::extension_t{0, repeat}.end(), [&fdft, &arr, &res](auto /*idx*/) { - fdft(arr.base(), res.base()); - std::rotate(res.begin(), res.begin() + res.size()/2, res.end()); - }); - BOOST_TEST_MESSAGE( "FFTW shift "<< unnamed.elapsed_sec()/repeat <<" sec" ); // prints 0.000882224 sec - }(); -} diff --git a/include/multi/adaptors/fftw/test/so_shift.cpp b/include/multi/adaptors/fftw/test/so_shift.cpp deleted file mode 100644 index ab33330c6..000000000 --- a/include/multi/adaptors/fftw/test/so_shift.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022 Alfredo A. Correa - -#include - -#include // includes fftw3.hpp - -#include // for std::rotate -#include -#include -#include // for std::iota - -namespace multi = boost::multi; - -auto main() -> int { - using complex = std::complex; - - // input array - auto const x = std::invoke([] { // NOLINT(readability-identifier-length) - multi::array ret(8); - // fill the first array with some numbers - std::iota(ret.begin(), ret.end(), 1.0); - return ret; - }); - - // output array - // multi::array y(x.size()); // NOLINT(readability-identifier-length) - // compute the FFT of x and store results in y - auto y = +multi::fftw::dft_forward(x); // NOLINT(readability-identifier-length) - - // display the results - std::cout << "FFT =" << std::endl; - std::copy(y.begin(), y.end(), std::ostream_iterator(std::cout, "\n")); - - // "shifted" results - std::rotate(y.begin(), y.begin() + y.size() / 2 + y.size() % 2, y.end()); - - std::cout << "FFT shifted =" << std::endl; - std::copy(y.begin(), y.end(), std::ostream_iterator(std::cout, "\n")); -} diff --git a/include/multi/adaptors/fftw/test/thrust.cpp b/include/multi/adaptors/fftw/test/thrust.cpp deleted file mode 100644 index bcdfc0f38..000000000 --- a/include/multi/adaptors/fftw/test/thrust.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4-*- -$CXX $0 -o $0x -lfftw3 -lboost_unit_test_framework -ftemplate-backtrace-limit=0&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020 - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW adaptor (cpu) with thrust complex" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../fftw.hpp" - -#include -#include - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(fftw_2D_identity){ - - using complex = thrust::complex; complex const I{0, 1}; - - multi::array const in = { - { 1. + 2.*I, 9. - 1.*I, 2. + 4.*I}, - { 3. + 3.*I, 7. - 4.*I, 1. + 9.*I}, - { 4. + 1.*I, 5. + 3.*I, 2. + 4.*I}, - { 3. - 1.*I, 8. + 7.*I, 2. + 1.*I}, - { 31. - 1.*I, 18. + 7.*I, 2. + 10.*I} - }; - auto fwd = multi::fftw::dft({true, true}, in, multi::fftw::forward); - - multi::array, 2> const in_t = in; - - auto fwd_t = multi::fftw::dft({true, true}, in_t, multi::fftw::forward); - - BOOST_REQUIRE( fwd == fwd_t ); - -} - diff --git a/include/multi/adaptors/fftw/test/transpose.cpp b/include/multi/adaptors/fftw/test/transpose.cpp deleted file mode 100644 index 00e40ba54..000000000 --- a/include/multi/adaptors/fftw/test/transpose.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW transpose" -#include - -#include - -#include // NOLINT(build/c++11) -#include -#include -#include - -namespace multi = boost::multi; - -using namespace std::string_literals; // NOLINT(build/namespaces) for ""s - -class watch : private std::chrono::high_resolution_clock { - std::string label; - time_point start = now(); - - public: - template - explicit watch(String&& label) : label{std::forward(label)} {} // std::string NOLINT(fuchsia-default-arguments-calls) - watch(watch const&) = delete; - watch(watch&&) = delete; - - auto operator=(watch const&) = delete; - auto operator=(watch&&) = delete; - - auto elapsed_sec() const {return std::chrono::duration(now() - start).count();} - ~watch() {std::cerr<< label <<": "<< elapsed_sec() <<" sec"<; - - { - auto const in = [] { - // multi::array ret({10137, 9973}); - // multi::array ret({1013, 997}); - multi::array ret({101, 99}); - std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), - [eng = std::default_random_engine{std::random_device{}()}, uniform_01 = std::uniform_real_distribution<>{}]() mutable{ - return complex{uniform_01(eng), uniform_01(eng)}; - } - ); - // std::cout<<"memory size "<< ret.num_elements()*sizeof(complex)/1e6 <<" MB\n"; - return ret; - }(); - - { - multi::array out = in; - { - watch const unnamed{"transposition with aux %ws wall, CPU (%p%)\n"s}; - multi::array aux = ~out; - out = std::move(aux); - BOOST_REQUIRE( out[35][79] == in[79][35] ); - } - } - { - multi::array out = in; - auto* out_data = out.data_elements(); - { - watch const unnamed{"fftw transpose fun thread %ws wall, CPU (%p%)\n"s}; - multi::fftw::transpose( out ); - BOOST_REQUIRE( out.data_elements() == out_data ); - BOOST_REQUIRE( out[35][79] == in[79][35] ); - } - BOOST_REQUIRE( out == ~in ); - } - } -} diff --git a/include/multi/adaptors/fftw/test/transpose_square.cpp b/include/multi/adaptors/fftw/test/transpose_square.cpp deleted file mode 100644 index 4f18eb09a..000000000 --- a/include/multi/adaptors/fftw/test/transpose_square.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2022 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi FFTW transpose" - -#include -#include - -#include - -#include // NOLINT(build/c++11) -#include -#include -#include - -namespace multi = boost::multi; - -using namespace std::string_literals; // NOLINT(build/namespaces) for ""s - -using fftw_fixture = multi::fftw::environment; -BOOST_TEST_GLOBAL_FIXTURE( fftw_fixture ); - -using complex = std::complex; - -class watch : private std::chrono::high_resolution_clock { - std::string label; - time_point start = now(); - - public: - template - explicit watch(String&& label) : label{std::forward(label)} {} // NOLINT(fuchsia-default-arguments-calls) - watch(watch const&) = delete; - watch(watch&&) = delete; - - auto operator=(watch const&) = delete; - auto operator=(watch&&) = delete; - - auto elapsed_sec() const {return std::chrono::duration(now() - start).count();} - ~watch() { std::cerr<< label <<": "<< elapsed_sec() <<" sec"< ret({819, 819}); - multi::array ret({81, 81}); - std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(), - [eng = std::default_random_engine{std::random_device{}()}, uniform_01 = std::uniform_real_distribution<>{}]() mutable{ - return complex{uniform_01(eng), uniform_01(eng)}; - } - ); - // std::cout<<"memory size "<< ret.num_elements()*sizeof(complex)/1e6 <<" MB\n"; - return ret; - }(); - // multi::fftw::plan::with_nthreads(1); - { - multi::array out = in; - auto* data = out.data_elements(); - { - watch const unnamed{"fftw trans mve 1 thread %ws wall, CPU (%p%)\n"s}; - multi::fftw::transpose( out ); - BOOST_REQUIRE( out.data_elements() == data ); - BOOST_REQUIRE( out[35][79] == in[79][35] ); - } - BOOST_REQUIRE( out == ~in ); - } -// { -// multi::array out = in; -// auto p = out.data_elements(); -// { -// boost::timer::auto_cpu_timer t{"fftw trans mve 1 thread %ws wall, CPU (%p%)\n"}; -// out = multi::fftw::copy( transposed( move(out) ) ); -// BOOST_REQUIRE( out.data_elements() == p ); -// BOOST_REQUIRE( out[35][79] == in[79][35] ); -// } -// BOOST_REQUIRE( out == ~in ); -// } -// multi::fftw::plan::with_nthreads(2); -// { -// multi::array out = in; -// auto p = out.data_elements(); -// { -// boost::timer::auto_cpu_timer t{"fftw trans mve 2 thread %ws wall, CPU (%p%)\n"}; -// out = multi::fftw::copy( ~move(out) ); -// BOOST_REQUIRE( out.data_elements() == p ); -// BOOST_REQUIRE( out[35][79] == in[79][35] ); -// } -// BOOST_REQUIRE( out == ~in ); -// } -// multi::fftw::plan::with_nthreads(4); -// { -// multi::array out = in; -// auto p = out.data_elements(); -// { -// boost::timer::auto_cpu_timer t{"fftw trans mve 4 thread %ws wall, CPU (%p%)\n"}; -// out = multi::fftw::copy( ~move(out) ); -// BOOST_REQUIRE( out.data_elements() == p ); -// BOOST_REQUIRE( out[35][79] == in[79][35] ); -// } -// BOOST_REQUIRE( out == ~in ); -// } - { - multi::array out = in; - multi::array aux(extensions(out)); - { - watch const unnamed{"auxiliary copy %ws wall, CPU (%p%)\n"s}; - aux = ~out; - out = std::move(aux); - BOOST_REQUIRE( out[35][79] == in[79][35] ); - } - BOOST_REQUIRE( out == ~in ); - } - { - multi::array out = in; - { - watch const unnamed{"transposition with loop %ws wall, CPU (%p%)\n"s}; - std::for_each(extension(out).begin(), extension(out).end(), [&out](auto idx) { - auto ext = multi::extension_t(0L, idx); - std::for_each(ext.begin(), ext.end(), [&out, idx](auto jdx) { - std::swap(out[idx][jdx], out[jdx][idx]); - }); - }); - BOOST_REQUIRE( out[35][79] == in[79][35] ); - } - BOOST_REQUIRE( out == ~in ); - } - { - multi::array out = in; - { - watch const unnamed{"transposition with loop 2 %ws wall, CPU (%p%)\n"s}; - std::for_each(extension(out).begin(), extension(out).end(), [&out](auto idx) { - auto ext = multi::extension_t(idx + 1, out.size()); - std::for_each(ext.begin(), ext.end(), [&out, idx](auto jdx) { - std::swap(out[idx][jdx], out[jdx][idx]); - }); - }); - BOOST_REQUIRE( out[35][79] == in[79][35] ); - } - BOOST_REQUIRE( out == ~in ); - } - } -} diff --git a/include/multi/adaptors/hipthrust/test/speed.hip b/include/multi/adaptors/hipthrust/test/speed.hip deleted file mode 100644 index d1ae8ca5c..000000000 --- a/include/multi/adaptors/hipthrust/test/speed.hip +++ /dev/null @@ -1,183 +0,0 @@ -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA thrust universal copy and assignment" -#include - -#include - -// #include - -// #include -//#include -#include -// #include - -// #include - -#include - -#include - -#include - -namespace multi = boost::multi; - -// template<> -// inline constexpr bool multi::force_element_trivial_default_construction> = false; - -// template<> -// inline constexpr bool multi::force_element_trivial_default_construction> = false; - -// template<> -// inline constexpr bool multi::force_element_trivial_default_construction> = false; - -// template<> -// inline constexpr bool multi::force_element_trivial_default_construction> = false; - -using test_types = boost::mpl::list< - char, unsigned, int, - ::thrust::complex, std::complex, - ::thrust::complex, std::complex, - double, float>; - -BOOST_AUTO_TEST_CASE(warmup) { - using T = double; - - auto const n = 8000; - - multi::array> src({n, n}); - multi::array> dst(extensions(src)); - - auto const threshold = 0.30; - - auto const size = src.num_elements() * sizeof(T) / 1e9; - - auto const dummy = std::invoke([&] { - auto start_time = std::chrono::high_resolution_clock::now(); - auto r = hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice); - assert(r == hipSuccess); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; - auto rate = size / time.count(); - // std::cout<<"memcpy rate = "<< rate <<" GB/s (ratio = 1)\n"; - return rate; - }); - - auto const memcpy_rate = std::invoke([&] { - auto start_time = std::chrono::high_resolution_clock::now(); - auto r = hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice); - assert(r == hipSuccess); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; - auto rate = size / time.count(); - // std::cout<<"memcpy rate = "<< rate <<" GB/s (ratio = 1)\n"; - return rate; - }); - - { // cctor - auto tick = std::chrono::high_resolution_clock::now(); - - auto dst2 = src; - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - - // std::cout<<"cctor rate = "<< rate <<" GB/s (ratio = "<< ratio <<")\n"; - if(ratio >= threshold) { - std::cout << "x"; - } - } - { // assign - auto tick = std::chrono::high_resolution_clock::now(); - - dst = src; - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - - // std::cout << "assign rate = "<< rate <<" GB/s (ratio = "<< ratio <<")\n"; - if(ratio >= threshold) { - std::cout << "x"; - } - } - { // subarray assign - auto tick = std::chrono::high_resolution_clock::now(); - - dst({0, n - 2}, {0, n - 2}) = src({2, n}, {2, n}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - // std::cout << "subasssign rate = "<< rate <<" GB/s (ratio = "<< ratio << ")\n"; - if(ratio >= threshold) { - std::cout << "x"; - } - } -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(thrust_universal_speed, T, test_types) { - std::cout << typeid(T).name() << " ******************************************\n"; - - auto const n = 8000; - - multi::array> src({n, n}); - multi::array> dst(extensions(src)); - - auto const threshold = 0.10; - - auto const size = src.num_elements() * sizeof(T) / 1e9; - - auto const dummy = std::invoke([&] { - auto start_time = std::chrono::high_resolution_clock::now(); - auto r = hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice); - assert(r == hipSuccess); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; - auto rate = size / time.count(); - std::cout << "memcpy rate = " << rate << " GB/s (warmup)\n"; - return rate; - }); - - auto const memcpy_rate = std::invoke([&] { - auto start_time = std::chrono::high_resolution_clock::now(); - auto r = hipMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), hipMemcpyDeviceToDevice); - assert(r == hipSuccess); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; - auto rate = size / time.count(); - std::cout << "memcpy rate = " << rate << " GB/s (ratio = 1)\n"; - return rate; - }); - - { // cctor - auto tick = std::chrono::high_resolution_clock::now(); - - auto dst2 = src; - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - - std::cout << "cctor rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; - BOOST_TEST(ratio >= threshold); - } - { // assign - auto tick = std::chrono::high_resolution_clock::now(); - - dst = src; - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - - std::cout << "assign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; - BOOST_TEST(ratio >= threshold); - } - { // subarray assign - auto tick = std::chrono::high_resolution_clock::now(); - - dst({0, n - 2}, {0, n - 2}) = src({2, n}, {2, n}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - std::cout << "subasssign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; - BOOST_TEST(ratio >= threshold); - } -} diff --git a/include/multi/adaptors/lapack.hpp b/include/multi/adaptors/lapack.hpp deleted file mode 100644 index 0811de557..000000000 --- a/include/multi/adaptors/lapack.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#include "lapack/getrf.hpp" - diff --git a/include/multi/adaptors/lapack/geqrf.hpp b/include/multi/adaptors/lapack/geqrf.hpp deleted file mode 100644 index 1386d673a..000000000 --- a/include/multi/adaptors/lapack/geqrf.hpp +++ /dev/null @@ -1,276 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXXX $CXXFLAGS $0 -o $0x$OXX `pkg-config --libs blas lapack` -lboost_unit_test_framework&&$0x$OXX -x 0&&rm $0x$OXX;exit -#endif -// © Alfredo A. Correa 2020 - -#ifndef MULTI_ADAPTORS_LAPACK_GEQRF_HPP -#define MULTI_ADAPTORS_LAPACK_GEQRF_HPP - -#include "../lapack/core.hpp" -#include "../blas/filling.hpp" - -#include "../../config/NODISCARD.hpp" - -#include - -namespace boost{namespace multi{namespace lapack{ - -using blas::filling; - -template -A&& geqrf(Context&& ctxt, A&& a, TAU&& tau, WORK&& work){ -// assert( stride(~a) == 1); - assert( size(tau) == std::min(size(~a), size(a)) ); - int info = -1; - geqrf_(std::forward(ctxt), size(~a), size(a), a.base(), stride(a), tau.base(), work.data(), work.size(), info); - assert(info == 0); - return std::forward(a); -} - -//using ::core::syev; -//using ::core::geqrf; - -#if 0 -template -auto syev(blas::filling uplo, Array2D&& a, Array1D&& w, Array1DW&& work) -->decltype(syev('V', uplo==blas::filling::upper?'L':'U', size(a), base(a), stride(a), base(w), base(work), size(work), std::declval()), a({0l, 1l}, {0l, 1l})) -{ - assert( size(work) >= std::max(1l, 3*size(a)-1l) ); - assert( size(a) == size(w) ); - assert( stride(w)==1 ); - assert( stride(work)==1 ); - if(size(a)==0) return std::forward(a)(); - int info = -1; - if(stride(rotated(a))==1) syev('V', uplo==blas::filling::upper?'L':'U', size(a), base(a), stride( a ), base(w), base(work), size(work), info); - else if(stride( a )==1) syev('V', uplo==blas::filling::upper?'U':'L', size(a), base(a), stride(rotated(a)), base(w), base(work), size(work), info); - else assert(0); // case not contemplated by lapack - if(info < 0) assert(0); // bad argument - return std::forward(a)({0, size(a)-info}, {0, size(a)-info}); -} - -template::decay_type> -auto syev(blas::filling uplo, Array2D&& a, Array1D&& w) -->decltype(syev(uplo, std::forward(a), std::forward(w), Array1DW(std::max(1l, 3*size(a)-1l), get_allocator(w)))){ - return syev(uplo, std::forward(a), std::forward(w), Array1DW(std::max(1l, 3*size(a)-1l), get_allocator(w)));}// TODO obtain automatic size from lapack info routine - -template -NODISCARD("because input array is const, output gives eigenvectors") -typename Array2D::decay_type syev(blas::filling uplo, Array2D const& a, Array1D&& w){ - auto ret = a.decay(); - if(syev(uplo, ret, std::forward(w)).size() != a.size()) assert(0); // failed - return ret; -} - -template -NODISCARD("because input array is const, output gives eigenvalues") -auto syev(blas::filling uplo, Array2D&& a){ - multi::array::element_type, 1, decltype(get_allocator(a))> eigenvalues(size(a), get_allocator(a)); - syev(uplo, std::forward(a), eigenvalues); - return eigenvalues; -} - -template -NODISCARD("because input array is const, output gives a structured binding of eigenvectors and eigenvactor") -auto syev(blas::filling uplo, Array2D const& a){ - struct{ - typename Array2D::decay_type eigenvectors; - typename Array2D::value_type eigenvalues; - } ret{a, {size(a), get_allocator(a)}}; - auto&& l = syev(uplo, ret.eigenvectors, ret.eigenvalues); - assert( size(l) == size(a) ); - return ret; -} -#endif - -}}} - -#if not __INCLUDE_LEVEL__ - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi lapack adaptor geqrf" -#if not __INTEL_COMPILER -#define BOOST_TEST_DYN_LINK -#endif -#include - -#include "../../array.hpp" - -#include // std::isnan -#include -#include // std::max - -namespace multi = boost::multi; -namespace lapack = multi::lapack; - -template decltype(auto) print(M const& C){ - using std::cout; - using multi::size; - for(int i = 0; i != size(C); ++i){ - for(int j = 0; j != size(C[i]); ++j) cout << C[i][j] << ' '; - cout << std::endl; - } - return cout << std::endl; -} - -template decltype(auto) print_1d(M const& C){ - using std::cout; - using multi::size; - for(int i = 0; i != size(C); ++i) cout<< C[i] <<' '; - return cout << std::endl; -} - -BOOST_AUTO_TEST_CASE(lapack_geqrf){ - - multi::array A = - { - {1., 2., 3.}, - {4., 5., 6.}, - {7., 8., 9.} - } - ; - multi::lapack::context ctxt; - - multi::array TAU(std::min(size(A), size(~A))); - multi::array WORK(std::max(1l, 3*size(A)-1)); - - multi::lapack::geqrf(ctxt, A, TAU, WORK); - - print(A); - print(TAU); - -} - -#if 0 -BOOST_AUTO_TEST_CASE(lapack_syev, *boost::unit_test::tolerance(0.00001) ){ -{ - multi::array A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - multi::array WORK(std::max(1l, 3*size(A)-1)); - multi::lapack::syev(multi::blas::filling::upper, A, W, WORK); - BOOST_TEST( A[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - multi::array A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - multi::lapack::syev(multi::blas::filling::upper, A, W); - BOOST_TEST( A[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - multi::array A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - multi::lapack::syev(multi::blas::filling::lower, rotated(A), W); - BOOST_TEST( A[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - namespace lapack = multi::lapack; - multi::array A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - auto W = lapack::syev(multi::blas::filling::upper, A); - BOOST_TEST( A[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - multi::array const A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto A_copy = lapack::syev(lapack::filling::upper, A, W); - BOOST_TEST( A[1][2] == 126.746 ); - BOOST_TEST( A_copy[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - multi::array A = { - {167.413, 126.804, 0.}, - {NAN , 167.381, 0.}, - {NAN , NAN , 0.} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto&& A_ref = lapack::syev(lapack::filling::upper, A, W); - BOOST_TEST( size(A_ref)==3 ); - BOOST_TEST( W[0]==0. ); -} -{ - multi::array A = { - {1. , 1., 1.}, - {NAN, 2 , 1.}, - {NAN, NAN, 1.} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto&& A_ref = lapack::syev(lapack::filling::upper, A, W); - print(A_ref); - BOOST_TEST( size(A_ref)==3 ); - BOOST_TEST( W[0]==0. ); -} -{ - multi::array A = {{5.}}; - multi::array W(size(A)); - namespace lapack = multi::lapack; - lapack::syev(lapack::filling::upper, A, W); - BOOST_TEST( A[0][0] == 1. ); - BOOST_TEST( W[0]==5. ); -} -{ - namespace lapack = multi::lapack; - multi::array A; - multi::array W(size(A)); - lapack::syev(lapack::filling::upper, A, W); -} -{ - multi::array const A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto sys = lapack::syev(lapack::filling::upper, A); - BOOST_TEST( A[1][2] == 126.746 ); - BOOST_TEST( sys.eigenvectors[2][1] == -0.579092 ); - BOOST_TEST( sys.eigenvalues[1] == 42.2081 ); -} -#if __cpp_structured_bindings -{ - multi::array const A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto [eigenvecs, eigenvals] = lapack::syev(lapack::filling::upper, A); - BOOST_TEST( A[1][2] == 126.746 ); - BOOST_TEST( eigenvecs[2][1] == -0.579092 ); - BOOST_TEST( eigenvals[1] == 42.2081 ); -} -#endif - -} -#endif -#endif - -#endif - - diff --git a/include/multi/adaptors/lapack/getrf.hpp b/include/multi/adaptors/lapack/getrf.hpp deleted file mode 100644 index 96475fdd4..000000000 --- a/include/multi/adaptors/lapack/getrf.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXXX $CXXFLAGS $0 -o $0x$OXX `pkg-config --libs blas lapack` -lboost_unit_test_framework&&$0x$OXX -x 0&&rm $0x$OXX;exit -#endif -// © Alfredo A. Correa 2020 - -#ifndef MULTI_ADAPTORS_LAPACK_GEQRF_HPP -#define MULTI_ADAPTORS_LAPACK_GEQRF_HPP - -#include "../lapack/core.hpp" -#include "../blas/filling.hpp" - -#include "../../config/NODISCARD.hpp" - -#include - -//#include - -namespace boost{namespace multi{namespace lapack{ - -using index = int; - -using blas::filling; - -template -auto getrf(Context&& ctxt, A&& a, IPIV&& ipiv){ - assert( ipiv.size() == std::min(size(a), size(~a)) ); - assert( stride(a) == 1 ); -// assert( stride(ipiv) == 1 ); - multi::index i = std::forward(ctxt).getrf(size(~a), size(a), base(a), stride(~a), ipiv.data() ); - if(i == 0) return a(); - else return a({0, i - 1}, {0, i - 1}); -} - -template -void getrs(Context&& ctxt, LU const& lu, IPIV const& ipiv, B&& b){ - assert( size(lu) == size(~lu) ); - assert( stride(lu) == 1 ); - assert( size(ipiv) >= size(lu) ); -// assert( stride(ipiv) == 1 ); - assert( stride(b) == 1 ); - std::forward(ctxt).getrs('N', size(lu), size(~b), base(lu), stride(~lu), ipiv.data(), base(b), stride(~b)); -} - -template -void getrs_one(Context&& ctxt, LU const& lu, IPIV const& ipiv, V&& b){ - assert( size(lu) == size(~lu) ); - assert( stride(lu) == 1 ); -// assert( stride(ipiv) == 1 ); - assert( stride(b) == 1 ); - std::forward(ctxt).getrs('N', size(lu), 1, base(lu), stride(~lu), ipiv.data(), base(b), size(lu)); -} - - -template -auto getrf(A&& a, IPIV&& ipiv){return getrf(::lapack::context{}, std::forward(a), std::forward(ipiv));} - -template -void getrs(LU const& lu, IPIV const& ipiv, B&& b){return getrs(::lapack::context{}, lu, ipiv, std::forward(b));} - -template -void getrs_one(LU const& lu, IPIV const& ipiv, B&& b){return getrs_one(::lapack::context{}, lu, ipiv, std::forward(b));} - - -}}} - -#endif - - diff --git a/include/multi/adaptors/lapack/potrf.hpp b/include/multi/adaptors/lapack/potrf.hpp deleted file mode 100644 index beee951b0..000000000 --- a/include/multi/adaptors/lapack/potrf.hpp +++ /dev/null @@ -1,220 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS -(echo '#include"'$0'"'>$0.cpp)&&$CXX -D_TEST_MULTI_ADAPTORS_LAPACK_POTRF $0.cpp -o$0x `pkg-config --libs blas lapack` -lboost_unit_test_framework&&valgrind $0x&&rm $0x $0.cpp;exit -#endif -// © Alfredo A. Correa 2019-2020 - -#ifndef MULTI_ADAPTORS_LAPACK_POTRF_HPP -#define MULTI_ADAPTORS_LAPACK_POTRF_HPP - -#include "../../array.hpp" -#include "../../config/NODISCARD.hpp" - -#include "../lapack/core.hpp" -#include "../blas/numeric.hpp" - -#include "../blas/filling.hpp" - -#include - -namespace boost{namespace multi{namespace lapack{ - -using blas::filling; - -namespace{ - -using ::core::potrf; - -template -auto potrf(filling t, Iterator first, Iterator last) -->decltype(potrf(static_cast(t), typename std::iterator_traits::difference_type{}, base(first), stride(first), std::declval()), Iterator{}) -{ - assert( stride(first) == stride(last) ); - assert( first->stride() == 1 ); - auto n = std::distance(first, last); -// auto lda = stride(first); - int info; - potrf(static_cast(t), n, base(first), stride(first), info); - assert( info >= 0 ); - return info==0?last:first + info; -} -} - -template -auto potrf(filling t, A2D&& A) -->decltype(potrf(t, begin(A), end(A)), A({0, 1}, {0, 1})) -{ - using blas::flip; - if(stride(A)==1){ - auto last = potrf(flip(t), begin(rotated(A)), end(rotated(A))); - using std::distance; - return A({0, distance(begin(rotated(A)), last)}, {0, distance(begin(rotated(A)), last)}); - } - auto last = potrf(t, begin(A), end(A)); - using std::distance; - return A({0, distance(begin(A), last)}, {0, distance(begin(A), last)}); -} - -template -struct hermitic_t : private A{ - using underlying_type = A; - underlying_type const& underlying()const &{return *this;} - underlying_type& underlying()&{return *this;} - underlying_type&& underlying()&&{return std::move(*this);} - blas::filling side; - hermitic_t(A const& a, blas::filling side) : A{a}, side{side}{} - using A::size; -}; - -template hermitic_t()())>> hermitic(blas::filling side, A&& a){ - return {a(), side}; -} - -template -NODISCARD("result is returned because third argument is const") -auto potrf(filling t, A2D const& A) -->decltype(potrf(t, decay(A)), decay(A)){ - auto ret = decay(A); - auto last = potrf(t, ret); assert( size(last) == size(ret) ); - return ret; -} - -template -NODISCARD("result is returned because third argument is const") -decltype(auto) potrf(HA&& ha){ - return hermitic(ha.side, potrf(ha.side, std::forward(ha).underlying()));//static_cast(ha))); -} - -// orthonormalize rows -template auto onrm(A&& a, filling f = filling::upper) -->decltype(trsm(flip(f), hermitized(potrf(f, herk(f, a))), std::forward(a))){assert(size(a) <= size(rotated(a))); - return trsm(flip(f), hermitized(potrf(f, herk(f, a))), std::forward(a)); -} - -template auto onrm(A&& a, B&& buffer, filling f = filling::upper) -->decltype(trsm(flip(f), hermitized(potrf(f, herk(f, a, buffer))), std::forward(a))){assert(size(a) <= size(rotated(a))); - return trsm(flip(f), hermitized(potrf(f, herk(f, a, buffer))), std::forward(a)); -} - -//template -//decltype(auto) potrf(A2D&& A){return potrf(blas::detect_triangular(A), A);} - -}}} - -#if _TEST_MULTI_ADAPTORS_LAPACK_POTRF - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi lapack adaptor potrf" -#define BOOST_TEST_DYN_LINK -#include - -#include // std::isnan - -namespace multi = boost::multi; -namespace lapack = multi::lapack; - -template decltype(auto) print(M const& C){ - using std::cout; - using multi::size; - for(int i = 0; i != size(C); ++i){ - for(int j = 0; j != size(C[i]); ++j) cout << C[i][j] << ' '; - cout << std::endl; - } - return cout << std::endl; -} - -BOOST_AUTO_TEST_CASE(lapack_potrf, *boost::unit_test::tolerance(0.00001) ){ - using complex = std::complex; complex const I{0, 1}; -{ - multi::array A = { - {167.413, 126.804 - 0.00143505*I, 125.114 - 0.1485590*I}, - {NAN , 167.381 , 126.746 + 0.0327519*I}, - {NAN , NAN , 167.231 } - }; - using lapack::filling; - using lapack::potrf; - potrf(filling::upper, A); // A is hermitic in upper triangular (implicit below) - BOOST_TEST( real(A[1][2]) == 3.78646 ); - BOOST_TEST( imag(A[1][2]) == 0.0170734 ); -// BOOST_TEST( std::isnan(norm(A[2][1])) ); -} -{ - multi::array A = - {{167.413, 126.804 - 0.00143505*I, 125.114 - 0.1485590*I}, - {NAN, 167.381, 126.746 + 0.0327519*I}, - {NAN, NAN , 167.231}} - ; - multi::array At = rotated(A); - auto&& Att = rotated(At); - using lapack::filling; - using lapack::potrf; - potrf(filling::upper, Att); // A is hermitic in the upper triangular (implicit hermitic below) - BOOST_TEST( real(Att[1][2]) == 3.78646 ); - BOOST_TEST( imag(Att[1][2]) == 0.0170734 ); -// BOOST_TEST( std::isnan(norm(Att[2][1])) ); -} -{ - multi::array A = - {{167.413, 126.804 - 0.00143505*I, 125.114 - 0.1485590*I}, - {NAN, 167.381, 126.746 + 0.0327519*I}, - {NAN, NAN , 167.231}} - ; - using lapack::potrf; - using lapack::filling; - potrf(filling::upper, A); // A is hermitic in the upper triangular (implicit hermitic below) - BOOST_TEST( real(A[1][2]) == 3.78646 ); - BOOST_TEST( imag(A[1][2]) == 0.0170734 ); -// BOOST_TEST( std::isnan(A[2][1]) ); -} -{ - multi::array A = - {{190., 126., 125.}, - {NAN , 1110., 122.}, - {NAN , NAN , 1350.}} - ; - using lapack::filling; - using lapack::potrf; - potrf(filling::upper, A); // A is the upper triangle (implicit hermitic/symmetric below), A becomes upper triangular with implicit zeros - BOOST_TEST( real(A[1][2]) == 1.22058 ); -// BOOST_TEST( std::isnan(norm(A[2][1])) ); -} -{ - multi::array A = - {{190., 126., 125.}, - {NAN , 1110., 122.}, - {NAN , NAN , 1350.}} - ; - using lapack::filling; - using lapack::potrf; - potrf(filling::upper, A); // A is the upper triangle (implicit hermitic/symmetric below), A becomes upper triangular with implicit zeros - BOOST_TEST( A[1][2] == 1.22058 ); -// BOOST_TEST( std::isnan(norm(A[2][1])) ); -} -{ - multi::array A = - {{190., 126., 125.}, - {NAN , 1110., 122.}, - {NAN , NAN , 1350.}} - ; - using lapack::filling; - using lapack::potrf; - potrf(filling::lower, rotated(A)); // A is the upper triangle (implicit hermitic/symmetric below), A becomes upper triangular with implicit symmetry - print(A); - BOOST_TEST( A[1][2] == 1.22058 ); -// BOOST_TEST( std::isnan(norm(A[2][1])) ); -} -{ - multi::array const A = - {{190., 126., 125.}, - {NAN , 1110., 122.}, - {NAN , NAN , 1350.}} - ; - using lapack::filling; - using lapack::potrf; - auto B = potrf(filling::upper, A); - print(B); - BOOST_TEST( real(B[1][2]) == 1.22058 ); -} -} - -#endif -#endif - diff --git a/include/multi/adaptors/lapack/syev.hpp b/include/multi/adaptors/lapack/syev.hpp deleted file mode 100644 index e4ed6798a..000000000 --- a/include/multi/adaptors/lapack/syev.hpp +++ /dev/null @@ -1,231 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXX $0 -o $0x `pkg-config --libs blas lapack` -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020 - -#ifndef MULTI_ADAPTORS_LAPACK_SYEV_HPP -#define MULTI_ADAPTORS_LAPACK_SYEV_HPP - -#include "../lapack/core.hpp" -#include "../blas/filling.hpp" - -#include "../../config/NODISCARD.hpp" - -#include - -namespace boost{namespace multi{namespace lapack{ - -using blas::filling; - -using ::core::syev; - -template -auto syev(blas::filling uplo, Array2D&& a, Array1D&& w, Array1DW&& work) -->decltype(syev('V', uplo==blas::filling::upper?'L':'U', size(a), base(a), stride(a), base(w), base(work), size(work), std::declval()), a({0l, 1l}, {0l, 1l})) -{ - assert( size(work) >= std::max(1l, 3*size(a)-1l) ); - assert( size(a) == size(w) ); - assert( stride(w)==1 ); - assert( stride(work)==1 ); - if(size(a)==0) return std::forward(a)(); - int info = -1; - if(stride(rotated(a))==1) syev('V', uplo==blas::filling::upper?'L':'U', size(a), base(a), stride( a ), base(w), base(work), size(work), info); - else if(stride( a )==1) syev('V', uplo==blas::filling::upper?'U':'L', size(a), base(a), stride(rotated(a)), base(w), base(work), size(work), info); - else assert(0); // case not contemplated by lapack - if(info < 0) assert(0); // bad argument - return std::forward(a)({0, size(a)-info}, {0, size(a)-info}); -} - -template::decay_type> -auto syev(blas::filling uplo, Array2D&& a, Array1D&& w) -->decltype(syev(uplo, std::forward(a), std::forward(w), Array1DW(std::max(1l, 3*size(a)-1l), get_allocator(w)))){ - return syev(uplo, std::forward(a), std::forward(w), Array1DW(std::max(1l, 3*size(a)-1l), get_allocator(w)));}// TODO obtain automatic size from lapack info routine - -template -NODISCARD("because input array is const, output gives eigenvectors") -typename Array2D::decay_type syev(blas::filling uplo, Array2D const& a, Array1D&& w){ - auto ret = a.decay(); - auto l = syev(uplo, ret, std::forward(w)); - if(size(l) != size(a)) assert(0); // failed - return ret; -} - -template -NODISCARD("because input array is const, output gives eigenvalues") -auto syev(blas::filling uplo, Array2D&& a){ - multi::array::element_type, 1, decltype(get_allocator(a))> eigenvalues(size(a), get_allocator(a)); - syev(uplo, std::forward(a), eigenvalues); - return eigenvalues; -} - -template -NODISCARD("because input array is const, output gives a structured binding of eigenvectors and eigenvactor") -auto syev(blas::filling uplo, Array2D const& a){ - struct{ - typename Array2D::decay_type eigenvectors; - typename Array2D::value_type eigenvalues; - } ret{a, {size(a), get_allocator(a)}}; - auto&& l = syev(uplo, ret.eigenvectors, ret.eigenvalues); - assert( size(l) == size(a) ); - return ret; -} - -}}} - -#if not __INCLUDE_LEVEL__ // _TEST_MULTI_ADAPTORS_LAPACK_SYEV - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi lapack adaptor syev" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../array.hpp" - -#include // std::isnan -#include -#include // std::max - -namespace multi = boost::multi; -namespace lapack = multi::lapack; - -template decltype(auto) print(M const& C){ - using std::cout; - using multi::size; - for(int i = 0; i != size(C); ++i){ - for(int j = 0; j != size(C[i]); ++j) cout << C[i][j] << ' '; - cout << std::endl; - } - return cout << std::endl; -} - -BOOST_AUTO_TEST_CASE(lapack_syev, *boost::unit_test::tolerance(0.00001) ){ -{ - multi::array A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - multi::array WORK(std::max(1l, 3*size(A)-1)); - multi::lapack::syev(multi::blas::filling::upper, A, W, WORK); - BOOST_TEST( A[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - multi::array A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - multi::lapack::syev(multi::blas::filling::upper, A, W); - BOOST_TEST( A[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - multi::array A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - multi::lapack::syev(multi::blas::filling::lower, rotated(A), W); - BOOST_TEST( A[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - namespace lapack = multi::lapack; - multi::array A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - auto W = lapack::syev(multi::blas::filling::upper, A); - BOOST_TEST( A[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - multi::array const A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto A_copy = lapack::syev(lapack::filling::upper, A, W); - BOOST_TEST( A[1][2] == 126.746 ); - BOOST_TEST( A_copy[2][1] == -0.579092 ); - BOOST_TEST( W[1] == 42.2081 ); -} -{ - multi::array A = { - {167.413, 126.804, 0.}, - {NAN , 167.381, 0.}, - {NAN , NAN , 0.} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto&& A_ref = lapack::syev(lapack::filling::upper, A, W); - BOOST_TEST( size(A_ref)==3 ); - BOOST_TEST( W[0]==0. ); -} -{ - multi::array A = { - {1. , 1., 1.}, - {NAN, 2 , 1.}, - {NAN, NAN, 1.} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto&& A_ref = lapack::syev(lapack::filling::upper, A, W); - print(A_ref); - BOOST_TEST( size(A_ref)==3 ); - BOOST_TEST( W[0]==0. ); -} -{ - multi::array A = {{5.}}; - multi::array W(size(A)); - namespace lapack = multi::lapack; - lapack::syev(lapack::filling::upper, A, W); - BOOST_TEST( A[0][0] == 1. ); - BOOST_TEST( W[0]==5. ); -} -{ - namespace lapack = multi::lapack; - multi::array A; - multi::array W(size(A)); - lapack::syev(lapack::filling::upper, A, W); -} -{ - multi::array const A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto sys = lapack::syev(lapack::filling::upper, A); - BOOST_TEST( A[1][2] == 126.746 ); - BOOST_TEST( sys.eigenvectors[2][1] == -0.579092 ); - BOOST_TEST( sys.eigenvalues[1] == 42.2081 ); -} -#if __cpp_structured_bindings -{ - multi::array const A = { - {167.413, 126.804, 125.114}, - {NAN , 167.381, 126.746}, - {NAN , NAN , 167.231} - }; - multi::array W(size(A)); - namespace lapack = multi::lapack; - auto [eigenvecs, eigenvals] = lapack::syev(lapack::filling::upper, A); - BOOST_TEST( A[1][2] == 126.746 ); - BOOST_TEST( eigenvecs[2][1] == -0.579092 ); - BOOST_TEST( eigenvals[1] == 42.2081 ); -} -#endif - -} -#endif -#endif - - diff --git a/include/multi/adaptors/lapack/test/CMakeLists.txt b/include/multi/adaptors/lapack/test/CMakeLists.txt deleted file mode 100644 index c827cf634..000000000 --- a/include/multi/adaptors/lapack/test/CMakeLists.txt +++ /dev/null @@ -1,69 +0,0 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- -#[=[Multi Test suite can be run like this: - mkdir -p build - cd build - cmake .. [-DENABLE_CUDA=1] - make -j - ctest -j --output-on-error [-T memcheck] - exit -#]=] -cmake_minimum_required(VERSION 3.11) - -set(CMAKE_VERBOSE_MAKEFILE ON) - -project( - boost-multi-adaptors-lapack-test - VERSION 0.1 - LANGUAGES CXX -) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -find_package(Boost REQUIRED COMPONENTS unit_test_framework) -add_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) -include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) -link_libraries(${Boost_LIBRARIES}) -link_directories(${Boost_LIBRARY_DIRS}) - -# find_package(BLAS REQUIRED) - -set(BLA_VENDOR OpenBLAS) -# set(BLA_VENDOR Intel10_64lp) find_package(BLAS) if(BLAS_FOUND) # in some systems with MKL, regular BLAS headers need to be found for it to work message("Multi/BLAS: MKL environment detected") add_definitions(-DRETURN_BY_STACK) else() -# message("Multi/BLAS: MKL environment not detected, looking for other BLAS") unset(BLA_VENDOR) -find_package(BLAS REQUIRED) -# endif() - -find_path( - BLAS_INCLUDE_DIRS - cblas.h - /usr/include - /usr/local/include - $ENV{BLAS_HOME}/include -) - -include_directories(../../../..) - -link_libraries(${BLAS_LIBRARIES}) -link_libraries(-llapacke) - -include_directories(${TEST_EXE} PRIVATE ${BLAS_INCLUDE_DIRS}) - -add_compile_options( - -Werror - -Wall - -Wextra - -fno-common - -Wfatal-errors - # $<$: -Wpedantic -Wformat-truncation -fstack-usage >#-Wconversion - # $<$,$>: - # -Wpedantic -Wmove > $<$: -wd161 -diag-disable=remark -Warray-bounds -Wchar-subscripts -Wcomment -Wenum-compare -Wformat -Wuninitialized -Wmaybe-uninitialized -Wmain -Wnarrowing -Wnonnull -Wparentheses - # -Wpointer-sign -Wreorder -Wno-return-type -Wsign-compare -Wsequence-point -Wtrigraphs -Wunused-function -Wunused-but-set-variable -Wunused-variable -Wwrite-strings -Werror -diag-error:3846 > $<$: /W4 > -) - -enable_testing() -include(CTest) - -add_executable(getrf.cpp.x getrf.cpp) -add_test(NAME getrf.cpp.x COMMAND ./getrf.cpp.x) diff --git a/include/multi/adaptors/lapack/test/getrf.cpp b/include/multi/adaptors/lapack/test/getrf.cpp deleted file mode 100644 index 7f5485015..000000000 --- a/include/multi/adaptors/lapack/test/getrf.cpp +++ /dev/null @@ -1,304 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS -(echo '#include"'$0'"'>$0.cpp)&&nvcc -x cu --expt-relaxed-constexpr`#$CXX` $0 -o $0x -Wno-deprecated-declarations -lcudart -lcublas -lcusolver `pkg-config --libs blas lapack` -DBOOST_TEST_DYN_LINK -lboost_unit_test_framework -DBOOST_LOG_DYN_LINK -lboost_log -lpthread -lboost_system &&$0x&&rm $0x $0.cpp; exit -#endif -// © Alfredo A. Correa 2019-2020 -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi getrf" -#include - -#include -#include -#include -#include - -namespace multi = boost::multi; - -//BOOST_AUTO_TEST_CASE(lapack_getrf){ - -//// https://www.ibm.com/support/knowledgecenter/SSFHY8_6.2/reference/am5gr_hsgetrf.html -// multi::array A = { -// { 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6 }, -// { 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4 }, -// { 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2 }, -// { 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0 }, -// { 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8 }, -// { 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6 }, -// { 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4 }, -// { 2.4, 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2 }, -// { 2.6, 2.4, 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0 } -// }; - -// multi::array P({9}, 0.); -// lapack::context ctxt; -// auto const& LU = multi::lapack::getrf(ctxt, A, P); - -// BOOST_REQUIRE( LU.size() == A.size() ); - -// BOOST_REQUIRE_CLOSE( LU[0][0] , 2.6 , 1e-5 ); -// BOOST_REQUIRE_CLOSE( LU[0][8] , 1. , 1e-5 ); -// BOOST_REQUIRE_CLOSE( LU[8][0] , 0.923077 , 1e-5 ); -// BOOST_REQUIRE_CLOSE( LU[8][8] , 0.4 , 1e-5 ); - -//} - -//BOOST_AUTO_TEST_CASE(lapack_getrf2){ - -//// https://www.ibm.com/support/knowledgecenter/SSFHY8_6.2/reference/am5gr_hsgetrf.html -// multi::array A = { -// { 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, -// { 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0 }, -// { 4.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0 }, -// { 0.0, 5.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0 }, -// { 0.0, 0.0, 6.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0 }, -// { 0.0, 0.0, 0.0, 7.0, 1.0, 1.0, 1.0, 1.0, 1.0 }, -// { 0.0, 0.0, 0.0, 0.0, 8.0, 1.0, 1.0, 1.0, 1.0 }, -// { 0.0, 0.0, 0.0, 0.0, 0.0, 9.0, 1.0, 1.0, 1.0 }, -// { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 11.0, 12.0 } -// }; - -// multi::array P({9}, 0.); -// lapack::context ctxt; -// auto const& LU = multi::lapack::getrf(ctxt, A, P); - -// BOOST_REQUIRE( LU.size() == A.size() ); - -// for(int i = 0; i != 9; ++i){ -// for(int j = 0; j != 9; ++j){ -// std::cout<<'\t'<< LU[i][j] <<','; -// } -// std::cout< const Aconst = { - { 6.80, -6.05, -0.45, 8.32, -9.67}, - {-2.11, -3.30, 2.58, 2.71, -5.14}, - { 5.66, 5.36, -2.70, 4.35, -7.26}, - { 5.97, -4.44, 0.27,-7.17, 6.08}, - { 8.23, 1.08, 9.04, 2.14, -6.87} - }; - - multi::array Bconst = { - { 4.02, -1.56, 9.81}, - { 6.19, 4.00, -4.09}, - {-8.22, -8.67, -4.57}, - {-7.57, 1.75, -8.61}, - {-3.03, 2.86, 8.99} - }; - - multi::array P({5}, 0.); - - auto A = Aconst; - auto B = Bconst; - - auto AT = +~A; - auto BT = +~B; - - auto lu_solve = [](auto&& Aio, auto&& Po, auto&& Bio){ // solve A.X = B; put result in B - auto AT = +~Aio; - auto BT = +~Bio; - - auto const& LU = multi::lapack::getrf(~AT, Po); - assert( LU.size() == Po.size() ); - multi::lapack::getrs(LU, std::as_const(Po), ~BT); - - Bio = ~BT; - Aio = ~AT; - }; - lu_solve(~AT, P, ~BT); - - using multi::blas::operators::operator*; - BOOST_REQUIRE_CLOSE( (Aconst*(~BT))[1][2] , Bconst[1][2] , 1e-10); - -} - -BOOST_AUTO_TEST_CASE(lapack_getrf_two_column){ - - multi::array const Aconst = { - { 6.80, -6.05, -0.45, 8.32, -9.67}, - {-2.11, -3.30, 2.58, 2.71, -5.14}, - { 5.66, 5.36, -2.70, 4.35, -7.26}, - { 5.97, -4.44, 0.27,-7.17, 6.08}, - { 8.23, 1.08, 9.04, 2.14, -6.87} - }; - - multi::array Bconst = { - { 4.02, -1.56}, - { 6.19, 4.00}, - {-8.22, -8.67}, - {-7.57, 1.75}, - {-3.03, 2.86} - }; - - multi::array P({5}, 0.); - - auto A = Aconst; - auto B = Bconst; - - auto AT = +~A; - auto BT = +~B; - - auto lu_solve = [](auto&& Aio, auto&& Po, auto&& Bio){ // solve A.X = B; put result in B - auto AT = +~Aio; - auto BT = +~Bio; - - auto const& LU = multi::lapack::getrf(~AT, Po); - assert( LU.size() == Po.size() ); - multi::lapack::getrs(LU, std::as_const(Po), ~BT); - - Bio = ~BT; - Aio = ~AT; - }; - lu_solve(~AT, P, ~BT); - - using multi::blas::operators::operator*; - BOOST_REQUIRE_CLOSE( (Aconst*(~BT))[2][1] , Bconst[2][1] , 1e-10); - BOOST_REQUIRE_CLOSE( (Aconst*(~BT))[2][0] , Bconst[2][0] , 1e-10); - -} - -BOOST_AUTO_TEST_CASE(lapack_getrf_one_column){ - - multi::array const Aconst = { - { 6.80, -6.05, -0.45, 8.32, -9.67}, - {-2.11, -3.30, 2.58, 2.71, -5.14}, - { 5.66, 5.36, -2.70, 4.35, -7.26}, - { 5.97, -4.44, 0.27,-7.17, 6.08}, - { 8.23, 1.08, 9.04, 2.14, -6.87} - }; - - multi::array Bconst = { - { 4.02}, - { 6.19}, - {-8.22}, - {-7.57}, - {-3.03} - }; - - multi::array P({5}, 0.); - - auto A = Aconst; - auto B = Bconst; - - auto lu_solve = [](auto&& Aio, auto&& Po, auto&& Bio){ // solve A.X = B; put result in B - auto AT = +~Aio; - auto BT = +~Bio; - - auto const& LU = multi::lapack::getrf(~AT, Po); - assert( LU.size() == Po.size() ); - multi::lapack::getrs(LU, std::as_const(Po), ~BT); - - Bio = ~BT; - Aio = ~AT; - }; - lu_solve(A, P, B); - - using multi::blas::operators::operator*; - BOOST_REQUIRE_CLOSE( (Aconst*B)[1][0] , Bconst[1][0] , 1e-10); - BOOST_REQUIRE_CLOSE( (Aconst*B)[1][0] , Bconst[1][0] , 1e-10); - -} - -BOOST_AUTO_TEST_CASE(lapack_getrf_one_vector){ - - multi::array const Aconst = { - { 6.80, -6.05, -0.45, 8.32, -9.67}, - {-2.11, -3.30, 2.58, 2.71, -5.14}, - { 5.66, 5.36, -2.70, 4.35, -7.26}, - { 5.97, -4.44, 0.27,-7.17, 6.08}, - { 8.23, 1.08, 9.04, 2.14, -6.87} - }; - - multi::array Vconst = {4.02, 6.19, -8.22, -7.57, -3.03}; - - multi::array P({5}, 0.); - - auto A = Aconst; - auto V = Vconst; - - auto lu_solve_one = [](auto&& Aio, auto&& Po, auto&& V){ // solve A.X = B; put result in B - auto AT = +~Aio; - // auto BT = +~Bio; - - auto const& LU = multi::lapack::getrf(~AT, Po); - assert( LU.size() == Po.size() ); - multi::lapack::getrs_one(LU, std::as_const(Po), V); - - // Bio = ~BT; - Aio = ~AT; - }; - lu_solve_one(A, P, V); - - using multi::blas::operators::operator%; - BOOST_REQUIRE_CLOSE( (Aconst%V)[2] , Vconst[2] , 1e-10); - -} - -BOOST_AUTO_TEST_CASE(lapack_getrs){ - -// https://www.ibm.com/support/knowledgecenter/SSFHY8_6.2/reference/am5gr_hsgetrf.html - multi::array A = { - { 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6 }, - { 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4 }, - { 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2 }, - { 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0 }, - { 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6, 1.8 }, - { 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4, 1.6 }, - { 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2, 1.4 }, - { 2.4, 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 1.2 }, - { 2.6, 2.4, 2.2, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0 } - }; - - multi::array P({9}, 0.); - - auto B = +~multi::array{ - { 93.0, 186.0},// 279.0, 372.0, 465.0 }, - { 84.4, 168.8},// 253.2, 337.6, 422.0 }, - { 76.6, 153.2},// 229.8, 306.4, 383.0 }, - { 70.0, 140.0},// 210.0, 280.0, 350.0 }, - { 65.0, 130.0},// 195.0, 260.0, 325.0 }, - { 62.0, 124.0},//, 186.0, 248.0, 310.0 }, - { 61.4, 122.8},// 184.2, 245.6, 307.0 }, - { 63.6, 127.2},// 190.8, 254.4, 318.0 }, - { 69.0, 138.0}//, 207.0, 276.0, 345.0 } - }; - - lapack::context ctxt; - multi::lapack::getrf(ctxt, ~A, P); - - multi::array d({9}, 0.); - for(int i = 0; i !=9; ++i) d[P[i]]=i; - - - for(int i = 0; i != size(A); ++i){ - for(int j = 0; j != size(~A); ++j){ - std::cout<<'\t'<< A[i][j] <<','; - } - std::cout<$0.cpp)&&nvcc -x cu --expt-relaxed-constexpr`#$CXX` $0 -o $0x -Wno-deprecated-declarations -lcudart -lcublas -lcusolver `pkg-config --libs blas lapack` -DBOOST_TEST_DYN_LINK -lboost_unit_test_framework -DBOOST_LOG_DYN_LINK -lboost_log -lpthread -lboost_system &&$0x&&rm $0x $0.cpp; exit -#endif -// © Alfredo A. Correa 2019-2020 -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuSolver potrf" -#include - -#include "../../../adaptors/cuda.hpp" -#include "../../lapack/potrf.hpp" -#include "../../blas/herk.hpp" -#include "../../blas/trsm.hpp" - -#include "../../../adaptors/lapack/cuda.hpp" -#include "../../../adaptors/blas/cuda.hpp" - -#include -#include - -namespace multi = boost::multi; -namespace lapack = multi::lapack; -namespace blas = multi::blas; - -using complex = std::complex; - -std::ostream& operator<<(std::ostream& os, std::complex const& c){ - return os<< real(c) <<" + I*"<< imag(c); -} - -template decltype(auto) print(M const& C){ - using std::cout; - using multi::size; - cout<<'{'; - for(int i = 0; i != size(C); ++i){ - cout<<'{'; - for(int j = 0; j != size(C[i]); ++j){ - cout<< C[i][j]; - if(j + 1 != size(C[i])) cout<<", "; - } - cout<<'}'< -M&& randomize(M&& A){ - std::mt19937 eng{123}; - auto gen = [&](){return std::complex{std::uniform_real_distribution<>{-1, 1}(eng), std::uniform_real_distribution<>{-1, 1}(eng)};}; - std::for_each(begin(A), end(A), [&](auto&& r){std::generate(begin(r), end(r), gen);}); - return std::forward(A); -} - -/* -BOOST_AUTO_TEST_CASE(orthogonalization_over_rows, *boost::unit_test::tolerance(0.00001)){ - auto A = randomize(multi::array({3, 10})); - lapack::onrm(A); - - using blas::herk; - using blas::hermitized; - using blas::filling; - auto id = herk(filling::upper, A); - BOOST_TEST( real(id[1][1]) == 1. ); BOOST_TEST( imag(id[1][1]) == 0. ); - BOOST_TEST( real(id[1][2]) == 0. ); BOOST_TEST( imag(id[1][2]) == 0. ); -} -*/ - -BOOST_AUTO_TEST_CASE(orthogonalization_over_rows_cuda, *boost::unit_test::tolerance(0.00001)){ - auto Acpu = randomize(multi::array({3, 10})); - - multi::cuda::array A = Acpu; - - using namespace blas; - using namespace lapack; - - trsm(filling::lower, hermitized(potrf(filling::upper, herk(filling::upper, A))), A); - - Acpu = A; - auto id = herk(filling::upper, Acpu); - BOOST_TEST( real(id[1][1]) == 1. ); BOOST_TEST( imag(id[1][1]) == 0. ); - BOOST_TEST( real(id[1][2]) == 0. ); BOOST_TEST( imag(id[1][2]) == 0. ); -} - -/* -BOOST_AUTO_TEST_CASE(orthogonalization_over_columns, *boost::unit_test::tolerance(0.00001)){ - - auto A = randomize( multi::array({10, 3}) ); - using blas::hermitized; - lapack::onrm(hermitized(A)); - - using blas::filling; - auto id = herk(filling::upper, hermitized(A)); - BOOST_TEST( real(id[1][1]) == 1. ); BOOST_TEST( imag(id[1][1]) == 0. ); - BOOST_TEST( real(id[1][2]) == 0. ); BOOST_TEST( imag(id[1][2]) == 0. ); -}*/ - -BOOST_AUTO_TEST_CASE(lapack_potrf, *boost::unit_test::tolerance(0.00001) ){ - - complex const I{0, 1}; -{ - multi::array A = { - {167.413, 126.804 - 0.00143505*I, 125.114 - 0.1485590*I}, - {NAN , 167.381 , 126.746 + 0.0327519*I}, - {NAN , NAN , 167.231 } - }; - using lapack::filling; - using lapack::potrf; - potrf(filling::upper, A); // A is hermitic in upper triangular (implicit below) - BOOST_TEST( real(A[1][2]) == 3.78646 ); - BOOST_TEST( imag(A[1][2]) == 0.0170734 ); -// BOOST_TEST( A[2][1] != A[2][1] ); - print(A); -} -{ - multi::cuda::managed::array A = { - {167.413, 126.804 - 0.00143505*I, 125.114 - 0.1485590*I}, - {NAN , 167.381 , 126.746 + 0.0327519*I}, - {NAN , NAN , 167.231 } - }; - using lapack::filling; - using lapack::potrf; - potrf(filling::upper, A); // A is hermitic in upper triangular (implicit below) - BOOST_TEST( real(A[1][2]) == 3.78646 ); - BOOST_TEST( imag(A[1][2]) == 0.0170734 ); -// BOOST_TEST( A[2][1] != A[2][1] ); -} -{ - multi::cuda::array A = { - {167.413, 126.804 - 0.00143505*I, 125.114 - 0.1485590*I}, - {NAN , 167.381 , 126.746 + 0.0327519*I}, - {NAN , NAN , 167.231 } - }; - using lapack::filling; - using lapack::potrf; - potrf(filling::upper, A); // A is hermitic in upper triangular (implicit below) - multi::array A_copy = A; - print(A_copy); -} - -} - diff --git a/include/multi/adaptors/rangev3.hpp b/include/multi/adaptors/rangev3.hpp deleted file mode 100644 index 2aa35c47b..000000000 --- a/include/multi/adaptors/rangev3.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS -(echo '#include"'$0'"'>$0.cpp)&&c++ -D_TEST_MULTI_ADAPTORS_RANGEV3 -Wall -Wfatal-errors $0.cpp -o$0x &&$0x&&rm $0x $0.cpp;exit -#endif -// © Alfredo A. Correa 2019 - -// This header is to make some exotic cases of Multi iterators with Ranges v3 -#include "../array.hpp" - -#include - -#ifndef MULTI_ADAPTORS_RANGEV3_HPP -#define MULTI_ADAPTORS_RANGEV3_HPP - -namespace ranges{namespace v3{ -namespace concepts{ // needed for later version of rangesv3 -// this allows to recognize const_iterator as RandomAccessIterator -#if 0 - template - struct common_reference< - boost::multi::subarray&&, - MA& - >{ - using type = boost::multi::subarray&&; - }; - template - struct common_reference< - MA&, - boost::multi::subarray&& - >{ - using type = boost::multi::subarray&&; - }; -#endif -} -}} - -#ifdef _TEST_MULTI_ADAPTORS_RANGEV3 - -#include - -namespace multi = boost::multi; - -int main(){ - - using I = multi::array::const_iterator; - static_assert( ranges::RandomAccessIterator{}, "!"); - -} -#endif - -#endif - diff --git a/include/multi/adaptors/serialization/xml_archive.hpp b/include/multi/adaptors/serialization/xml_archive.hpp deleted file mode 100644 index 669aaeb0a..000000000 --- a/include/multi/adaptors/serialization/xml_archive.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXX $0 -o $0x -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2019-2021 -#include "../../utility.hpp" - -#include -#include - -namespace boost{ -namespace multi{ - -template<> -struct archive_traits{ - template static auto make_nvp(char const* name, T& value) -> decltype(auto){ - return boost::serialization::make_nvp(name, value); - } -}; -template<> -struct archive_traits{ - template static auto make_nvp(char const* name, T& value) -> decltype(auto){ - return boost::serialization::make_nvp(name, value); - } -}; - -} // end namespace multi -} // end namespace boost - -#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi adaptor serialization xml_archive" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../array.hpp" - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_adaptor_serialization_xml_archive){ - BOOST_REQUIRE(true); -} -#endif - diff --git a/include/multi/adaptors/thrust/CMakeLists.txt b/include/multi/adaptors/thrust/CMakeLists.txt deleted file mode 100644 index 7b63bd322..000000000 --- a/include/multi/adaptors/thrust/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -cmake_minimum_required(VERSION 3.11) -project( - boost-multi-adaptor-cuda - VERSION 0.1 - LANGUAGES CXX -) - -set(CMAKE_VERBOSE_MAKEFILE ON) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -find_package(Boost REQUIRED COMPONENTS unit_test_framework) - -if(ENABLE_CUDA OR DEFINED CXXCUDA) - enable_language(CUDA) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -std=c++17 --extended-lambda --expt-relaxed-constexpr -Xcudafe \"--display_error_number\"") - - enable_testing() - list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") # needs cmake 3.1 - - add_subdirectory(test) -endif() diff --git a/include/multi/adaptors/thrust/cuda/test/CMakeLists.txt b/include/multi/adaptors/thrust/cuda/test/CMakeLists.txt deleted file mode 100644 index 2e7025bfc..000000000 --- a/include/multi/adaptors/thrust/cuda/test/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -set(CMAKE_VERBOSE_MAKEFILE ON) - -find_package(Boost REQUIRED COMPONENTS unit_test_framework) - -include_directories(${Boost_INCLUDE_DIRS}) - -file( - GLOB TEST_SRCS - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - *.cu -) -# set(TEST_SRCS managed.cu ) - -foreach(TEST_FILE ${TEST_SRCS}) - get_filename_component(TEST_EXE ${TEST_FILE} NAME_WE) - add_executable(${TEST_EXE} ${TEST_FILE}) - if(ENABLE_CUDA OR DEFINED CXXCUDA) - set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) - target_compile_options(${TEST_EXE} PRIVATE -std=c++17 --expt-relaxed-constexpr) - endif() - # target_compile_features (${TEST_EXE} PUBLIC cxx_std_17) - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") - target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) - # target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) - target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) - target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) - # if(NOT ENABLE_CUDA) target_compile_options (${TEST_EXE} PRIVATE -Werror -Wall -Wextra -fno-common $<$: -Wpedantic -Wformat-truncation -fstack-usage>#-Wconversion - # $<$,$>: - # -Wpedantic -Wmove> $<$: -wd161 -diag-disable=remark -Warray-bounds -Wchar-subscripts -Wcomment -Wenum-compare -Wformat -Wuninitialized -Wmaybe-uninitialized -Wmain -Wnarrowing -Wnonnull -Wparentheses - # -Wpointer-sign -Wreorder -Wno-return-type -Wsign-compare -Wsequence-point -Wtrigraphs -Wunused-function -Wunused-but-set-variable -Wunused-variable -Wwrite-strings -Werror -diag-error:3846 > $<$: /W4>) endif() - add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) -endforeach() diff --git a/include/multi/adaptors/thrust/cuda/test/managed.cu b/include/multi/adaptors/thrust/cuda/test/managed.cu deleted file mode 100644 index 755bbb4cb..000000000 --- a/include/multi/adaptors/thrust/cuda/test/managed.cu +++ /dev/null @@ -1,60 +0,0 @@ -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA thrust" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../cuda/managed.hpp" - -namespace multi = boost::multi; - -void set_one(double* p){ - *p = 1.; -} - -void set_two_gpu(thrust::cuda::pointer p){ - *p = 2.; -} - -void set_three_ref(double& p){ - p = 3.; -} - -template::value_type, class = std::enable_if_t{} and std::is_convertible>{}> > -void some_fun(Pointer p){} - -template::value_type, class = std::enable_if_t{} and std::is_convertible{}> > -void some_other_fun(Pointer p){} - -template class prio : std::conditional_t, std::false_type>{}; - -template::value_type, std::enable_if_t{} and std::is_convertible>{}, int> =0> -int overload_aux(Pointer p, prio<0>){return 0;} - -template::value_type, std::enable_if_t{} and std::is_convertible{}, int> =0> -int overload_aux(Pointer p, prio<1>){return 1;} - -template int overload(Pointer p){return overload_aux(p, prio<1>{});} - -BOOST_AUTO_TEST_CASE(vector){ - - multi::thrust::cuda::managed::allocator alloc; - multi::thrust::cuda::managed::pointer p = alloc.allocate(100); - - p[17] = 3.; - BOOST_TEST_REQUIRE( p[17] == 3. ); - - set_one(p); - BOOST_TEST_REQUIRE( p[0] == 1. ); - - set_two_gpu(p); - BOOST_TEST_REQUIRE( p[0] == 2. ); - - set_three_ref( p[1] ); - BOOST_TEST_REQUIRE( p[1] == 3. ); - - some_fun(p); - - BOOST_TEST_REQUIRE(overload(p) == 1); - - alloc.deallocate(p, 100); - -} diff --git a/include/multi/adaptors/thrust/fix_pointer_traits.hpp b/include/multi/adaptors/thrust/fix_pointer_traits.hpp deleted file mode 100644 index 6387bf704..000000000 --- a/include/multi/adaptors/thrust/fix_pointer_traits.hpp +++ /dev/null @@ -1,31 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021-2023 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_THRUST_FIX_POINTER_TRAITS_HPP_ -#define MULTI_ADAPTORS_THRUST_FIX_POINTER_TRAITS_HPP_ -#pragma once - -#include - -// #include - -// begin of nvcc trhust 11.5 workaround : https://github.com/NVIDIA/thrust/issues/1629 -namespace thrust { - -template class pointer; -template struct pointer_traits; - -} // end namespace thrust - -namespace std { - -template struct pointer_traits> -: thrust::detail::pointer_traits> { - template - using rebind = typename thrust::detail::pointer_traits>::template rebind::other; -}; - -} // end namespace std -// end of nvcc thrust 11.5 workaround - -#endif diff --git a/include/multi/adaptors/thrust/test/CMakeLists.txt b/include/multi/adaptors/thrust/test/CMakeLists.txt deleted file mode 100644 index 8261489a9..000000000 --- a/include/multi/adaptors/thrust/test/CMakeLists.txt +++ /dev/null @@ -1,86 +0,0 @@ -# -*-indent-tabs-mode:nil;c-basic-offset:2;tab-width:4;autowrap:nil;-*- -cmake_minimum_required(VERSION 3.11) -project( - boost-multi-adaptor-cuda-thrust-test - VERSION 0.1 - LANGUAGES CXX CUDA -) - -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr --extended-lambda --Werror=cross-execution-space-call -Xcudafe \"--display_error_number\"") - -find_package(Boost REQUIRED COMPONENTS unit_test_framework) - -enable_testing() - -find_program(MEMORYCHECK_COMMAND valgrind) -set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --error-exitcode=1") -include(CTest) - -# TODO(correaa) this will be simpler in Cmake 3.24 https://stackoverflow.com/a/72332793/225186 -# if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) -# # temporary workaround to detect CUDA arch, for between the deprecation of FindCUDA in CMake 3.10 and the availability of FindCudaToolkit in CMake 3.17 # https://stackoverflow.com/a/68223399/225186 -# include(FindCUDA/select_compute_arch) -# cuda_detect_installed_gpus(INSTALLED_GPU_CCS_1) -# string(STRIP "${INSTALLED_GPU_CCS_1}" INSTALLED_GPU_CCS_2) -# string( -# REPLACE " " -# ";" -# INSTALLED_GPU_CCS_3 -# "${INSTALLED_GPU_CCS_2}" -# ) -# string( -# REPLACE "." -# "" -# CUDA_ARCH_LIST -# "${INSTALLED_GPU_CCS_3}" -# ) -# set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCH_LIST}) -# set_property(GLOBAL PROPERTY CUDA_ARCHITECTURES "${CUDA_ARCH_LIST}") -# endif() - -# if("30;35;50;52;60;61;70;70+PTX;75;75+PTX" STREQUAL "${CUDA_ARCH_LIST}" OR "" STREQUAL "${CUDA_ARCH_LIST}") -# message(STATUS "gpu not detected ${CUDA_ARCH_LIST}") -# set(TEST_SRCS) -# else() -# message(STATUS "gpu detected: ${CUDA_ARCH_LIST}") -# endif() - -find_package(Boost REQUIRED COMPONENTS unit_test_framework timer) - -set(TEST_SRCS - array.cu - memory_resource.cu - set_identity_kernel.cu - speed.cu - speed_algo.cu - universal.cu - vector.cu -) - -foreach(TEST_FILE ${TEST_SRCS}) - set(TEST_EXE "${TEST_FILE}.x") - add_executable(${TEST_EXE} ${TEST_FILE}) - if(ENABLE_CUDA OR DEFINED CXXCUDA) - set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) - endif() - - target_include_directories(${TEST_EXE} PUBLIC ../../../../../include) - - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_PP_VARIADICS") - target_compile_definitions(${TEST_EXE} PRIVATE ${Boost_DEFINITIONS}) - target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) - target_link_libraries(${TEST_EXE} PRIVATE ${Boost_LIBRARIES}) - target_link_directories(${TEST_EXE} PRIVATE ${Boost_LIBRARY_DIRS}) - # if("30;35;50;52;60;61;70;70+PTX;75;75+PTX" STREQUAL "${CUDA_ARCH_LIST}" OR "" STREQUAL "${CUDA_ARCH_LIST}" OR NOT ENABLE_CUDA) - # message(STATUS "gpu not detected ${CUDA_ARCH_LIST}") - # else() - # message(STATUS "gpu detected: ${CUDA_ARCH_LIST}") - # add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) - # endif() - if(ENABLE_CUDA) - # message(STATUS "gpu run forced") - add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) - endif() -endforeach() - -set_tests_properties(speed.cu.x PROPERTIES RUN_SERIAL TRUE) diff --git a/include/multi/adaptors/thrust/test/array.cu b/include/multi/adaptors/thrust/test/array.cu deleted file mode 100644 index 04ea4a898..000000000 --- a/include/multi/adaptors/thrust/test/array.cu +++ /dev/null @@ -1,740 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021-2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA thrust" -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include - -namespace multi = boost::multi; - -#ifdef __NVCC__ -template<> -inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::std::complex> = true; -template<> -inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::std::complex> = true; -template<> -inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::thrust::complex> = true; -template<> -inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::thrust::complex> = true; -#else // vvv nvcc (12.1?) doesn't support this kind of customization: "error: expected initializer before ‘<’" -template -inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::std::complex> = std::is_trivially_default_constructible::value; -template -inline constexpr bool ::boost::multi::force_element_trivial_default_construction<::thrust::complex> = std::is_trivially_default_constructible::value; -#endif - -namespace { - -template using test_allocator = -// multi ::memory::cuda::allocator -// multi ::memory::cuda::managed::allocator//, std::integral_constant > -// multi ::memory::cuda::cached::allocator > -// multi::thrust::cuda::managed_allocator - thrust::cuda::allocator -; - -} - -using types_list = boost::mpl::list< - // char, - double, - // std::complex, - thrust::complex ->; - -BOOST_AUTO_TEST_CASE(dummy_test) {} - -BOOST_AUTO_TEST_CASE(test_univ_alloc) { - multi::array > Dev({128, 128}); - *raw_pointer_cast(Dev.base()) = 99.0; -} - -BOOST_AUTO_TEST_CASE(mtc_universal_array) { - multi::thrust::cuda::universal_array Dev({128, 128}); - *raw_pointer_cast(Dev.base()) = 99.0; -} - -BOOST_AUTO_TEST_CASE(mtc_universal_coloncolon_array) { - multi::thrust::cuda::universal::array Dev({128, 128}); - *raw_pointer_cast(Dev.base()) = 99.0; -} - -BOOST_AUTO_TEST_CASE(test_alloc) { - multi::array > Dev({128, 128}); - // *raw_pointer_cast(Dev.base()) = 99.0; // segmentation fault (correct behavior) -} - -#ifdef NDEBUG -BOOST_AUTO_TEST_CASE_TEMPLATE(thrust_copy_1D_issue123, T, types_list) { // BOOST_AUTO_TEST_CASE(fdfdfdsfds) { using T = char; - static_assert( multi::is_trivially_default_constructible{}, "!"); - static_assert( std::is_trivially_copy_constructible{} , "!"); - static_assert( std::is_trivially_assignable{} , "!"); - - - multi::array> Devc(multi::extensions_t<1>{10240*10240}); - multi::array> Dev2(multi::extensions_t<1>{10240*10240}); - multi::array Host(multi::extensions_t<1>{10240*10240}); std::iota(Host.elements().begin(), Host.elements().end(), 12.); - multi::array Hos2(multi::extensions_t<1>{10240*10240}); - - std::cout<<"| 1D `"<< typeid(T).name() <<"` total data size: "<< Host.num_elements()*sizeof(T) / 1073741824. <<" GB | speed |\n|---|---|"< devc | "<< Host.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Host.sliced(0, 10240*10240/2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Host.strided(2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< host | "<< Host.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< host | "<< Host.sliced(0, 10240*10240/2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< host | "<< Host.strided(2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Dev2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Devc.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Dev2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Dev2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Dev2.sliced(0, 10240*10240/2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Dev2.strided(2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< host | "<< Hos2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< host | "<< Hos2.sliced(0, 10240*10240/2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< host | "<< Hos2.strided(2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"<({10240, 10240}); - - std::cout<<"| 2D `"<< typeid(T).name() <<"` max data size "<< exts.num_elements()*sizeof(T) / 1073741824. <<" GB | speed |\n|---|---|"<> Devc(exts); - multi::array> Dev2(exts); - - multi::array Host(exts); std::iota(Host.elements().begin(), Host.elements().end(), 12.); - multi::array Hos2(exts); - - { - Devc({0, 5120},{0, 5120}) = Host({0, 5120},{0, 5120}); // 0.002859s - } - { - boost::timer::auto_cpu_timer t{""}; - Devc = Host; - std::cout<<"| contiguous host to devc | "<< Host.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Devc.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"<({1024, 1024, 100}); - - std::cout<<"| 3D `"<< typeid(T).name() <<"` max data size "<< exts.num_elements()*sizeof(T) / 1073741824. <<" GB | speed |\n|---|---|"<> Devc(exts); - multi::array> Dev2(exts); - multi::array Host(exts); std::iota(Host.elements().begin(), Host.elements().end(), 12.); - multi::array Hos2(exts); - - { - Devc({0, 512}, {0, 512}, {0, 512}) = Host({0, 512}, {0, 512}, {0, 512}); // 0.002859s - } - { - boost::timer::auto_cpu_timer t{""}; - Devc = Host; - std::cout<<"| contiguous host to devc | "<< Host.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << " GB/sec |"< devc | "<< Devc.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Dev2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"< devc | "<< Dev2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |"<> Devc(multi::extensions_t<1>{10240*10240}); - multi::array> Dev2(multi::extensions_t<1>{10240*10240}); - multi::array Host(multi::extensions_t<1>{10240*10240}); - std::iota(Host.elements().begin(), Host.elements().end(), 12.); - multi::array Hos2(multi::extensions_t<1>{10240*10240}); - - std::cout<<"| 1D `"<< typeid(T).name() <<"` total data size: "<< Host.num_elements()*sizeof(T) / 1073741824. <<" GB | speed |\n|---|---|\n"; - - Devc = Host; - Dev2 = Host; - Hos2 = Host; - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE( Devc == Dev2 ); - std::cout<<"| contiguous devc == devc | "<< Devc.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - // BOOST_REQUIRE( Devc.sliced(0, Devc.size()/2) == Dev2.sliced(0, Devc.size()/2) ); - BOOST_REQUIRE( thrust::equal( Devc.sliced(0, Devc.size()/2).elements().begin(), Devc.sliced(0, Devc.size()/2).elements().end(), Dev2.sliced(0, Devc.size()/2).elements().begin() ) ); - std::cout<<"| sliced devc == devc | "<< Devc.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - // BOOST_REQUIRE(Host == Hos2); - BOOST_REQUIRE( std::equal( Host.elements().begin(), Host.elements().end(), Hos2.elements().begin() ) ); - std::cout<<"| contiguous host == host | "<< Hos2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - // BOOST_REQUIRE(Host.sliced(0, Devc.size()/2) == Hos2.sliced(0, Devc.size()/2) ); - BOOST_REQUIRE( std::equal( Host.sliced(0, Host.size()/2).elements().begin(), Host.sliced(0, Host.size()/2).elements().end(), Hos2.sliced(0, Devc.size()/2).elements().begin() ) ); - std::cout<<"| sliced host == host | "<< Hos2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - - std::cout<<" "< Host = {{1, 2, 3}, {4, 5, 6}}; - multi::array Hos2 = {{1, 2, 3}, {4, 5, 6}}; - BOOST_REQUIRE( Host.size() == 2 ); - - BOOST_REQUIRE( *Host().elements().begin() == *Hos2().elements().begin() ); - - BOOST_REQUIRE( Host().elements().begin()[0] == Hos2().elements().begin()[0] ); - BOOST_REQUIRE( Host().elements().begin()[1] == Hos2().elements().begin()[1] ); - BOOST_REQUIRE( Host().elements().begin()[2] == Hos2().elements().begin()[2] ); - BOOST_REQUIRE( Host().elements().begin()[3] == Hos2().elements().begin()[3] ); - BOOST_REQUIRE( Host().elements().begin()[4] == Hos2().elements().begin()[4] ); - BOOST_REQUIRE( Host().elements().begin()[5] == Hos2().elements().begin()[5] ); - - BOOST_REQUIRE( *(Host().elements().end() - 1) == *(Hos2().elements().end() - 1) ); - BOOST_REQUIRE( *(Host().elements().end() - 2) == *(Hos2().elements().end() - 2) ); - BOOST_REQUIRE( *(Host().elements().end() - 3) == *(Hos2().elements().end() - 3) ); - - BOOST_REQUIRE( std::equal(Host().elements().begin(), Host().elements().end(), Hos2().elements().begin()) ); - BOOST_REQUIRE( thrust::equal(Host().elements().begin(), Host().elements().end(), Hos2().elements().begin()) ); - -// BOOST_REQUIRE( Host() == Hos2() ); -} - -BOOST_AUTO_TEST_CASE(thrust_equality_2D_small_gpu_issue123) { - multi::array Host = {{1, 2, 3}, {4, 5, 6}}; - - multi::array> Devc(Host.extensions()); Devc = Host; - multi::array> Dev2(Host.extensions()); Dev2 = Host; - BOOST_REQUIRE( Dev2.size() == 2 ); - - BOOST_REQUIRE( thrust::equal( - Devc().elements().begin(), - Devc().elements().end() , Dev2().elements().begin() - )); - - BOOST_REQUIRE( thrust::equal( - thrust::cuda::par, - Devc().elements().begin(), - Devc().elements().end() , Dev2().elements().begin() - )); - - BOOST_REQUIRE( thrust::equal( - Devc.rotated().elements().begin(), - Devc.rotated().elements().end() , Dev2.rotated().elements().begin() - )); - - BOOST_REQUIRE( thrust::equal( - thrust::cuda::par, - Devc.rotated().elements().begin(), - Devc.rotated().elements().end() , Dev2.rotated().elements().begin() - )); - - BOOST_REQUIRE( multi::adl_equal( - Devc.rotated().elements().begin(), - Devc.rotated().elements().end() , Dev2.rotated().elements().begin() - )); -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(thrust_equality_2D_issue123, T, types_list) { - multi::extensions_t<2> x({10240, 10240}); - multi::array> Devc(x); - multi::array> Dev2(x); - multi::array Host(x); std::iota(Host.elements().begin(), Host.elements().end(), 12.); - multi::array Hos2(x); - - std::cout<<"| 2D `"<< typeid(T).name() <<"` max data size "<< Host.num_elements()*sizeof(T) / 1073741824. <<" GB | speed |\n|---|---|\n"; - - Devc = Host; - Dev2 = Host; - Hos2 = Host; - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE(Host == Hos2); - std::cout<<"| contiguous host == host | "<< Hos2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE(Host.sliced(0, Host.size()/2) == Hos2.sliced(0, Host.size()/2)); - // BOOST_REQUIRE( std::equal(Host.sliced(0, 5120).elements().begin(), Host.sliced(0, 5120).elements().end(), Hos2.sliced(0, 5120).elements().begin()) ); - std::cout<<"| sliced host == host | "<< Hos2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE(Host({0, Host.size()/2},{0, Host.size()/2}) == Hos2({0, Hos2.size()/2},{0, Hos2.size()/2})); - // BOOST_REQUIRE( std::equal(Host({0, 5120},{0, 5120}).elements().begin(), Host({0, 5120},{0, 5120}).elements().end(), Hos2({0, 5120},{0, 5120}).elements().begin()) ); - std::cout<<"| strided host == host | "<< Hos2({0, Host.size()/2},{0, Host.size()/2}).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE(Devc == Dev2); - std::cout<<"| contiguous devc == devc | "<< Devc.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE(Devc.sliced(0, Devc.size()/2) == Dev2.sliced(0, Dev2.size()/2)); - std::cout<<"| sliced devc == devc | "<< Devc.sliced(0, Devc.size()/2).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE(Devc({0, Devc.size()/2},{0, Devc.size()/2}) == Dev2({0, Dev2.size()/2},{0, Dev2.size()/2})); - std::cout<<"| strided devc == devc | "<< Devc({0, Devc.size()/2},{0, Devc.size()/2}).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - std::cout<<" "<> Devc({1024, 1024, 100}); - multi::array> Dev2({1024, 1024, 100}); - multi::array Host({1024, 1024, 100}); std::iota(Host.elements().begin(), Host.elements().end(), 12.); - multi::array Hos2({1024, 1024, 100}); - - std::cout<<"| 3D `"<< typeid(T).name() <<"` max data size "<< Host.num_elements()*sizeof(T) / 1073741824. <<" GB | speed |\n|---|---|\n"; - - Devc = Host; - Dev2 = Host; - Hos2 = Host; - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE( Devc == Dev2 ); - std::cout<<"| contiguous devc == devc | "<< Devc.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << " GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE( Devc.sliced(0, 512) == Dev2.sliced(0, 512) ); - std::cout<<"| sliced devc == devc | "<< Dev2.sliced(0, 512).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << " GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE( Devc({0, 512}, {0, 512}, {0, 512}) == Dev2({0, 512}, {0, 512}, {0, 512}) ); - std::cout<<"| strided devc == devc | "<< Dev2({0, 512},{0, 512}, {0, 512}).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - BOOST_REQUIRE( Host == Hos2 ); - std::cout<<"| contiguous host == host | "<< Hos2.num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << " GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - // BOOST_REQUIRE( Host.sliced(0, 512) == Hos2.sliced(0, 512) ); - BOOST_REQUIRE( std::equal( Host.sliced(0, 512).elements().begin(), Host.sliced(0, 512).elements().end(), Hos2.sliced(0, 512).elements().begin() ) ); - std::cout<<"| sliced host == host | "<< Hos2.sliced(0, 512).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << " GB/sec |\n"; - } - { - boost::timer::auto_cpu_timer t{""}; - // BOOST_REQUIRE( Host({0, 512}, {0, 512}, {0, 512}) == Hos2({0, 512}, {0, 512}, {0, 512}) ); - BOOST_REQUIRE( std::equal( Host({0, 512}, {0, 512}, {0, 512}).elements().begin(), Host({0, 512}, {0, 512}, {0, 512}).elements().end(), Hos2({0, 512}, {0, 512}, {0, 512}).elements().begin() ) ); - std::cout<<"| strided host == host | "<< Hos2({0, 512},{0, 512}, {0, 512}).num_elements()*sizeof(T) / (t.elapsed().wall/1e9) / 1073741824. << "GB/sec |\n"; - } - std::cout<<" "<; -} - -BOOST_AUTO_TEST_CASE(thrust_complex_cached_1D) { - using T = inq::complex; - multi::array > aa(10, T{1., 1.}); - multi::array > bb(10, T{2., 2.}); - - bb = aa; - - BOOST_REQUIRE(( bb[0] == T{1., 1.} )); -} - -BOOST_AUTO_TEST_CASE(thrust_complex_cached_without_values_1D) { - using T = inq::complex; - multi::array > aa(10); - multi::array > bb(10); - BOOST_REQUIRE( aa.size() == 10 ); - BOOST_REQUIRE( bb.size() == 10 ); - - bb = aa; - - BOOST_REQUIRE(( bb[0] == aa[0] )); -} - -BOOST_AUTO_TEST_CASE(thrust_complex_cached_2D) { - using T = inq::complex; - multi::array > aa({10, 20}, T{1., 1.}); - multi::array > bb({10, 20}, T{2., 2.}); - - bb = aa; - - BOOST_REQUIRE(( bb[0][0] == T{1., 1.} )); -} - -BOOST_AUTO_TEST_CASE(thrust_complex_cached_without_values_2D) { - using T = inq::complex; - multi::array > aa({10, 20}); - multi::array > bb({10, 20}); - BOOST_REQUIRE( aa.size() == 10 ); - BOOST_REQUIRE( bb.size() == 10 ); - - bb = aa; - - BOOST_REQUIRE(( bb[0][0] == aa[0][0] )); -} - -BOOST_AUTO_TEST_CASE(array) { - -//{ -// multi::thrust::cuda::array C({2, 3}); - -// C[0][0] = 0. ; -// C[1][1] = 11.; -// BOOST_TEST_REQUIRE( C[1][1] == 11. ); -//} - -//{ -// multi::array const H = { -// {00., 01., 02.}, -// {10., 11., 12.}, -// }; - -// BOOST_TEST_REQUIRE( H[1][1] == 11. ); - -// { -// multi::thrust::cuda::array C(H.extensions()); -// BOOST_REQUIRE( C.num_elements() == H.num_elements() ); - -// thrust::copy_n(H.data_elements(), H.num_elements(), C.data_elements()); -// BOOST_TEST_REQUIRE( C[1][1] == 11. ); -// BOOST_REQUIRE( C == H ); -// } -// { -// multi::thrust::cuda::array C(H.extensions()); -// BOOST_REQUIRE( C.num_elements() == H.num_elements() ); - -// std::copy_n(H.data_elements(), H.num_elements(), C.data_elements()); -// BOOST_TEST_REQUIRE( C[1][1] == 11. ); -// BOOST_REQUIRE( C == H ); -// } -// { -// multi::thrust::cuda::array C(H.extensions()); -// BOOST_REQUIRE( C.num_elements() == H.num_elements() ); - -// std::uninitialized_copy_n(H.data_elements(), H.num_elements(), C.data_elements()); -// BOOST_TEST_REQUIRE( C[1][1] == 11. ); -// BOOST_REQUIRE( C == H ); -// } -// { -// multi::thrust::cuda::array C(H.extensions()); -// BOOST_REQUIRE( C.num_elements() == H.num_elements() ); - -// thrust::uninitialized_copy_n(H.data_elements(), H.num_elements(), C.data_elements()); -// BOOST_TEST_REQUIRE( C[1][1] == 11. ); -// BOOST_REQUIRE( C == H ); -// } -// { -// multi::thrust::cuda::array C(H.extensions()); -// BOOST_REQUIRE( C.extensions() == H.extensions() ); -// thrust::copy_n(H.begin(), H.size(), C.begin()); -// BOOST_REQUIRE( C == H ); -// } -// { -// multi::thrust::cuda::array C(H.extensions()); -// BOOST_REQUIRE( C.extensions() == H.extensions() ); -// std::copy_n(H.begin(), H.size(), C.begin()); -// BOOST_REQUIRE( C == H ); -// } -// { -// multi::thrust::cuda::array C(H.extensions()); -// C = H; -// BOOST_REQUIRE( C == H ); -// } -// { -// multi::thrust::cuda::array C = H; -// BOOST_REQUIRE( C == H ); -// } -//} - -} -#endif diff --git a/include/multi/adaptors/thrust/test/speed.cu b/include/multi/adaptors/thrust/test/speed.cu deleted file mode 100644 index 102ce5498..000000000 --- a/include/multi/adaptors/thrust/test/speed.cu +++ /dev/null @@ -1,170 +0,0 @@ -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA thrust universal copy and assignment" -#include - -#include - -#include - -#include - -#include - -namespace multi = boost::multi; - -// template<> -// inline constexpr bool multi::force_element_trivial_default_construction> = false; - -// template<> -// inline constexpr bool multi::force_element_trivial_default_construction> = false; - -// template<> -// inline constexpr bool multi::force_element_trivial_default_construction> = false; - -// template<> -// inline constexpr bool multi::force_element_trivial_default_construction> = false; - -using test_types = boost::mpl::list< - char, unsigned, int, - ::thrust::complex, std::complex, - ::thrust::complex, std::complex, - double, float>; - -BOOST_AUTO_TEST_CASE(warmup) { - using T = double; - - auto const n = 8000; - - multi::array> src({n, n}); - multi::array> dst(extensions(src)); - - auto const threshold = 0.30; - - auto const size = src.num_elements() * sizeof(T) / 1e9; - - auto const dummy = std::invoke([&] { - auto start_time = std::chrono::high_resolution_clock::now(); - cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; - auto rate = size / time.count(); - // std::cout<<"memcpy rate = "<< rate <<" GB/s (ratio = 1)\n"; - return rate; - }); - - auto const memcpy_rate = std::invoke([&] { - auto start_time = std::chrono::high_resolution_clock::now(); - cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; - auto rate = size / time.count(); - // std::cout<<"memcpy rate = "<< rate <<" GB/s (ratio = 1)\n"; - return rate; - }); - - { // cctor - auto tick = std::chrono::high_resolution_clock::now(); - - auto dst2 = src; - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - - // std::cout<<"cctor rate = "<< rate <<" GB/s (ratio = "<< ratio <<")\n"; - if(ratio >= threshold) { - std::cout << "x"; - } - } - { // assign - auto tick = std::chrono::high_resolution_clock::now(); - - dst = src; - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - - // std::cout << "assign rate = "<< rate <<" GB/s (ratio = "<< ratio <<")\n"; - if(ratio >= threshold) { - std::cout << "x"; - } - } - { // subarray assign - auto tick = std::chrono::high_resolution_clock::now(); - - dst({0, n - 2}, {0, n - 2}) = src({2, n}, {2, n}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - // std::cout << "subasssign rate = "<< rate <<" GB/s (ratio = "<< ratio << ")\n"; - if(ratio >= threshold) { - std::cout << "x"; - } - } -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(thrust_universal_speed, T, test_types) { - std::cout << typeid(T).name() << " ******************************************\n"; - - auto const n = 8000; - - multi::array> src({n, n}); - multi::array> dst(extensions(src)); - - auto const threshold = 0.15; - - auto const size = src.num_elements() * sizeof(T) / 1e9; - - auto const dummy = std::invoke([&] { - auto start_time = std::chrono::high_resolution_clock::now(); - cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; - auto rate = size / time.count(); - std::cout << "memcpy rate = " << rate << " GB/s (warmup)\n"; - return rate; - }); - - auto const memcpy_rate = std::invoke([&] { - auto start_time = std::chrono::high_resolution_clock::now(); - cudaMemcpy(raw_pointer_cast(dst.data_elements()), raw_pointer_cast(src.data_elements()), src.num_elements() * sizeof(T), cudaMemcpyDeviceToDevice); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - start_time; - auto rate = size / time.count(); - std::cout << "memcpy rate = " << rate << " GB/s (ratio = 1)\n"; - return rate; - }); - - { // cctor - auto tick = std::chrono::high_resolution_clock::now(); - - auto dst2 = src; - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - - std::cout << "cctor rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; - BOOST_TEST(ratio >= threshold); - } - { // assign - auto tick = std::chrono::high_resolution_clock::now(); - - dst = src; - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - - std::cout << "assign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; - BOOST_TEST(ratio >= threshold); - } - { // subarray assign - auto tick = std::chrono::high_resolution_clock::now(); - - dst({0, n - 2}, {0, n - 2}) = src({2, n}, {2, n}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size / time.count(); - double ratio = rate / memcpy_rate; - std::cout << "subasssign rate = " << rate << " GB/s (ratio = " << ratio << ")\n"; - BOOST_TEST(ratio >= threshold); - } -} diff --git a/include/multi/adaptors/thrust/test/speed_algo.cu b/include/multi/adaptors/thrust/test/speed_algo.cu deleted file mode 100644 index d507ce035..000000000 --- a/include/multi/adaptors/thrust/test/speed_algo.cu +++ /dev/null @@ -1,106 +0,0 @@ -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA thrust universal copy and assignment" -#include - -#include -//#include - -#include - -//#include - - -#include - -namespace multi = boost::multi; -using complex = thrust::complex; - -template -void doNotOptimize(T const& val) { - asm volatile("" : : "g"(val) : "memory"); -} - -BOOST_AUTO_TEST_CASE(thrust_universal_speed_algo) { - - auto const n = 8000; - { //cctor - auto tick = std::chrono::high_resolution_clock::now(); - multi::array> A({n, n}); - cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements()*sizeof(complex), 0); - - auto size = A.num_elements()*sizeof(complex)/1e9; - std::fill_n(raw_pointer_cast(A.data_elements()), A.num_elements(), complex{1.0}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - double rate = size/time.count(); - - } - { //cctor - auto tick = std::chrono::high_resolution_clock::now(); - multi::array> A({n, n}); - - std::fill_n(raw_pointer_cast(A.data_elements()), A.num_elements(), complex{1.0}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - - auto size = A.num_elements()*sizeof(complex)/1e9; - double rate = size/time.count(); - - std::cout<<"no prefetch+cpu_algo rate = "<< rate <<" GB/s\n"; - } - { //cctor - auto tick = std::chrono::high_resolution_clock::now(); - multi::array> A({n, n}); - cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements()*sizeof(complex), 0); - - thrust::fill_n(raw_pointer_cast(A.data_elements()), A.num_elements(), complex{1.0}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - - auto size = A.num_elements()*sizeof(complex)/1e9; - double rate = size/time.count(); - - std::cout<<"dev prefetch+cpu_algo rate = "<< rate <<" GB/s\n"; - } - { - auto tick = std::chrono::high_resolution_clock::now(); - multi::array> A({n, n}); - - thrust::fill_n(A.data_elements(), A.num_elements(), complex{1.0}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - - auto size = A.num_elements()*sizeof(complex)/1e9; - double rate = size/time.count(); - - std::cout<<"no prefetch+gpu_algo rate = "<< rate <<" GB/s\n"; - } - { - auto tick = std::chrono::high_resolution_clock::now(); - multi::array> A({n, n}); - cudaMemPrefetchAsync(raw_pointer_cast(A.data_elements()), A.num_elements()*sizeof(complex), 0); - - thrust::fill_n(A.data_elements(), A.num_elements(), complex{1.0}); - - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - - auto size = A.num_elements()*sizeof(complex)/1e9; - double rate = size/time.count(); - - std::cout<<"dev prefetch+gpu_algo rate = "<< rate <<" GB/s\n"; - } - { - auto tick = std::chrono::high_resolution_clock::now(); - multi::array> A({n, n}, complex{0.0}); - doNotOptimize(A); - std::chrono::duration time = std::chrono::high_resolution_clock::now() - tick; - - auto size = A.num_elements()*sizeof(complex)/1e9; - double rate = size/time.count(); - - std::cout<<"fill constructor rate = "<< rate <<" GB/s\n"; - } -} - -BOOST_AUTO_TEST_CASE(thrust_run) { - multi::array> A(100); -} \ No newline at end of file diff --git a/include/multi/adaptors/thrust/test/universal.cu b/include/multi/adaptors/thrust/test/universal.cu deleted file mode 100644 index b1e54334b..000000000 --- a/include/multi/adaptors/thrust/test/universal.cu +++ /dev/null @@ -1,80 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA thrust universal" -#include - -#include - -#include - -#include - -#include - -#include -#include - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(thrust_universal_ptr) { - multi::array Host({1024, 1024}); - std::iota(Host.data_elements(), Host.data_elements() + Host.num_elements(), 12.0); - - multi::array> Univ({1024, 1024}); - - Univ({0, 10}, {0, 20}) = Host({0, 10}, {0, 20}); - - multi::array Hos2({1024, 1024}); - Hos2({0, 10}, {0, 20}) = Univ({0, 10}, {0, 20}); - - BOOST_REQUIRE( Hos2[0][0] == 12.0 ); -} - -BOOST_AUTO_TEST_CASE(thrust_universal_ptr_initializer_list) { - multi::array Host = {1.0, 2.0, 3.0}; - BOOST_REQUIRE( Host.size() == 3 ); - { - multi::array> Univ(multi::extensions_t<1>{3}); - Univ[0] = 3.0; - Univ[1] = 2.0; - Univ[2] = 1.0; - - Host() = Univ(); - - BOOST_REQUIRE( Host[0] == 3.0 ); - } - { - multi::array tmp = {3.0, 2.0, 1.0,}; - multi::array> Univ{multi::extensions_t<1>{3}}; - Univ = tmp; - - Host() = Univ(); - - BOOST_REQUIRE( Host[0] == 3.0 ); - } - { - multi::array tmp = {3.0, 2.0, 1.0,}; - multi::array> Univ{tmp}; - - Host() = Univ(); - - BOOST_REQUIRE( Host[0] == 3.0 ); - } - { - multi::array> Univ = {3.0, 2.0, 1.0,}; - - Host() = Univ(); - - BOOST_REQUIRE( Host[0] == 3.0 ); - } -// what( thrust::cuda::universal_allocator{} ); -// { -// multi::array> Univ = {3., 2., 1.}; - -// Host() = Univ(); - -// BOOST_REQUIRE( Host[0] == 3. ); -// } -} - diff --git a/include/multi/adaptors/totalview.hpp b/include/multi/adaptors/totalview.hpp deleted file mode 100644 index 11f3b6350..000000000 --- a/include/multi/adaptors/totalview.hpp +++ /dev/null @@ -1,150 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXXX $CXXFLAGS $0 -o $0x -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020-2021 - -#ifndef MULTI_ADAPTORS_TOTALVIEW_HPP -#define MULTI_ADAPTORS_TOTALVIEW_HPP - -#include // TODO remove -#include - -#include -#include -//#include - -#include "../adaptors/../array.hpp" - -#include "tv_data_display.h" // you have to find the directory with the totalview include files -#include "../src/tv_data_display.c" // you have to find the directory with the totalview include files -// ^^^^^^^^^^^ this can produce problemas later with linking -// https://docs.roguewave.com/totalview/2018.1/html/index.html#page/Reference_Guide%2FCompilingAndLinkingTV_data_display.html%23ww1738654 - -template constexpr char const* pretty_name = "unknown"; - -template<> constexpr char const* pretty_name = "double"; -template<> constexpr char const* pretty_name = "float"; - -template<> constexpr char const* pretty_name> = "std::complex"; -template<> constexpr char const* pretty_name > = "std::complex"; - -template<> constexpr char const* pretty_name = "long"; -template<> constexpr char const* pretty_name = "int"; - -template -#ifdef __GCC__ -__attribute__((used)) -#endif -int TV_ttf_display_type(boost::multi::array const* mad1P) { - if(not mad1P->is_empty()) { - std::array tname; // char tname[128]; - snprintf(tname.data(), tname.size(), "%s[%ld]", pretty_name, (long)mad1P->size());//, (long)mad1P->stride()); - int result = TV_ttf_add_row("elements", tname.data(), mad1P->origin()); - if (result != 0){ - fprintf(stderr, "TV_ttf_add_row returned error %d\n", result); - return TV_ttf_format_failed; - } - } - return TV_ttf_format_ok_elide; -} - -template -#ifdef __GCC__ -__attribute__((used)) -#endif -int TV_ttf_display_type(boost::multi::array const* mad2P) { - if(not mad2P->is_empty()) { - std::arra tname; // char tname[128]; - using std::get; - snprintf(tname.data(), tname.size(), "%s[%ld][%ld]", pretty_name, (long)get<0>(mad2P->sizes()), (long)get<1>(mad2P->sizes()));//, (long)mad1P->stride()); - int result = TV_ttf_add_row("elements", tname.data(), mad2P->origin()); - - if(result != 0) { - fprintf(stderr, "TV_ttf_add_row returned error %d\n", result); - return TV_ttf_format_failed; - } - } - return TV_ttf_format_ok_elide; -} - -template -#ifdef __GCC__ -__attribute__((used)) -#endif -int TV_ttf_display_type(boost::multi::subarray const* mad2P) { - boost::multi::array const value = *mad2P; - return TV_ttf_display_type(std::addressof(value)); -} - -template -#ifdef __GCC__ -__attribute__((used)) -#endif -int TV_ttf_display_type(boost::multi::subarray const* mad2P) { - boost::multi::array const value = *mad2P; - return TV_ttf_display_type(std::addressof(value)); -} - -template int TV_ttf_display_type(boost::multi::array const*); -template int TV_ttf_display_type(boost::multi::array const*); -template int TV_ttf_display_type>(boost::multi::array, 1> const*); -template int TV_ttf_display_type >(boost::multi::array , 1> const*); -template int TV_ttf_display_type(boost::multi::array const*); -template int TV_ttf_display_type(boost::multi::array const*); - -template int TV_ttf_display_type(boost::multi::array const*); -template int TV_ttf_display_type(boost::multi::array const*); -template int TV_ttf_display_type>(boost::multi::array, 2> const*); -template int TV_ttf_display_type >(boost::multi::array , 2> const*); -template int TV_ttf_display_type(boost::multi::array const*); -template int TV_ttf_display_type(boost::multi::array const*); - -template int TV_ttf_display_type(boost::multi::subarray const*); -template int TV_ttf_display_type(boost::multi::subarray const*); -template int TV_ttf_display_type>(boost::multi::subarray, 1> const*); -template int TV_ttf_display_type >(boost::multi::subarray , 1> const*); -template int TV_ttf_display_type(boost::multi::subarray const*); -template int TV_ttf_display_type(boost::multi::subarray const*); - -template int TV_ttf_display_type(boost::multi::subarray const*); -template int TV_ttf_display_type(boost::multi::subarray const*); -template int TV_ttf_display_type>(boost::multi::subarray, 2> const*); -template int TV_ttf_display_type >(boost::multi::subarray , 2> const*); -template int TV_ttf_display_type(boost::multi::subarray const*); -template int TV_ttf_display_type(boost::multi::subarray const*); - -#if defined(__INCLUDE_LEVEL__) and (not __INCLUDE_LEVEL__) - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi TotalView adaptor" -#define BOOST_TEST_DYN_LINK -#include - -#include "../array.hpp" -#include "../utility.hpp" - -#include -#include -#include // iota -#include // transform - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_1d) { - - std::vector V = {10, 20, 30}; - - multi::array const A = {1, 2, 3, 4, 5}; - auto&& Apart = A({1, 3}); - - multi::array const B = {{1, 2, 3}, {4, 5, 6}}; - - double sum = 0; - for(auto i : A.extension()) sum += A[i]; - - BOOST_REQUIRE( sum == 15. ); - BOOST_REQUIRE( B[1][0] == 4. ); -} - -#endif -#endif - diff --git a/include/multi/array.hpp b/include/multi/array.hpp deleted file mode 100644 index c9dd0d173..000000000 --- a/include/multi/array.hpp +++ /dev/null @@ -1,1274 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa - -#ifndef MULTI_ARRAY_HPP_ -#define MULTI_ARRAY_HPP_ - -#include "../multi/array_ref.hpp" -#include "./config/NO_UNIQUE_ADDRESS.hpp" - -#include "./detail/adl.hpp" -#include "./detail/memory.hpp" -#include "./detail/type_traits.hpp" - -#include // for copy -#include // for allocator_traits -#include // needed by a deprecated function -#include // for move - -namespace boost::multi { - -template -inline constexpr bool force_element_trivial_default_construction = false; - -template -struct array_allocator { - using allocator_type = Allocator; - array_allocator() = default; - - private: - MULTI_NO_UNIQUE_ADDRESS allocator_type alloc_; - - using allocator_traits = typename multi::allocator_traits; - using size_type_ = typename allocator_traits::size_type; - using pointer_ = typename allocator_traits::pointer; - - protected: - auto alloc() & -> allocator_type & {return alloc_;} - auto alloc() const& -> allocator_type const& {return alloc_;} - - explicit array_allocator(allocator_type const& alloc) : alloc_{alloc} {} // NOLINT(modernize-pass-by-value) - - auto allocate(size_type_ n) -> pointer_ { - return n?allocator_traits::allocate(alloc_, n):pointer_{nullptr}; - } - auto allocate(size_type_ n, typename allocator_traits::const_void_pointer hint) -> pointer_ { - return n?allocator_traits::allocate(alloc_, n, hint):pointer_{nullptr}; - } - - auto uninitialized_fill_n(pointer_ first, size_type_ count, typename allocator_traits::value_type const& value) { - return adl_alloc_uninitialized_fill_n(alloc_, first, count, value); - } - template - auto uninitialized_copy_n(It first, size_type count, pointer_ d_first) { - if constexpr(std::is_trivial_v::value_type>) { - return adl_copy_n( first, count, d_first); - } else { - return adl_alloc_uninitialized_copy_n(alloc_, first, count, d_first); - } - } - template - auto destroy_n(It first, size_type n) {return adl_alloc_destroy_n(this->alloc(), first, n);} - - public: - constexpr auto get_allocator() const -> allocator_type {return alloc_;} -}; - -template> // DummyAlloc mechanism allows using the convention array>, is an_allocator supports void template argument -struct static_array // NOLINT(fuchsia-multiple-inheritance) : multiple inheritance used for composition -: protected array_allocator< - // Alloc - typename allocator_traits::template rebind_alloc -> -, public array_ref::template rebind_alloc>::pointer> -, boost::multi::random_iterable::template rebind_alloc>> { - static_assert( - std::is_same_v::value_type>, typename static_array::element> - or std::is_same_v::value_type>, void >, // allocator template can be redundant or void (which can be a default for the allocator) - "allocator value type must match array value type" - ); - private: - using Alloc = typename allocator_traits::template rebind_alloc; - - protected: - using array_alloc = array_allocator; - - public: - using array_alloc::get_allocator; - using allocator_type = typename array_allocator::allocator_type; - using decay_type = array; - using layout_type = typename array_ref::pointer>::layout_type; - - using ref = array_ref::template rebind_alloc>::pointer>; - - protected: - using alloc_traits = typename multi::allocator_traits; - - auto uninitialized_value_construct() { - return adl_alloc_uninitialized_value_construct_n(static_array::alloc(), this->base_, this->num_elements()); - } - - auto uninitialized_default_construct_if(std::true_type /*true */) { /*noop*/ } - auto uninitialized_default_construct_if(std::false_type /*false*/) { - return adl_alloc_uninitialized_default_construct_n(static_array::alloc(), this->base_, this->num_elements()); - } - - auto uninitialized_default_construct() { - return uninitialized_default_construct_if(std::integral_constant< - bool, - std::is_trivially_default_constructible_v or multi::force_element_trivial_default_construction - >{}); - } - - template auto uninitialized_copy_elements(It first) { - return array_alloc::uninitialized_copy_n(first, this->num_elements(), this->data_elements()); - } - - void destroy() { - if constexpr(not (std::is_trivially_destructible_v or multi::force_element_trivial_default_construction)) { - array_alloc::destroy_n(this->data_elements(), this->num_elements()); - } - } - - void allocate() {this->base_ = array_alloc::allocate(static_cast::size_type>(static_array::num_elements()));} - - public: - using value_type = typename std::conditional_t< - (D > 1), // this parenthesis is needed - array, - typename static_array::element - >; - - using typename ref::size_type; - using typename ref::difference_type; - explicit static_array(allocator_type const& alloc) : array_alloc{alloc} {} - - using ref::operator(); - HD constexpr auto operator()() && -> decltype(auto) {return ref::element_moved();} - - using ref::take; - constexpr auto take(difference_type n) && -> decltype(auto) {return ref::take(n).element_moved();} - - using ref::drop; - constexpr auto drop(difference_type n) && -> decltype(auto) {return ref::drop(n).element_moved();} - - static_array(static_array&&) = delete; - - static_array(decay_type&& other, allocator_type const& alloc) noexcept // 6b TODO(correaa) move from array only - : array_alloc{alloc} // TODO(correaa) : handle allocation propagation here - , ref{std::exchange(other.base_, nullptr), other.extensions()} { - std::move(other).layout_mutable() = {}; - } - - explicit static_array(decay_type&& other) noexcept - : static_array(std::move(other), allocator_type{}) {} // 6b - - template>::difference_type> - static_array(It first, It last, allocator_type const& alloc) - : array_alloc{alloc} - , ref { - array_alloc::allocate(static_cast::size_type>(layout_type {index_extension {adl_distance(first, last)}*multi::extensions(*first)}.num_elements())), - index_extension {adl_distance(first, last)}*multi::extensions(*first) - } { - adl_alloc_uninitialized_copy(static_array::alloc(), first, last, ref::begin()); - } - - template>::difference_type> - // analogous to std::vector::vector (5) https://en.cppreference.com/w/cpp/container/vector/vector - static_array(It first, It last) : static_array(first, last, allocator_type{}) {} - - template< - class Range, class = std::enable_if_t>{}>, - class = decltype(/*static_array*/(std::declval().begin() - std::declval().end())), // instantiation of static_array here gives a compiler error in 11.0, partially defined type? - class = std::enable_if_t{}> - > - // cppcheck-suppress noExplicitConstructor ; because I want to use equal for lazy assigments form range-expressions // NOLINTNEXTLINE(runtime/explicit) - static_array(Range const& rng) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax - : static_array{std::begin(rng), std::end(rng)} {} // Sonar: Prefer free functions over member functions when handling objects of generic type "Range". - // : static_array{rng.begin(), rng.end()} {} // Sonar: Prefer free functions over member functions when handling objects of generic type "Range". - - template - auto uninitialized_fill_elements(TT const& value) { - return array_alloc::uninitialized_fill_n(this->data_elements(), this->num_elements(), value); - } - - template - static_array(array_ref const& other, allocator_type const& alloc) - : array_alloc{alloc} - , ref{ - array_alloc::allocate(static_cast::size_type>(other.num_elements())), - other.extensions() - } { - if constexpr(std::is_trivial_v) { - adl_copy_n( other.data_elements(), other.num_elements(), this->data_elements()); - } else { - adl_alloc_uninitialized_copy_n(static_array::alloc(), other.data_elements(), other.num_elements(), this->data_elements()); - } - } - - static_array(typename static_array::extensions_type extensions, typename static_array::element const& elem, allocator_type const& alloc) // 2 - : array_alloc{alloc} - , ref{array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t{extensions}.num_elements()), nullptr), extensions} { - array_alloc::uninitialized_fill_n(this->data_elements(), static_cast::size_type>(this->num_elements()), elem); - } - - template{} and (D == 0), int> = 0> - explicit static_array(Element const& elem, allocator_type const& alloc) - : static_array(typename static_array::extensions_type{}, elem, alloc) {} - - static_array(typename static_array::extensions_type extensions, typename static_array::element const& elem) // 2 - : array_alloc{} - , ref{array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t{extensions}.num_elements()), nullptr), extensions} { - array_alloc::uninitialized_fill_n(this->base(), static_cast::size_type>(this->num_elements()), elem); - } - - template{}>> - explicit static_array(typename static_array::index_extension const& extension, ValueType const& value, allocator_type const& alloc) // 3 - = delete; - - template{}>> - explicit static_array(typename static_array::index_extension const& extension, ValueType const& value) // 3 - = delete; - - explicit static_array(typename static_array::extensions_type extensions, allocator_type const& alloc) - : array_alloc{alloc} - , ref{array_alloc::allocate(static_cast::size_type>(typename static_array::layout_t{extensions}.num_elements())), extensions} { - uninitialized_default_construct(); - } - - explicit static_array(typename static_array::extensions_type extensions) - : static_array(extensions, allocator_type{}) {} - - template::element>{}>, - class = decltype(adl_copy(std::declval const&>().begin(), std::declval const&>().end(), std::declval())) - > - static_array(multi::subarray const& other, allocator_type const& alloc) - : static_array(other.extensions(), alloc) { - adl_uninitialized_copy(other.begin(), other.end(), this->begin()); // TODO(correaa): call this conditionally on T properties - } - - template const&>().base()), T>, int> =0, - // class = std::enable_if_t::element>{}>, - class = decltype(adl_copy(std::declval const&>().begin(), std::declval const&>().end(), std::declval())) - > - // cppcheck-suppress noExplicitConstructor // NOLINTNEXTLINE(runtime/explicit) - /*mplct*/static_array(multi::subarray const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - : static_array(other, allocator_type{}) {} - - template const&>().base()), T>, int> =0, - // class = std::enable_if_t::element>{}>, - class = decltype(adl_copy(std::declval const&>().begin(), std::declval const&>().end(), std::declval())) - > - explicit static_array(multi::subarray const& other) - : static_array(other, allocator_type{}) {} - - template&>().base()), T>, int> =0, - // class = std::enable_if_t::element>{}>, - class = decltype(adl_copy(std::declval&>().begin(), std::declval const&>().end(), std::declval())) - > - // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(runtime/explicit) - /*mplct*/static_array(multi::subarray& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - : static_array(other, allocator_type{}) {} - - template&>().base()), T>, int> =0, - // class = std::enable_if_t::element>{}>, - class = decltype(adl_copy(std::declval&>().begin(), std::declval const&>().end(), std::declval())) - > - explicit static_array(multi::subarray& other) - : static_array(other, allocator_type{}) {} - - template&&>().base()), T>, int> =0, - // class = std::enable_if_t::element>{}>, - class = decltype(adl_copy(std::declval&&>().begin(), std::declval const&>().end(), std::declval())) - > - // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(runtime/explicit) - /*mplct*/static_array(multi::subarray&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - : static_array(std::move(other), allocator_type{}) {} - - template&&>().base()), T>, int> =0, - // class = std::enable_if_t::element>{}>, - class = decltype(adl_copy(std::declval&&>().begin(), std::declval const&>().end(), std::declval())) - > - explicit static_array(multi::subarray&& other) - : static_array(std::move(other), allocator_type{}) {} - - template&>().base()), T>, int> =0 - > - /*mplct*/static_array(array_ref & other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - : array_alloc{} - , ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { - static_array::uninitialized_copy_elements(other.data_elements()); - } - - template - explicit static_array(array_ref & other, std::enable_if_t>* /*unused*/= nullptr) // NOLINT(fuchsia-default-arguments-declarations) - : array_alloc{} - , ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { - static_array::uninitialized_copy_elements(other.data_elements()); - } - - template &&>().base()), T>, int> =0> - /*mplct*/static_array(array_ref && other) // NOLINT(fuchsia-default-arguments-declarations,google-explicit-constructor,hicpp-explicit-conversions) - : array_alloc{} - , ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { - static_array::uninitialized_copy_elements(std::move(other).data_elements()); - } - - template - explicit static_array(array_ref && other, std::enable_if_t>* /*unused*/= nullptr) // NOLINT(fuchsia-default-arguments-declarations) - : array_alloc{} - , ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { - static_array::uninitialized_copy_elements(std::move(other).data_elements()); - } - - template - /*mplct*/static_array(array_ref const& other, std::enable_if_t< multi::is_implicitly_convertible_v>* /*unused*/= nullptr) // NOLINT(fuchsia-default-arguments-declarations,google-explicit-constructor,hicpp-explicit-conversions) - : array_alloc{} - , ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { - static_array::uninitialized_copy_elements(other.data_elements()); - // [[maybe_unused]] auto const fun = [&](){T dummy{*other.base()};}; - } - - template - explicit static_array(array_ref const& other, std::enable_if_t>* /*unused*/= nullptr) // NOLINT(fuchsia-default-arguments-declarations) - : array_alloc{} - , ref{array_alloc::allocate(static_cast::size_type>(other.num_elements())), other.extensions()} { - static_array::uninitialized_copy_elements(std::move(other).data_elements()); - } - - static_array(static_array const& other) // 5b - : array_alloc{allocator_traits::select_on_container_copy_construction(other.alloc())} - , ref{array_alloc::allocate(static_cast::size_type>(other.num_elements()), other.data_elements()), extensions(other)} { - uninitialized_copy_elements(other.data_elements()); - } - - // cppcheck-suppress noExplicitConstructor ; to allow assignment-like construction of nested arrays - static_array(std::initializer_list::value_type> values) - : static_array{array(values.begin(), values.end())} {} // construct all with default constructor and copy to special memory at the end - - static_array( - std::initializer_list::value_type> values, - allocator_type const& alloc - ) - : static_array{static_array(values.begin(), values.end()), alloc} {} - - template - constexpr explicit static_array(TT(&array)[N]) // @SuppressWarnings(cpp:S5945) NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backward compatibility // NOSONAR - : static_array(std::begin(array), std::end(array)) {} - - constexpr auto begin() const& -> typename static_array::const_iterator {return ref:: begin();} - constexpr auto end () const& -> typename static_array::const_iterator {return ref:: end ();} - - constexpr auto begin() && -> typename static_array:: iterator {return ref:: begin();} - constexpr auto end () && -> typename static_array:: iterator {return ref:: end ();} - - constexpr auto begin() & -> typename static_array:: iterator {return ref:: begin();} - constexpr auto end () & -> typename static_array:: iterator {return ref:: end ();} - - constexpr auto operator[](index idx) const& -> typename static_array::const_reference {return ref::operator[](idx);} - constexpr auto operator[](index idx) && -> decltype(auto) { - if constexpr(D == 1) {return std::move(ref::operator[](idx) );} - else {return ref::operator[](idx).moved();} // NOLINT(readability/braces) - } - constexpr auto operator[](index idx) & -> typename static_array:: reference {return ref::operator[](idx);} - - protected: - void deallocate() { - if(this->num_elements()) { - alloc_traits::deallocate(this->alloc(), this->base_, static_cast(this->num_elements())); - } - } - void clear() noexcept { - this->destroy(); - deallocate(); - this->layout_mutable() = {}; - } - template - constexpr auto reindex(Indices... idxs) & -> static_array& { - static_array::layout_t::reindex(idxs...); - return *this; - } - template - constexpr auto reindex(Indices... idxs) && -> static_array&& { - reindex(idxs...); - return std::move(*this); - } - - public: - static_array() = default; - ~static_array() noexcept {destroy(); deallocate();} - - using element_const_ptr = typename std::pointer_traits::template rebind; - using element_move_ptr = multi::move_ptr; - - using reference = std::conditional_t< - (D > 1), - subarray, - std::conditional_t< - D == 1, - typename std::iterator_traits::reference, - void - > - >; - using const_reference = std::conditional_t< - (D > 1), - subarray, // TODO(correaa) should be const_reference, but doesn't work witn rangev3? - std::conditional_t< - D == 1, - decltype(*std::declval()), - void - > - >; - - using iterator = multi::array_iterator; - using const_iterator = multi::array_iterator; - - friend MULTI_NONV_CONSTEXPR auto get_allocator(static_array const& self) -> allocator_type {return self.get_allocator();} - - HD constexpr auto data_elements() const& -> element_const_ptr {return this->base_;} - HD constexpr auto data_elements() & -> typename static_array::element_ptr {return this->base_;} - HD constexpr auto data_elements() && -> typename static_array::element_move_ptr {return std::make_move_iterator(this->base_);} - friend constexpr auto data_elements(static_array const& self) {return self .data_elements();} - friend constexpr auto data_elements(static_array & self) {return self .data_elements();} - friend constexpr auto data_elements(static_array && self) {return std::move(self).data_elements();} - - constexpr auto base() & -> typename static_array::element_ptr { return ref::base(); } - constexpr auto base() const& -> typename static_array::element_const_ptr { return typename static_array::element_const_ptr{ref::base()}; } - - friend MULTI_NONV_CONSTEXPR auto base(static_array & self) -> typename static_array::element_ptr { return self.base(); } - friend MULTI_NONV_CONSTEXPR auto base(static_array const& self) -> typename static_array::element_const_ptr { return self.base(); } - - constexpr auto origin() & -> typename static_array::element_ptr {return ref::origin();} - constexpr auto origin() const& -> typename static_array::element_const_ptr {return ref::origin();} - friend MULTI_NONV_CONSTEXPR auto origin(static_array & self) -> typename static_array::element_ptr {return self.origin();} - friend MULTI_NONV_CONSTEXPR auto origin(static_array const& self) -> typename static_array::element_const_ptr {return self.origin();} - - private: - constexpr auto rotated_aux() const { - typename static_array::layout_t new_layout = this->layout(); - new_layout.rotate(); - return subarray{new_layout, this->base_}; - } - - public: - constexpr auto rotated() const& {return std::move(*this).rotated_aux();} - constexpr auto rotated() & {return std::move(*this).rotated_aux();} - constexpr auto rotated() && {return std::move(*this).rotated_aux();} - - friend constexpr auto rotated(static_array& self) -> decltype(auto) {return self.rotated();} - friend /*constexpr*/ auto rotated(static_array const& self) -> decltype(auto) {return self.rotated();} - - constexpr auto unrotated() const& { - typename static_array::layout_t new_layout = this->layout(); - new_layout.unrotate(); - return subarray{new_layout, this->base_}; - } - constexpr auto unrotated() & { - typename static_array::layout_t new_layout = this->layout(); - new_layout.unrotate(); - return subarray{new_layout, this->base_}; - } - - friend constexpr auto unrotated(static_array & self) -> decltype(auto) {return self.unrotated();} - friend constexpr auto unrotated(static_array const& self) -> decltype(auto) {return self.unrotated();} - - template - auto operator=(multi::subarray const& other) -> static_array& { - ref::operator=(other); // TODO(correaa) : protect for self assigment - return *this; - } - auto operator=(static_array const& other) & -> static_array& { - if(std::addressof(other) == this) {return *this;} // cert-oop54-cpp - assert( extensions(other) == static_array::extensions() ); - if(&other == this) {return *this;} // lints (cert-oop54-cpp) : handle self-assignment properly - adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); - return *this; - } - constexpr auto operator=(static_array&& other) noexcept -> static_array& { // lints (cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - assert( extensions(other) == static_array::extensions() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : allow a constexpr-friendly assert - adl_move(other.data_elements(), other.data_elements() + other.num_elements(), this->data_elements()); // there is no std::move_n algorithm - return *this; - } - template - auto operator=(static_array const& other) & -> static_array& { - assert( extensions(other) == static_array::extensions() ); - adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); - return *this; - } - constexpr explicit operator subarray()& { - return this->template static_array_cast(*this); - } - - template - void serialize(Archive& arxiv, unsigned int const version) { - ref::serialize(arxiv, version); - } -}; - -template -struct static_array // NOLINT(fuchsia-multiple-inheritance) : design -: protected array_allocator -, public array_ref::allocator_type>::pointer> { - static_assert( std::is_same::value_type, typename static_array::element>{}, - "allocator value type must match array value type"); - - private: - using array_alloc = array_allocator; - - public: - // NOLINTNEXTLINE(runtime/operator) - auto operator&() && -> static_array * = delete; // NOLINT(google-runtime-operator) : delete to avoid taking address of temporary - // NOLINTNEXTLINE(runtime/operator) - auto operator&() & -> static_array * {return this;} // NOLINT(google-runtime-operator) : override from base - // NOLINTNEXTLINE(runtime/operator) - auto operator&() const& -> static_array const* {return this;} // NOLINT(google-runtime-operator) : override from base - - using array_alloc::get_allocator; - using allocator_type = typename static_array::allocator_type; - using decay_type = array; - - template - void assign(Ptr data) & { - if(data) { - assert(this->num_elements() == 1); - adl_copy_n(data, this->num_elements(), this->base()); - } - } - - template and not std::is_same_v, int> =0, - class = decltype(adl_copy_n( &std::declval(), 1, typename static_array::element_ptr{})) - > - auto operator=(Singleton const& single) -> static_array& { - assign(&single); - return *this; - } - - protected: - using alloc_traits = typename multi::allocator_traits; - using ref = array_ref::template rebind_alloc>::pointer>; - - auto uninitialized_value_construct() { - if constexpr(not(std::is_trivially_default_constructible_v or multi::force_element_trivial_default_construction)) { - return adl_alloc_uninitialized_value_construct_n(static_array::alloc(), this->base_, this->num_elements()); - } - } - - template auto uninitialized_copy(It first) {return adl_alloc_uninitialized_copy_n(this->alloc(), first, this->num_elements(), this->data_elements());} - template - auto uninitialized_move(It first) { - return adl_alloc_uninitialized_move_n(this->alloc(), first, this->num_elements(), this->data_elements()); - } - auto destroy() { - return adl_alloc_destroy_n(this->alloc(), this->data_elements(), this->num_elements()); - // array_alloc::destroy_n(this->data_elements(), this->num_elements()); - } - - public: - using typename ref::value_type; - using typename ref::size_type; - using typename ref::difference_type; - constexpr explicit static_array(allocator_type const& alloc) : array_alloc{alloc} {} - - constexpr static_array(decay_type&& other, allocator_type const& alloc) // 6b - : array_alloc{alloc} - , ref{other.base_, other.extensions()} { - std::move(other).ref::layout_t::operator=({}); - } - - using ref::operator==; - using ref::operator!=; - - static_array(typename static_array::extensions_type extensions, typename static_array::element const& elem, allocator_type const& alloc) // 2 - : array_alloc{alloc} - , ref(static_array::allocate(typename static_array::layout_t{extensions}.num_elements()), extensions) { - uninitialized_fill(elem); - } - - static_array(typename static_array::element_type const& elem, allocator_type const& alloc) - : static_array(typename static_array::extensions_type{}, elem, alloc) {} - - template - explicit static_array(multi::subarray const& other, allocator_type const& alloc) - : array_alloc{alloc} - , ref(static_array::allocate(other.num_elements()), extensions(other)) { - assert( other.num_elements() <= 1 ); - if(other.num_elements()) {adl_alloc_uninitialized_copy(static_array::alloc(), other.base(), other.base() + other.num_elements(), this->base());} // ref::begin()); - } - - template - explicit static_array(multi::static_array const& other, allocator_type const& alloc) // TODO(correaa) : call other constructor (above) - : array_alloc{alloc}, ref(static_array::allocate(other.num_elements()) - , extensions(other)) { - if(other.data_elements() and other.num_elements()) { - if constexpr(std::is_trivial_v) { - adl_copy_n( other.data_elements(), other.num_elements(), this->data_elements()); - } else { - adl_alloc_uninitialized_copy_n(static_array::alloc(), other.data_elements(), other.num_elements(), this->data_elements()); - } - } - } - - template - explicit static_array(multi::static_array const& other) - : static_array(other, allocator_type{}) {} - - auto uninitialized_fill(typename static_array::element const& elem) { - array_alloc::uninitialized_fill_n( - this->base_, - static_cast::size_type>(this->num_elements()), - elem - ); - } - - template - auto operator=(multi::subarray const& other) -> static_array& { - adl_copy_n(other.base(), 1, this->base()); - return *this; - } - - static_array( - typename static_array::extensions_type const& extensions, - typename static_array::element const& elem - ) // 2 - : array_alloc{} - , ref(static_array::allocate(static_cast::size_type>(typename static_array::layout_t{extensions}.num_elements()), nullptr), extensions) { - uninitialized_fill(elem); - } - - static_array() : static_array(multi::iextensions<0>{}) {} - - explicit static_array(typename static_array::element const& elem) // 2 - : static_array(multi::iextensions<0>{}, elem) {} - - template // , class = decltype(adl_copy_n(std::declval(), 1, typename static_array::element_ptr{}))> - explicit static_array(Pointer data) - : ref(data?static_array::allocate(1):nullptr, typename static_array::extensions_type{}) { - // if(data) { - // if constexpr(std::is_trivial_v) { - adl_copy_n( data, 1, this->data_elements()); - // } else { - // adl_alloc_uninitialized_copy_n(static_array::alloc(), data, 1, this->data_elements()); - // } - // } - } - - template and not std::is_same_v, int> =0, - class = decltype(adl_copy_n( &std::declval(), 1, typename static_array::element_ptr{})) - > - // NOLINTNEXTLINE(runtime/explicit) - static_array(Singleton const& single) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - : ref(static_array::allocate(1), typename static_array::extensions_type{}) - { - if constexpr(std::is_trivial_v) { - adl_copy_n( &single, 1, this->data_elements()); - } else { - adl_alloc_uninitialized_copy_n(static_array::alloc(), &single, 1, this->data_elements()); - } - } - - template{}>> - explicit static_array(typename static_array::index_extension const& extension, ValueType const& value, allocator_type const& alloc) // 3 - : static_array(extension*extensions(value), alloc) { - using std::fill; fill(this->begin(), this->end(), value); - } - template{}>> - explicit static_array(typename static_array::index_extension const& extension, ValueType const& value) // 3 // TODO(correaa) : call other constructor (above) - : static_array(extension*extensions(value)) { - using std::fill; fill(this->begin(), this->end(), value); - } - - explicit static_array(typename static_array::extensions_type const& extensions, allocator_type const& alloc) // 3 - : array_alloc{alloc} - , ref{static_array::allocate(typename static_array::layout_t{extensions}.num_elements()), extensions} { - uninitialized_value_construct(); - } - explicit static_array(typename static_array::extensions_type const& extensions) // 3 - : static_array(extensions, allocator_type{}) {} - - static_array(static_array const& other, allocator_type const& alloc) // 5b - : array_alloc{alloc} - , ref{static_array::allocate(other.num_elements()), extensions(other)} { - uninitialized_copy_(other.data_elements()); - } - - static_array(static_array const& other) // 5b - : array_alloc{other.get_allocator()} - , ref{static_array::allocate(other.num_elements(), other.data_elements()), {}} { - uninitialized_copy(other.data_elements()); - } - - static_array(static_array&& other) noexcept // it is private because it is a valid operation for derived classes //5b - : array_alloc{other.get_allocator()} - , ref{static_array::allocate(static_cast::size_type>(other.num_elements()), other.data_elements()), other.extensions()} { - uninitialized_move(other.data_elements()); - } -// template static auto distance(It a, It b) {using std::distance; return distance(a, b);} - - protected: - void deallocate() { // TODO(correaa) : move this to array_allocator - if(this->num_elements()) { - alloc_traits::deallocate(this->alloc(), this->base_, static_cast(this->num_elements())); - } - } - void clear() noexcept { - this->destroy(); - deallocate(); - layout_t<0>::operator=({}); - } - - public: - ~static_array() noexcept { - this->destroy(); - deallocate(); - } - using element_const_ptr = typename std::pointer_traits::template rebind; - - friend MULTI_NONV_CONSTEXPR auto get_allocator(static_array const& self) -> allocator_type {return self.get_allocator();} - - constexpr auto base() & -> typename static_array::element_ptr {return ref::base();} - constexpr auto base() const& -> typename static_array::element_const_ptr {return ref::base();} - friend constexpr auto base(static_array & self) -> typename static_array::element_ptr {return self.base();} - friend constexpr auto base(static_array const& self) -> typename static_array::element_const_ptr {return self.base();} - - constexpr auto origin() & -> typename static_array::element_ptr {return ref::origin();} - constexpr auto origin() const& -> typename static_array::element_const_ptr {return ref::origin();} - friend constexpr auto origin(static_array & self) -> typename static_array::element_ptr {return self.origin();} - friend constexpr auto origin(static_array const& self) -> typename static_array::element_const_ptr {return self.origin();} - - constexpr operator typename std::iterator_traits::reference() const& { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - return *(this->base_); - } - constexpr operator typename std::add_rvalue_reference::reference>::type()&& { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - return std::move(*(this->base_)); - } - constexpr operator typename std::iterator_traits::reference()& { // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - return *(this->base_); - } - - constexpr explicit operator typename static_array::element_type() const { - return *(this->base_); - } - - constexpr auto rotated() const& { - typename static_array::layout_t new_layout = *this; - new_layout.rotate(); - return subarray{new_layout, this->base_}; - } - - constexpr auto rotated() & { - typename static_array::layout_t new_layout = *this; - new_layout.rotate(); - return subarray{new_layout, this->base_}; - } - - constexpr auto rotated() && { - typename static_array::layout_t new_layout = *this; - new_layout.rotate(); - return subarray{new_layout, this->base_}; - } - - friend constexpr auto rotated(static_array& self) -> decltype(auto) {return self.rotated();} - friend constexpr auto rotated(static_array const& self) -> decltype(auto) {return self.rotated();} - - private: - constexpr auto unrotated_aux() { - typename static_array::layout_t new_layout = *this; - new_layout.unrotate(); - return subarray{new_layout, this->base_}; - } - - public: - constexpr auto unrotated() & {return unrotated_aux();} - constexpr auto unrotated() const& {return unrotated_aux().as_const();} - - friend constexpr auto unrotated(static_array & self) -> decltype(auto) {return self.unrotated();} - friend constexpr auto unrotated(static_array const& self) -> decltype(auto) {return self.unrotated();} - -// constexpr auto operator<<(dimensionality_type d) -> decltype(auto) {return rotated(d);} -// constexpr auto operator>>(dimensionality_type d) -> decltype(auto) {return unrotated(d);} - -// constexpr auto operator<<(dimensionality_type d) const -> decltype(auto) {return rotated(d);} -// constexpr auto operator>>(dimensionality_type d) const -> decltype(auto) {return unrotated(d);} - - constexpr auto operator=(static_array const& other) -> static_array& { - assert( extensions(other) == static_array::extensions() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : allow a constexpr-friendly assert - if(this == &other) {return *this;} // lints (cert-oop54-cpp) : handle self-assignment properly - adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); - return *this; - } - - private: - constexpr auto equal_extensions_if(std::true_type /*true */, static_array const& other ) {return this->extensions() == extensions(other);} - constexpr auto equal_extensions_if(std::false_type /*false*/, static_array const&/*other*/) {return true;} - - public: - constexpr auto operator=(static_array&& other) noexcept -> static_array& { - assert( equal_extensions_if(std::integral_constant{}, other) ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : allow a constexpr-friendly assert - adl_move(other.data_elements(), other.data_elements() + other.num_elements(), this->data_elements()); // there is no std::move_n algorithm - return *this; - } - - template{}> > - auto operator=(static_array const& other)& -> static_array& { - assert( extensions(other) == static_array::extensions() ); - adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); - return *this; - } - - constexpr explicit operator subarray()& { - return this->template static_array_cast(); - // return static_array_cast(*this); - } - - template - void serialize(Archive& arxiv, unsigned int const version) { - ref::serialize(arxiv, version); - } -}; - -template -struct array : static_array { - using static_ = static_array; - using static_::static_; - - using static_array::operator=; - - template - auto operator=(multi::array const& other) & -> array& { - if(other.base()) {adl_copy_n(other.base(), other.num_elements(), this->base());} - return *this; - } - - template - auto operator=(multi::array const& other) && -> array&& { // NOLINT(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) should assigment return auto& ? - if(other.base()) {adl_copy_n(other.base(), other.num_elements(), this->base());} - return std::move(*this); - } - - template>{}, int> /*dummy*/=0> - auto operator=(Other const& other) -> array& {this->assign(&other); return *this;} // NOLINT(google-runtime-operator) allow assigment from other ranges - - auto reextent(typename array::extensions_type const& /*empty_extensions*/) -> array& { - return *this; - } - - // NOLINTNEXTLINE(runtime/operator) - auto operator&() && -> array * = delete; // NOLINT(google-runtime-operator) : delete operator&& defined in base class to avoid taking address of temporary - // auto operator&() & -> array *{return this;} - // auto operator&() const& -> array const*{return this;} -}; - -template -struct array : static_array { - using static_ = static_array; - static_assert( - std::is_same_v> - or std::is_same_v, - "only exact type of array element or void (default?) is allowed as allocator value type" - ); - - public: - // NOLINTNEXTLINE(runtime/operator) - auto operator&() && -> array * = delete; // NOLINT(google-runtime-operator) : delete operator&& defined in base class to avoid taking address of temporary - // NOLINTNEXTLINE(runtime/operator) - auto operator&() & -> array * {return this;} // NOLINT(google-runtime-operator) : delete operator&& defined in base class to avoid taking address of temporary - // NOLINTNEXTLINE(runtime/operator) - auto operator&() const& -> array const* {return this;} // NOLINT(google-runtime-operator) : delete operator&& defined in base class to avoid taking address of temporary - - friend auto sizes(array const& self) -> typename array::sizes_type {return self.sizes();} - - template - void serialize(Archive& arxiv, unsigned int const version) { - auto extensions_ = this->extensions(); - using make_nvp = typename multi::archive_traits::make_nvp; // can be boost::serialization::make_nvp or cereal::make_nvp( - arxiv & make_nvp("extensions", extensions_); - if(this->extensions() != extensions_) { - clear(); - this->reextent(extensions_); - } - static_::serialize(arxiv, version); - } - - using static_::static_; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) passing c-arrays to base - using typename static_::value_type; - - // cppcheck-suppress noExplicitConstructor ; to allow assignment-like construction of nested arrays - array(std::initializer_list::value_type> ilv) - : static_{array(ilv.begin(), ilv.end())} {} - - template::value_type, OtherT> and not std::is_convertible_v::value_type> and (D == 1) > > - explicit array(std::initializer_list ilv) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) inherit explicitness of conversion from the elements - : static_{array(ilv.begin(), ilv.end()).element_transformed([](auto const& elem) noexcept {return static_cast(elem);})} {} // TODO(correaa) investigate why noexcept is necessary - - // template and std::is_same_v and std::is_convertible_v and D == 1, int> = 0> - // explicit array(std::initializer_list ilv, TTT* = nullptr) - // : static_{array(ilv.begin(), ilv.end())} { - // if(ilv.size() == 1) {throw std::runtime_error{"ambiguous call"};} - // } - - // template and std::is_same_v and std::is_convertible_v and D == 1, int> = 0> - // array(std::initializer_list ilv, TTT* = nullptr, double = 0.0) - // : static_{array(ilv.begin(), ilv.end())} { - // if(ilv.size() == 1) {throw std::runtime_error{"ambiguous call"};} - // } - - array() = default; - array(array const&) = default; - - auto reshape(typename array::extensions_type extensions) & -> array& { - typename array::layout_t const new_layout{extensions}; // TODO(correaa) implement move-reextent in terms of reshape - assert( new_layout.num_elements() == this->num_elements() ); - this->layout_mutable() = new_layout; - return *this; - } - - auto clear() noexcept -> array& { - static_::clear(); - return *this; - } - friend auto clear(array& self) noexcept -> array& {return self.clear();} - - friend auto data_elements(array const& self) {return self.data_elements();} - friend auto data_elements(array & self) {return self.data_elements();} - friend auto data_elements(array && self) {return std::move(self).data_elements();} - - auto move() & -> subarray> { - subarray> - ret = multi::static_array_cast>(*this); - layout_t::operator=({}); - return ret; - } - friend auto move(array& self) -> subarray >{ - return self.move(); - } - - array(array&& other, typename array::allocator_type const& alloc) noexcept : static_{std::move(other), alloc} {} - array(array&& other) noexcept : array{std::move(other), other.get_allocator()} {} - - friend MULTI_NONV_CONSTEXPR auto get_allocator(array const& self) -> typename array::allocator_type {return self.get_allocator();} - - void swap(array& other) noexcept { - using std::swap; - if constexpr(allocator_traits::propagate_on_container_swap::value) { - swap(this->alloc(), other.alloc()); - } - swap(this->base_, other.base_); - swap( - this->layout_mutable(), - other.layout_mutable() - ); - } - -#ifndef NOEXCEPT_ASSIGNMENT - auto operator=(array&& other) noexcept -> array& { - if(this == std::addressof(other)) {return *this;} - clear(); - this->base_ = other.base_; - if constexpr(allocator_traits::propagate_on_container_move_assignment::value) { - this->alloc() = std::move(other.alloc()); - } - this->layout_mutable() = std::exchange(other.layout_mutable(), {}); - return *this; - } - - auto operator=(array const& other) -> array& { - if(array::extensions() == other.extensions()) { - if(this == &other) {return *this;} // required by cert-oop54-cpp - if constexpr(allocator_traits::propagate_on_container_copy_assignment::value) { - this->alloc() = other.alloc(); - } - static_::operator=(other); - } else { - clear(); - if constexpr(allocator_traits::propagate_on_container_copy_assignment::value) { - this->alloc() = other.alloc(); - } - this->layout_mutable() = other.layout(); - array::allocate(); - array::uninitialized_copy_elements(other.data_elements()); - } - return *this; - } -#else - auto operator=(array o) noexcept -> array& {return swap(o), *this;} -#endif - - template - auto operator=(multi::subarray const& other) -> array& { - if(array::extensions() == other.extensions()) { - static_::operator=(other); // TODO(correaa) : protect for self assigment - } else { - operator=(array{other}); - } - return *this; - } - - template // , std::enable_if_t> , int> =0> - auto operator=(multi::array const& other) -> array& { // TODO(correaa) : check that LHS is not read-only? - if(array::extensions() == other.extensions()) { - // this->operator()() = other; - static_::operator=(other); - } else if(this->num_elements() == other.extensions().num_elements()) { - reshape(other.extensions()); - static_::operator=(other); - // this->operator()() = other; - } else { - operator=(static_cast(other)); - } - return *this; - } - - template< - class Range, - class = decltype(std::declval().operator=(std::declval())), - std::enable_if_t>::value, int> =0, - std::enable_if_t>{}, int> =0 - > - auto operator=(Range&& other) ->array& { // TODO(correaa) : check that LHS is not read-only? - if(array::extensions() == other.extensions()) { - this->operator()() = other; - // static_::operator=(other); - } else if(this->num_elements() == other.extensions().num_elements()) { - reshape(other.extensions()); - // static_::operator=(other); - this->operator()() = other; - } else { - operator=(static_cast(std::forward(other))); - } - return *this; - } - - template< - class Range, - class = decltype(std::declval().operator=(std::declval())), - std::enable_if_t>{}, int> = 0 - > - auto from(Range&& other) ->array& { // TODO(correaa) : check that LHS is not read-only? - if(array::extensions() == other.extensions()) { - this->operator()() = other; - // static_::operator=(other); - } else if(this->num_elements() == other.extensions().num_elements()) { - reshape(other.extensions()); - this->operator()() = other; - // static_::operator=(other); - } else { - operator=(static_cast(std::forward(other))); - } - return *this; - } - - friend void swap(array& self, array& other) noexcept(true/*noexcept(self.swap(other))*/) {self.swap(other);} - - void assign(typename array::extensions_type extensions, typename array::element const& elem) { - if(array::extensions() == extensions) { - adl_fill_n(this->base_, this->num_elements(), elem); - } else { - this->clear(); - (*this).array::layout_t::operator=(layout_t{extensions}); - this->base_ = this->static_::array_alloc::allocate(this->num_elements(), nullptr); - adl_alloc_uninitialized_fill_n(this->alloc(), this->base_, this->num_elements(), elem); - } - } - - template - auto assign(It first, It last) -> array& { - using std::next; using std::all_of; - if(adl_distance(first, last) == array::size()) { // and multi::extensions(*first) == multi::extensions(*array::begin())){ - static_::ref::assign(first); - } else { - this->operator=(array(first, last)); - } - return *this; - } - void assign(std::initializer_list values) {assign(values.begin(), values.end());} - - template auto assign(Range&& other) & - ->decltype(assign(adl_begin(std::forward(other)), adl_end(std::forward(other)))) { // TODO(correaa) use forward - return assign(adl_begin(std::forward(other)), adl_end(std::forward(other))); } - - auto operator=(std::initializer_list values) -> array& { - assign(values.begin(), values.end()); - return *this; - } - - template - [[deprecated("use extensions for reextents, not tuples")]] - auto reextent(std::tuple const& other) -> array& { - return reextent( - std::apply([](auto const&... extensions) {return typename array::extensions_type(extensions...);}, other) - ); // paren is important here ext_type(...) for allow narrowing casts ^^^ - } - - auto reextent(typename array::extensions_type const& extensions) && -> array&& { - if(extensions == this->extensions()) {return std::move(*this);} - this->destroy(); - this->deallocate(); - this->layout_mutable() = typename array::layout_t{extensions}; - this->base_ = this->static_::array_alloc::allocate( - static_cast::size_type>( - typename array::layout_t{extensions}.num_elements() - ), - this->data_elements() // used as hint - ); - if constexpr(not (std::is_trivially_default_constructible_v or multi::force_element_trivial_default_construction)) { - adl_alloc_uninitialized_value_construct_n(this->alloc(), this->base_, this->num_elements()); - } - return std::move(*this); - } - - auto reextent(typename array::extensions_type const& extensions) & -> array& { - if(extensions == this->extensions()) {return *this;} - auto&& tmp = typename array::ref{ - this->static_::array_alloc::allocate( - static_cast::size_type>( - typename array::layout_t{extensions}.num_elements() - ), - this->data_elements() // used as hint - ), - extensions - }; - if constexpr(not (std::is_trivially_default_constructible_v or multi::force_element_trivial_default_construction)) { - adl_alloc_uninitialized_value_construct_n(this->alloc(), tmp.data_elements(), tmp.num_elements()); - } - auto const is = intersection(this->extensions(), extensions); - tmp.apply(is) = this->apply(is); // TODO(correaa) : use (and implement) `.move();` - this->destroy(); - this->deallocate(); - this->base_ = tmp.base(); - this->layout_mutable() = tmp.layout(); - return *this; - } - - constexpr auto operator+() const& {return array{ *this };} - constexpr auto operator+() && {return array{std::move(*this)};} - -#if 0 - auto reextent(typename array::extensions_type const& extensions, typename array::element const& elem) && -> array&& { - if(extensions == this->extensions()) {return std::move(*this);} - this->destroy(); - this->deallocate(); - this->layout_mutable() = typename array::layout_t{extensions}; - this->base_ = this->static_::array_alloc::allocate( - static_cast::size_type>( - typename array::layout_t{extensions}.num_elements() - ), - this->data_elements() // used as hint - ); - this->uninitialized_fill_n(this->base_, static_cast::size_type>(this->num_elements()), elem); - - return std::move(*this); - } -#endif - - auto reextent(typename array::extensions_type const& exs, typename array::element const& elem) & -> array& { - if(exs == this->extensions()) { - return *this; - } -#if 0 - array tmp(x, e, this->get_allocator()); // TODO(correaa) opportunity missed to use hint allocation - auto const is = intersection(this->extensions(), x); - tmp.apply(is) = this->apply(is); - swap(tmp); -#else // implementation with hint - auto&& tmp = typename array::ref{this->static_::array_alloc::allocate( - static_cast::size_type>(typename array::layout_t{exs}.num_elements()), - this->data_elements() // use as hint - ), exs}; - this->uninitialized_fill_n(tmp.data_elements(), static_cast::size_type>(tmp.num_elements()), elem); - auto const is = intersection(this->extensions(), exs); - tmp.apply(is) = this->apply(is); - this->destroy(); - this->deallocate(); - this->base_ = tmp.base(); // TODO(correaa) : use (and implement) `.move();` - this->layout_mutable() = tmp.layout(); - // (*this).array::layout_t::operator=(tmp.layout()); -#endif - return *this; - } - template constexpr auto reindex(Indices... idxs)&& -> array&& {this->layout_mutable().reindex(idxs...); return std::move(*this);} - template constexpr auto reindex(Indices... idxs) & -> array & {this->layout_mutable().reindex(idxs...); return *this ;} - - ~array() = default; -}; - -#if defined(__cpp_deduction_guides) - -#define IL std::initializer_list // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing TODO(correaa) remove - -template static_array(IL ) -> static_array; -template static_array(IL> ) -> static_array; -template static_array(IL>> ) -> static_array; -template static_array(IL>>> ) -> static_array; -template static_array(IL>>>>) -> static_array; - -template array(IL ) -> array; -template array(IL> ) -> array; -template array(IL>> ) -> array; -template array(IL>>> ) -> array; -template array(IL>>>>) -> array; - -#undef IL - -template array(T[] ) -> array; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - -// vvv these are necessary to catch {n, m, ...} notation (or single integer notation) -template>> array(iextensions<0>, T) -> array; -template>> array(iextensions<1>, T) -> array; -template>> array(iextensions<2>, T) -> array; -template>> array(iextensions<3>, T) -> array; -template>> array(iextensions<4>, T) -> array; -template>> array(iextensions<5>, T) -> array; - -// generalization, will not work with naked {n, m, ...} notation (or single integer notation) -template> > -array(iextensions, T) -> array; - -template -array(MatrixRef)->array; - -template array(subarray)->array; - -#endif // ends defined(__cpp_deduction_guides) - -template -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility -auto decay(const T(&arr)[N]) noexcept -> multi::array, std::rank_v> { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - return multi::array_cref::type, std::rank_v>(data_elements(arr), extensions(arr)); -} - -template -struct array_traits { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - using reference = T&; - using element = std::remove_all_extents_t; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - using decay_type = multi::array; -}; - -} // end namespace boost::multi - -namespace boost::multi::pmr { - -#if (defined(__cpp_lib_memory_resource) && (__cpp_lib_memory_resource >= 201603)) -// # or defined(__INTEL_LLVM_COMPILER) or defined(__INTEL_COMPILER) // workaround for icpx 2022.2.0 with gcc 9.3 and icpc icpc 2021.7.0 -template -using array = boost::multi::array>; -#define MULTI_PROVIDES_PMR_ARRAY 1 // NOLINT(cppcoreguidelines-macro-usage) -#else -// template -// struct [[deprecated("no PMR allocator")]] array; // your version of C++ doesn't provide polymorphic_allocators -#define MULTI_PROVIDES_PMR_ARRAY 0 // NOLINT(cppcoreguidelines-macro-usage) -#endif - -} // end namespace boost::multi::pmr - -namespace boost::serialization { - -template -struct version< boost::multi::array > { - using type = std::integral_constant; // typedef mpl::int_<1> type; - enum { value = type::value }; -}; - -} // end namespace boost::serialization - -#endif // MULTI_ARRAY_HPP_ diff --git a/include/multi/array_ref.hpp b/include/multi/array_ref.hpp deleted file mode 100644 index 3cc5b03c0..000000000 --- a/include/multi/array_ref.hpp +++ /dev/null @@ -1,2798 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa - -#ifndef MULTI_ARRAY_REF_HPP_ -#define MULTI_ARRAY_REF_HPP_ -#pragma once - -#include "../multi/memory/pointer_traits.hpp" -#include "../multi/utility.hpp" - -#include "../multi/detail/adl.hpp" -#include "../multi/detail/layout.hpp" -#include "../multi/detail/memory.hpp" // for pointer_traits -#include "../multi/detail/operators.hpp" // for random_iterable -#include "../multi/detail/serialization.hpp" -#include "../multi/detail/types.hpp" // for dimensionality_type - -#if defined(__NVCC__) -#define HD __host__ __device__ -#else -#define HD -#endif - -#include // fpr copy_n -#include // for memset in reinterpret_cast -#include // for invoke -#include // for next -#include // for pointer_traits -#include // for forward - -#if not defined(__NVCC__) /*and not defined(__NVCOMPILER) and not defined(__INTEL_COMPILER)*/ - #define MULTI_NONV_CONSTEXPR constexpr // this generates a problem with intel compiler 19 and v2021 "a constexpr function cannot have a nonliteral return type" -#else - #define MULTI_NONV_CONSTEXPR /*constexpr*/ -#endif - -namespace boost::multi { - -template> -struct subarray; - -template -constexpr auto home(Array&& arr) -->decltype(std::forward(arr).home()) { - return std::forward(arr).home(); } - -template> struct array; - -template> -struct array_types : private Layout { // cppcheck-suppress syntaxError ; false positive in cppcheck - using element = T; - using element_type = element; // this follows more closely https://en.cppreference.com/w/cpp/memory/pointer_traits - - using element_ptr = ElementPtr; - using element_const_ptr = typename std::pointer_traits::template rebind; - using element_move_ptr = multi::move_ptr; - - using element_ref = typename std::iterator_traits::reference; - - using layout_t = Layout; - - using rank = typename layout_t::rank ; - - using layout_t::rank_v; - using layout_t::dimensionality; - - using typename layout_t::stride_type; - using layout_t::stride ; - - using layout_t::num_elements; - using layout_t::offset; - - using layout_t::offsets; - - using typename layout_t::index; - using typename layout_t::index_range; - using typename layout_t::index_extension; - - using typename layout_t::strides_type; - using layout_t::strides ; - - using typename layout_t::difference_type; - - using typename layout_t::size_type; - using layout_t::size ; - - using layout_t::nelems; - - using typename layout_t::extension_type; - using layout_t::extension; - - using typename layout_t::extensions_type; - using layout_t::extensions; - - constexpr auto extensions() const -> extensions_type {return static_cast(*this).extensions();} - - using layout_t::is_empty; - using layout_t:: empty; - - using layout_t::sub; - - using typename layout_t::sizes_type; - using layout_t::sizes; - - using layout_t::is_compact; - - friend constexpr auto size (array_types const& self) noexcept -> size_type {return self.size ();} - friend constexpr auto extension (array_types const& self) noexcept -> extension_type {return self.extension ();} - friend constexpr auto is_empty (array_types const& self) noexcept -> bool {return self.is_empty ();} - friend constexpr auto num_elements(array_types const& self) noexcept -> size_type {return self.num_elements();} - - friend constexpr auto extensions (array_types const& self) noexcept -> extensions_type {return self.extensions ();} - friend constexpr auto sizes (array_types const& self) noexcept -> sizes_type {return self.sizes ();} - - // TODO(correaa) [[deprecated("use member syntax for non-salient properties")]] - friend - constexpr auto stride (array_types const& self) noexcept -> stride_type {return self.stride ();} - - // TODO(correaa) [[deprecated("use member syntax for non-salient properties")]] - friend - constexpr auto strides (array_types const& self) noexcept -> strides_type {return self.strides ();} - - protected: - constexpr auto layout_mutable() -> layout_t& {return static_cast(*this);} - - public: - using value_type = typename std::conditional_t< - (D > 1), - array::default_allocator_type>, - element - >; - - using reference = typename std::conditional_t< - (D > 1), - subarray, - typename std::iterator_traits::reference - >; - - using const_reference = typename std::conditional_t< - (D > 1), - subarray, - typename std::iterator_traits::reference - >; - - HD constexpr auto base() const -> element_ptr {return base_;} - HD constexpr auto cbase() const -> element_const_ptr {return base_;} - HD constexpr auto mbase() const& -> element_ptr& {return base_;} - - friend /*constexpr*/ auto base(array_types const& self) -> element_ptr {return self.base();} - - HD constexpr auto layout() const -> layout_t const& {return *this;} - friend constexpr auto layout(array_types const& self) -> layout_t const& {return self.layout();} - - constexpr auto origin() const& -> decltype(auto) {return base_ + Layout::origin();} - friend constexpr auto origin(array_types const& self) -> decltype(auto) {return self.origin();} - - element_ptr base_; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes,misc-non-private-member-variables-in-classes) : TODO(correaa) try to make it private, [static_]array needs mutation - - protected: - using derived = subarray; - HD constexpr explicit array_types(std::nullptr_t nil) : Layout{}, base_{nil} {} - - public: - array_types() = default; - - HD constexpr array_types(layout_t const& lyt, element_ptr const& data) - : Layout{lyt}, base_{data} {} - - protected: - template< - class ArrayTypes, - typename = std::enable_if_t>{}> - , decltype(multi::explicit_cast(std::declval().base_))* = nullptr - > - // underlying pointers are explicitly convertible - HD constexpr explicit array_types(ArrayTypes const& other) - : Layout{other.layout()}, base_{other.base_} {} - - template< - class ArrayTypes, - typename = std::enable_if_t>{}>, - decltype(multi::implicit_cast(std::declval().base_))* = nullptr - > - // cppcheck-suppress noExplicitConstructor ; because underlying pointers are implicitly convertible - HD constexpr /*implt*/ array_types(ArrayTypes const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : inherit behavior of underlying pointer - : Layout{other.layout()}, base_{other.base_} {} - - template< - typename ElementPtr2, - typename = decltype(Layout{std::declval const&>().layout()}), - typename = decltype(element_ptr{std::declval const&>().base_}) - > - HD constexpr explicit array_types(array_types const& other) - : Layout{other.layout()}, base_{other.base_} {} - - template friend struct array_types; -}; - -template -struct subarray_ptr // NOLINT(fuchsia-multiple-inheritance) : to allow mixin CRTP -//: private Ref // TODO(correaa) : remove inheritance from Ref?? -: boost::multi::iterator_facade< - subarray_ptr, void, std::random_access_iterator_tag, - Ref const&, typename Layout::difference_type -> { //, boost::multi::totally_ordered2, void> -private: - mutable Ref ref_; - -public: - ~subarray_ptr() = default; // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - - HD constexpr auto operator=(subarray_ptr&& other) noexcept // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) // lints(hicpp-noexcept-move,performance-noexcept-move-constructor) - -> subarray_ptr& { - if(this == std::addressof(other)) {return *this;} // lints(cert-oop54-cpp) - this->ref_.base_ = other.ref_.base_; - // static_cast(*this) - this->ref_.layout_mutable() = other.ref_.layout(); - return *this; - } - - using pointer = Ref const*; - using element_type = typename Ref::decay_type; - using difference_type = typename Layout::difference_type; - - using value_type = element_type; - using reference = Ref; - using iterator_category = std::random_access_iterator_tag; - - // cppcheck-suppress noExplicitConstructor - HD constexpr subarray_ptr(std::nullptr_t nil) : ref_{nil} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) terse syntax and functionality by default - HD constexpr subarray_ptr() : subarray_ptr{nullptr} {} // TODO(correaa) consider uninitialized ptr - - template friend struct subarray_ptr; - - HD constexpr subarray_ptr(typename Ref::element_ptr base, layout_t lyt) : ref_{lyt, base} {} - HD constexpr subarray_ptr(typename Ref::element_ptr base, index_extensions exts) : ref_{base, exts} {} - template - // cppcheck-suppress noExplicitConstructor ; no information loss, allows comparisons - HD constexpr subarray_ptr(Array* other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - : subarray_ptr{other->data_elements(), other->layout()} {} - - subarray_ptr(subarray_ptr &&) noexcept = default; - subarray_ptr(subarray_ptr const& ) = default; - - HD constexpr auto operator=(subarray_ptr const& other) noexcept -> subarray_ptr& { - if(this == std::addressof(other)) {return *this;} // lints(cert-oop54-cpp) - this->ref_.base_ = other.ref_.base_; - // static_cast(*this) - this->ref_.layout_mutable() = other.ref_.layout(); - return *this; - } - HD constexpr explicit operator bool() const {return base();} - - HD constexpr auto dereference() const -> Ref {return Ref{this->layout(), this->base_};} - - HD constexpr auto operator* () const -> Ref {return ref_;} - - HD constexpr auto operator->() const -> Ref* {return std::addressof(ref_);} - // HD constexpr auto operator->() const -> Ref* {return const_cast(this);} // NOLINT(cppcoreguidelines-pro-type-const-cast) : TODO(correaa) find a better way without const_cast - // HD constexpr auto operator->() -> Ref* {return this;} - - HD constexpr auto operator[](difference_type n) const -> Ref {return *(*this + n);} - - HD constexpr auto operator<(subarray_ptr const& other) const -> bool {return distance_to(other) > 0;} - - HD constexpr subarray_ptr(typename Ref::element_ptr base, Layout const& lyt) : ref_{lyt, base} {} - - template - friend struct subarray; - - HD constexpr auto base() const -> typename Ref::element_ptr {return ref_.base();} - - friend HD constexpr auto base(subarray_ptr const& self) {return self.base();} - - constexpr auto operator==(subarray_ptr const& other) const -> bool { - return (this->ref_.base_ == other.ref_.base_) and (this->ref_.layout() == other.ref_.layout()); - } - - template >{}, int> =0> // TODO(correaa) improve this - friend HD constexpr auto operator==(subarray_ptr const& self, subarray_ptr const& other) -> bool {return self.base() == other->base() and self->layout() == other->layout();} - template >{}, int> =0> - friend HD constexpr auto operator!=(subarray_ptr const& self, subarray_ptr const& other) -> bool {return self.base() == other->base() and self->layout() == other->layout();} - - protected: - HD constexpr void increment() {ref_.base_ += Ref::nelems();} - HD constexpr void decrement() {ref_.base_ -= Ref::nelems();} - - HD constexpr void advance(difference_type n) {ref_.base_ += ref_.nelems()*n;} - HD constexpr auto distance_to(subarray_ptr const& other) const -> difference_type { - assert( Ref::nelems() == other.Ref::nelems() and Ref::nelems() != 0 ); - assert( (other.base() - base())%Ref::nelems() == 0); - assert( ref_.layout() == other.ref_.layout() ); - return (other.base() - base())/Ref::nelems(); - } - - public: - HD constexpr auto operator+=(difference_type n) -> subarray_ptr& {advance(n); return *this;} -}; - -template -struct array_iterator; - -template -struct array_iterator // NOLINT(fuchsia-multiple-inheritance) -: boost::multi::iterator_facade< - array_iterator, void, std::random_access_iterator_tag, - subarray const&, typename layout_t::difference_type -> -, multi::decrementable> -, multi::incrementable> -, multi::affine, multi::difference_type> -, multi::totally_ordered2, void> { - ~array_iterator() = default; // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - - constexpr auto operator=(array_iterator&&) // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - noexcept // lints(hicpp-noexcept-move,performance-noexcept-move-constructor) - -> array_iterator& = default; - - array_iterator(array_iterator&&) noexcept // lints(hicpp-noexcept-move,performance-noexcept-move-constructor) - = default; // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - - using difference_type = typename layout_t::difference_type; - using element = Element; - using element_ptr = ElementPtr; - using value_type = typename subarray::decay_type; - - using pointer = subarray*; - using reference = subarray; - - using iterator_category = std::random_access_iterator_tag; - - constexpr static dimensionality_type rank_v = D; - using rank = std::integral_constant; // TODO(correaa) make rank a function for compat with mdspan? - - using ptr_type = subarray_ptr, layout_t>; - using stride_type = index; - using layout_type = typename reference::layout_type; - - HD constexpr explicit array_iterator(std::nullptr_t nil) : ptr_{nil} {} //, stride_{1} - HD constexpr array_iterator() : array_iterator{nullptr} {} - - template friend struct array_iterator; - - template< - class EElement, typename PPtr, - decltype(multi::explicit_cast(std::declval>().base()))* = nullptr - > - HD constexpr explicit array_iterator(array_iterator const& other) - : ptr_{element_ptr{other.base()}, other.ptr_->layout()}, stride_{other.stride_} {} - - template(std::declval>().base()))* = nullptr - > - // cppcheck-suppress noExplicitConstructor ; because underlying pointer is implicitly convertible - HD constexpr/*mplct*/ array_iterator(array_iterator const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : propagate implicitness of pointer - : ptr_{element_ptr{other.ptr_->base()}, other.ptr_->layout()}, stride_{other.stride_} {} - - array_iterator(array_iterator const&) = default; - auto operator=(array_iterator const&) -> array_iterator& = default; - - HD constexpr explicit operator bool() const {return ptr_->base();} // TODO(correaa) implement bool conversion for subarray_ptr - HD constexpr auto operator*() const -> subarray {return {*ptr_};} - - HD constexpr auto operator->() const -> decltype(auto) {return ptr_;} - - HD constexpr auto operator+ (difference_type n) const -> array_iterator {array_iterator ret{*this}; ret += n; return ret;} - HD constexpr auto operator[](difference_type n) const -> subarray {return *((*this) + n);} - - friend HD constexpr auto operator==(array_iterator const& self, array_iterator const& other) -> bool { - return self.ptr_ == other.ptr_ and self.stride_== other.stride_ and self.ptr_->layout() == other.ptr_->layout(); - } - - HD constexpr auto operator< (array_iterator const& other) const -> bool { - assert(stride_ != 0); - return (0 < stride_)?(ptr_.base() < other.ptr_.base()):(other.ptr_.base() < ptr_.base()); - } - - HD constexpr explicit array_iterator(typename subarray::element_ptr base, layout_t lyt, index stride) - : ptr_{base, lyt}, stride_{stride} {} - - template friend struct subarray; - - template - HD constexpr auto operator()(index idx, As... args) const -> decltype(auto) {return this->operator[](idx)(args...); } - HD constexpr auto operator()(index idx) const -> decltype(auto) {return this->operator[](idx) ; } - - private: - template - static HD constexpr auto apply_impl(Self&& self, Tuple const& tuple, std::index_sequence/*012*/) -> decltype(auto) { - return std::forward(self)(std::get(tuple)...); - } - - public: - template HD constexpr auto apply(Tuple const& tpl) const& -> decltype(auto) { return apply_impl( *this , tpl, std::make_index_sequence::value>()); } - template HD constexpr auto apply(Tuple const& tpl) && -> decltype(auto) { return apply_impl(std::move(*this), tpl, std::make_index_sequence::value>()); } - template HD constexpr auto apply(Tuple const& tpl) & -> decltype(auto) { return apply_impl( *this , tpl, std::make_index_sequence::value>()); } - - private: - ptr_type ptr_; - stride_type stride_ = {1}; // nice non-zero default // TODO(correaa) use INT_MAX? - - HD constexpr void decrement() {ptr_->base_ -= stride_;} - HD constexpr void advance(difference_type n) {ptr_->base_ += stride_*n;} - - public: - HD constexpr auto base() const& -> element_ptr {return ptr_.base();} - friend /*constexpr*/ auto base(array_iterator const& self) -> element_ptr {return self.base();} - - HD constexpr auto stride() const& -> stride_type {return stride_;} - friend constexpr auto stride(array_iterator const& self) -> stride_type {return self.stride_;} - - constexpr auto operator++() -> array_iterator& {ptr_->base_ += stride_; return *this;} - constexpr auto operator--() -> array_iterator& {decrement(); return *this;} - - friend constexpr auto operator-(array_iterator const& self, array_iterator const& other) -> difference_type { - assert(self.stride_ == other.stride_); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) normal in a constexpr function - assert(self.stride_ != 0); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) normal in a constexpr function - return (self.ptr_.base() - other.ptr_.base())/self.stride_; - } - - constexpr auto operator+=(difference_type n) -> array_iterator& {advance(+n); return *this;} - constexpr auto operator-=(difference_type n) -> array_iterator& {advance(-n); return *this;} -}; - -template -struct cursor_t { - using difference_type = typename std::iterator_traits::difference_type; - using strides_type = StridesType; - - using element_ptr = ElementPtr; - using element_ref = typename std::iterator_traits::reference; - using element_type = typename std::iterator_traits::value_type; - - using pointer = element_ptr; - using reference = element_ref; - - using indices_type = typename extensions_t::indices_type; - - private: - strides_type strides_; - element_ptr base_; - - template friend struct subarray; - template friend struct cursor_t; - - HD constexpr cursor_t(element_ptr base, strides_type const& strides) : strides_{strides}, base_{base} {} - - public: - HD constexpr auto operator[](difference_type n) const -> decltype(auto) { - if constexpr(D != 1) { - return cursor_t< - ElementPtr, - D-1, - std::decay_t - >{ - base_ - + std::get<0>(strides_)*n, - strides_.tail() - }; - } else { - return base_[std::get<0>(strides_)*n]; - } - } - HD constexpr auto operator()(difference_type n) const -> decltype(auto) { - return operator[](n); - } - template - HD constexpr auto operator()(difference_type n, Ns... rest) const -> decltype(auto) { - return operator[](n)(rest...); - } - - private: - template - HD constexpr auto apply_impl(Tuple const& tup, std::index_sequence /*012*/) const -> decltype(auto) { - return ((std::get(tup)*std::get(strides_)) + ...); - } - - public: - template - HD constexpr auto operator+=(Tuple const& tup) -> cursor_t& { - base_ += apply_impl(tup, std::make_index_sequence::value>{}); - return *this; - } - HD constexpr auto operator* () const -> reference {return *base_;} - HD constexpr auto operator->() const -> pointer {return base_;} -}; - -template -struct elements_iterator_t // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) -: boost::multi::random_accessable, typename std::iterator_traits::difference_type, typename std::iterator_traits::reference> -{ - using difference_type = typename std::iterator_traits::difference_type; - using value_type = typename std::iterator_traits::value_type; - using pointer = Pointer; - using reference = typename std::iterator_traits::reference; - using iterator_category = std::random_access_iterator_tag; - - using const_pointer = typename std::pointer_traits::template rebind; - - using layout_type = LayoutType; - - private: - pointer base_; - layout_type l_; - difference_type n_ = 0; - extensions_t xs_; - - using indices_type = typename extensions_t::indices_type; - indices_type ns_ = {}; - - template friend struct elements_iterator_t; - template friend struct elements_range_t; - - constexpr elements_iterator_t(pointer base, layout_type lyt, difference_type n) - : base_{base}, l_{lyt}, n_{n}, xs_{l_.extensions()}, ns_{lyt.is_empty()?indices_type{}:xs_.from_linear(n)} {} - - public: - constexpr auto base() -> pointer {return base_;} - constexpr auto base() const -> const_pointer {return base_;} - HD constexpr auto layout() const -> layout_type {return l_;} - - template(std::declval().base_))* = nullptr> - // cppcheck-suppress noExplicitConstructor - HD constexpr /*impl*/ elements_iterator_t(Other const& other) : elements_iterator_t{other.base_, other.l_, other.n_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - template - HD constexpr explicit elements_iterator_t(Other const& other) : elements_iterator_t{other.base_, other.l_, other.n_} {} - - elements_iterator_t(elements_iterator_t const&) = default; - - HD constexpr auto operator++() -> elements_iterator_t& { - std::apply( [&xs = this->xs_](auto&... idxs){return xs.next_canonical(idxs...);}, ns_ ); - ++n_; - return *this; - } - HD constexpr auto operator--() -> elements_iterator_t& { - std::apply( [&xs = this->xs_](auto&... idxs) {return xs.prev_canonical(idxs...); }, ns_ ); - --n_; - return *this; - } - - HD constexpr auto operator+=(difference_type n) -> elements_iterator_t& { - auto const nn = std::apply(xs_, ns_); - ns_ = xs_.from_linear(nn + n); - n_ += n; - return *this; - } - HD constexpr auto operator-=(difference_type n) -> elements_iterator_t& { - auto const nn = std::apply(xs_, ns_); - ns_ = xs_.from_linear(nn - n); - n_ -= n; - return *this; - } - - HD /*[[gnu::pure]]*/ constexpr auto operator-(elements_iterator_t const& other) const -> difference_type { - assert(base_ == other.base_ and l_ == other.l_); - return n_ - other.n_; - } - HD constexpr auto operator<(elements_iterator_t const& other) const -> difference_type { - assert(base_ == other.base_ and l_ == other.l_); - return n_ < other.n_; - } - HD constexpr auto operator+(difference_type n) const -> elements_iterator_t {auto ret{*this}; ret += n; return ret;} // explicitly necessary for nvcc/thrust - HD constexpr auto operator-(difference_type n) const -> elements_iterator_t {auto ret{*this}; ret -= n; return ret;} // explicitly necessary for nvcc/thrust - - constexpr auto current() const -> pointer {return base_ + std::apply(l_, ns_);} - HD constexpr auto operator->() const -> pointer {return base_ + std::apply(l_, ns_) ;} - HD constexpr auto operator*() const -> reference {return base_ [std::apply(l_, ns_)];} - HD constexpr auto operator[](difference_type const& n) const -> reference { - auto const nn = std::apply(xs_, ns_); - return base_[std::apply(l_, xs_.from_linear(nn + n))]; - } // explicit here is necessary for nvcc/thrust - - HD constexpr auto operator==(elements_iterator_t const& other) const -> bool { - // assert(base_ == other.base_ and l_ == other.l_); // TODO(correaa) calling host function from host device - return n_ == other.n_; // and base_ == other.base_ and l_ == other.l_; - } - HD constexpr auto operator!=(elements_iterator_t const& other) const -> bool { - // assert(base_ == other.base_ and l_ == other.l_); // TODO(correaa) calling host function from host device - return n_ != other.n_; - } -}; - -template -struct elements_range_t { - using pointer = Pointer; - using layout_type = LayoutType; - - using value_type = typename std::iterator_traits::value_type; - using const_pointer = typename std::pointer_traits::template rebind; - - using reference = typename std::iterator_traits< pointer>::reference; - using const_reference = typename std::iterator_traits::reference; - - using size_type = typename std::iterator_traits::difference_type; - using difference_type = typename std::iterator_traits::difference_type; - - using iterator = elements_iterator_t; - using const_iterator = elements_iterator_t; - - private: - pointer base_; - layout_type l_; - - public: - template(std::declval().base_))* = nullptr> - // cppcheck-suppress noExplicitConstructor ; because underlying pointer is implicitly convertible // NOLINTNEXTLINE(runtime/explicit) - constexpr /*impl*/ elements_range_t(OtherRange const& other) : base_{other.base}, l_{other.l_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) to reproduce the implicitness of the argument - template(std::declval().base_))* = nullptr> - constexpr explicit elements_range_t(OtherRange const& other) : elements_range_t{other} {} - - constexpr elements_range_t(pointer base, layout_type lyt) : base_{base}, l_{lyt} {} - - private: - constexpr auto at_aux(difference_type n) const -> reference { - assert( not is_empty() ); - return base_[std::apply(l_, l_.extensions().from_linear(n))]; - } - - public: - HD constexpr auto operator[](difference_type n) const& -> const_reference {return at_aux(n);} - HD constexpr auto operator[](difference_type n) && -> reference {return at_aux(n);} - HD constexpr auto operator[](difference_type n) & -> reference {return at_aux(n);} - - constexpr auto size() const -> size_type {return l_.num_elements();} - - [[nodiscard]] - constexpr auto empty() const -> bool {return l_. empty();} - constexpr auto is_empty() const -> bool {return l_.is_empty();} - - elements_range_t(elements_range_t const&) = delete; - elements_range_t(elements_range_t &&) = delete; - - template auto operator==(elements_range_t const& other) const -> bool { - if( is_empty() and other.is_empty()) {return true;} - return size() == other.size() and adl_equal(other.begin(), other.end(), begin()); - } - template auto operator!=(elements_range_t const& other) const -> bool { - if(is_empty() and other.is_empty()) {return false;} - return size() != other.size() or not adl_equal(other.begin(), other.end(), begin()); - } - - template void swap(elements_range_t& other) & noexcept {assert(size() == other.size()); adl_swap_ranges(begin(), end(), other.begin());} - template void swap(elements_range_t& other) && noexcept {assert(size() == other.size()); adl_swap_ranges(begin(), end(), other.begin());} - template void swap(elements_range_t&& other) & noexcept {assert(size() == other.size()); adl_swap_ranges(begin(), end(), std::move(other).begin());} - template void swap(elements_range_t&& other) && noexcept {assert(size() == other.size()); adl_swap_ranges(begin(), end(), std::move(other).begin());} - - ~elements_range_t() = default; - - private: - constexpr auto begin_aux() const {return iterator{base_, l_, 0 };} - constexpr auto end_aux () const {return iterator{base_, l_, l_.num_elements()};} - - public: - constexpr auto begin() const& -> const_iterator {return begin_aux();} - constexpr auto end () const& -> const_iterator {return end_aux ();} - - constexpr auto begin() && -> iterator {return begin_aux();} - constexpr auto end () && -> iterator {return end_aux() ;} - - constexpr auto begin() & -> iterator {return begin_aux();} - constexpr auto end () & -> iterator {return end_aux() ;} - - constexpr auto front() const& -> const_reference {return *begin();} - constexpr auto back () const& -> const_reference {return *std::prev(end(), 1);} - - constexpr auto front() && -> reference {return *begin();} - constexpr auto back () && -> reference {return *std::prev(end(), 1);} - - constexpr auto front() & -> reference {return *begin();} - constexpr auto back () & -> reference {return *std::prev(end(), 1);} - - auto operator=(elements_range_t const&) -> elements_range_t& = delete; - auto operator=(elements_range_t &&) -> elements_range_t& = delete; - - template()), std::end(std::declval()), std::declval()))> - auto operator=(OtherElementRange&& other) & -> elements_range_t& {assert(size() == other.size()); - if(not is_empty()) {adl_copy(std::begin(other), std::end(other), begin());} - return *this; - } - - template()), std::end(std::declval()), std::declval()))> - auto operator=(OtherElementRange&& other) && -> elements_range_t& {assert(size() == other.size()); - if(not is_empty()) {adl_copy(std::begin(other), std::end(other), begin());} - return *this; - } - - auto operator=(std::initializer_list values) && -> elements_range_t& {operator=(values); return *this;} - auto operator=(std::initializer_list values) & -> elements_range_t& { - assert(static_cast(values.size()) == size()); - adl_copy_n(values.begin(), values.size(), begin()); - return *this; - } -}; - -template -HD constexpr auto ref(It begin, It end) -->multi::subarray { - return multi::subarray{begin, end}; -} - -template -struct subarray : array_types { - using types = array_types; - using ref_ = subarray; - - using array_types::rank_v; - - friend struct subarray; - friend struct subarray; - - using types::layout; - using typename types::element_type; - - using layout_type = Layout; - - HD constexpr auto layout() const -> layout_type {return array_types::layout();} - - using basic_const_array = subarray::template rebind, Layout>; - - subarray() = default; - - HD constexpr subarray(layout_type const& layout, ElementPtr const& base) - : array_types{layout, base} {} - - auto operator=(subarray&& other) noexcept(std::is_nothrow_copy_assignable_v) -> subarray& { // allows assigment in temporaries //NOSONAR - operator=(other); return *this; - } - - protected: - using types::types; - - template friend struct static_array; - subarray(subarray const&) = default; // NOTE: reference type cannot be copied. perhaps you want to return by std::move or std::forward if you got the object from a universal reference argument - - template friend struct subarray_ptr; - - public: - using element = typename types::element; - using element_ptr = typename types::element_ptr; - using element_const_ptr = typename types::element_const_ptr; - using element_move_ptr = multi::move_ptr; - using element_ref = typename types::element_ref; - using element_cref = typename std::iterator_traits::reference; - - using elements_iterator = elements_iterator_t; - using celements_iterator = elements_iterator_t; - - using elements_range = elements_range_t; - using const_elements_range = elements_range_t; - - private: - constexpr auto elements_aux() const {return elements_range{this->base(), this->layout()};} - - public: - subarray(subarray&&) noexcept = default; // lints(readability-redundant-access-specifiers) - - constexpr auto elements() & -> elements_range {return elements_aux();} - constexpr auto elements() && -> elements_range {return elements_aux();} - constexpr auto elements() const& -> const_elements_range {return const_elements_range{this->base(), this->layout()};} // TODO(correaa) simplify - constexpr auto const_elements() const -> const_elements_range {return elements_aux();} - - constexpr auto hull() const -> std::pair { - return {this->base(), std::abs(this->hull_size())}; - } - - ~subarray() = default; // this lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - - // in C++ < 17 this is necessary to return references from functions - friend constexpr auto sizes(subarray const& self) noexcept -> typename subarray::sizes_type {return self.sizes();} // needed by nvcc - friend constexpr auto size (subarray const& self) noexcept -> typename subarray::size_type {return self.size ();} // needed by nvcc - - template friend constexpr auto reinterpret_array_cast(subarray && self) {return std::move(self).template reinterpret_array_cast::template rebind>();} - template friend constexpr auto reinterpret_array_cast(subarray const& self) {return self .template reinterpret_array_cast::template rebind>();} - - friend constexpr auto dimensionality(subarray const& /*self*/) {return D;} - - using typename types::reference; - - using default_allocator_type = typename multi::pointer_traits::default_allocator_type; - - constexpr auto get_allocator() const -> default_allocator_type { - using multi::get_allocator; - return get_allocator(this->base()); - } - - friend MULTI_NONV_CONSTEXPR auto get_allocator(subarray const& self) -> default_allocator_type {return self.get_allocator();} - - using decay_type = array::default_allocator_type>; - - friend constexpr auto decay(subarray const& self) -> decay_type {return self.decay();} - constexpr auto decay() const& -> decay_type { - decay_type ret{*this}; - return ret; - } - - constexpr auto operator+() const -> decay_type {return decay();} - using typename types::const_reference; - - private: - HD constexpr auto at_aux(index idx) const { - return reference{ - this->layout().sub(), - this->base() + (idx*this->layout().stride() - this->layout().offset()) - }; // cppcheck-suppress syntaxError ; bug in cppcheck 2.5 - } - - public: - HD constexpr auto operator[](index idx) const& -> const_reference {return at_aux(idx);} - HD constexpr auto operator[](index idx) && -> reference {return at_aux(idx);} - HD constexpr auto operator[](index idx) & -> reference {return at_aux(idx);} - - template(D)>, typename = std::enable_if_t<(std::tuple_size::value > 1)> > - HD constexpr auto operator[](Tuple const& tup) const - ->decltype(operator[](std::get<0>(tup))[detail::tuple_tail(tup)]) { - return operator[](std::get<0>(tup))[detail::tuple_tail(tup)]; } - - template::value == 1)> > - HD constexpr auto operator[](Tuple const& tup) const - ->decltype(operator[](std::get<0>(tup))) { - return operator[](std::get<0>(tup)); } - - constexpr auto front() const& -> const_reference {return *begin();} - constexpr auto back() const& -> const_reference {return *std::prev(end(), 1);} - - constexpr auto front() && -> reference {return *begin();} - constexpr auto back() && -> reference {return *std::prev(end(), 1);} - - constexpr auto front() & -> reference {return *begin();} - constexpr auto back() & -> reference {return *std::prev(end(), 1);} - - using typename types::index; - - constexpr auto reindexed(index first) const& -> basic_const_array { - typename types::layout_t new_layout = this->layout(); - new_layout.reindex(first); - return {new_layout, types::base_}; - } - constexpr auto reindexed(index first)& -> subarray { - typename types::layout_t new_layout = this->layout(); - new_layout.reindex(first); - return {new_layout, types::base_}; - } - constexpr auto reindexed(index first)&& -> subarray { - typename types::layout_t new_layout = this->layout(); - new_layout.reindex(first); - return {new_layout, types::base_}; - } - - // TODO(correaa) : implement reindexed_aux - template - constexpr auto reindexed(index first, Indexes... idxs) const& -> basic_const_array { - return ((reindexed(first).rotated()).reindexed(idxs...)).unrotated(); - } - template - constexpr auto reindexed(index first, Indexes... idxs) & -> subarray { - return ((reindexed(first).rotated()).reindexed(idxs...)).unrotated(); - } - template - constexpr auto reindexed(index first, Indexes... idxs)&& -> subarray { - return ((std::move(*this).reindexed(first).rotated()).reindexed(idxs...)).unrotated(); - } - private: - constexpr auto take_aux(difference_type n) const { - assert( n <= this->size() ); - typename types::layout_t const new_layout{ - this->layout().sub(), - this->layout().stride(), - this->layout().offset(), - this->stride()*n - }; - return subarray{new_layout, this->base()}; - } - - public: - constexpr auto take(difference_type n) const& -> basic_const_array {return take_aux(n);} - constexpr auto take(difference_type n) && -> subarray {return take_aux(n);} - constexpr auto take(difference_type n) & -> subarray {return take_aux(n);} - - private: - constexpr auto drop_aux(difference_type n) const { - assert( n <= this->size() ); - typename types::layout_t const new_layout{ - this->layout().sub(), - this->layout().stride(), - this->layout().offset(), - this->stride()*(this->size() - n) - }; - return subarray{new_layout, this->base() + n*this->layout().stride() - this->layout().offset()}; - } - - public: - constexpr auto drop(difference_type n) const& -> basic_const_array {return drop_aux(n);} - constexpr auto drop(difference_type n) && -> subarray {return drop_aux(n);} - constexpr auto drop(difference_type n) & -> subarray {return drop_aux(n);} - - private: - HD constexpr auto sliced_aux(index first, index last) const { - MULTI_ACCESS_ASSERT(((first==last) or this->extension().contains(first ))&&"sliced first out of bounds"); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - MULTI_ACCESS_ASSERT(((first==last) or this->extension().contains(last - 1))&&"sliced last out of bounds"); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - typename types::layout_t new_layout = this->layout(); - new_layout.nelems() = this->stride()*(last - first); // TODO(correaa) : reconstruct layout instead of mutating it - return subarray{new_layout, this->base() + (first*this->layout().stride() - this->layout().offset())}; - } - - public: - HD constexpr auto sliced(index first, index last) const& -> basic_const_array {return sliced_aux(first, last);} - HD constexpr auto sliced(index first, index last) & -> subarray {return sliced_aux(first, last);} - HD constexpr auto sliced(index first, index last) && -> subarray {return sliced_aux(first, last);} - - constexpr auto blocked(index first, index last) const& -> basic_const_array {return sliced(first, last).reindexed(first);} - constexpr auto blocked(index first, index last) & -> subarray {return sliced(first, last).reindexed(first);} - - using iextension = typename subarray::index_extension; - - constexpr auto stenciled(iextension iex) & -> subarray{return blocked(iex.start(), iex.finish());} - constexpr auto stenciled(iextension iex, iextension iex1) & -> subarray{return ((stenciled(iex).rotated()).stenciled(iex1)).unrotated();} - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2) & -> subarray{return ((stenciled(iex).rotated()).stenciled(iex1, iex2)).unrotated();} - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3) & -> subarray{return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3)).unrotated();} - template - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3, Xs... iexs) & -> subarray{return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3, iexs...)).unrotated();} - - constexpr auto stenciled(iextension iex) && -> subarray{return blocked(iex.start(), iex.finish());} - constexpr auto stenciled(iextension iex, iextension iex1) && -> subarray{return ((stenciled(iex).rotated()).stenciled(iex1)).unrotated();} - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2) && -> subarray{return ((stenciled(iex).rotated()).stenciled(iex1, iex2)).unrotated();} - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3) && -> subarray{return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3)).unrotated();} - template - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3, Xs... iexs) && -> subarray{return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3, iexs...)).unrotated();} - - constexpr auto stenciled(iextension iex) const& -> basic_const_array {return blocked(iex.start(), iex.finish());} - constexpr auto stenciled(iextension iex, iextension iex1) const& -> basic_const_array {return ((stenciled(iex).rotated()).stenciled(iex1)).unrotated();} - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2) const& -> basic_const_array {return ((stenciled(iex).rotated()).stenciled(iex1, iex2)).unrotated();} - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3) const& -> basic_const_array {return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3)).unrotated();} - - template - constexpr auto stenciled(iextension iex, iextension iex1, iextension iex2, iextension iex3, Xs... iexs) const& -> basic_const_array { - return ((stenciled(iex).rotated()).stenciled(iex1, iex2, iex3, iexs...)).unrotated(); - } - - constexpr auto elements_at(size_type idx) const& -> decltype(auto) { - assert(idx < this->num_elements()); - auto const sub_num_elements = this->begin()->num_elements(); - return operator[](idx / sub_num_elements).elements_at(idx % sub_num_elements); - } - constexpr auto elements_at(size_type idx) && -> decltype(auto) { - assert(idx < this->num_elements()); - auto const sub_num_elements = this->begin()->num_elements(); - return operator[](idx / sub_num_elements).elements_at(idx % sub_num_elements); - } - constexpr auto elements_at(size_type idx) & -> decltype(auto) { - assert(idx < this->num_elements()); - auto const sub_num_elements = this->begin()->num_elements(); - return operator[](idx / sub_num_elements).elements_at(idx % sub_num_elements); - } - - private: - constexpr auto strided_aux(difference_type diff) const -> subarray { - typename types::layout_t const new_layout{this->layout().sub(), this->layout().stride()*diff, this->layout().offset(), this->layout().nelems()}; - return {new_layout, types::base_}; - } - - public: - constexpr auto strided(difference_type diff) const& -> basic_const_array {return strided_aux(diff);} - constexpr auto strided(difference_type diff) && -> subarray {return strided_aux(diff);} - constexpr auto strided(difference_type diff) & -> subarray {return strided_aux(diff);} - - constexpr auto sliced( - typename types::index first, typename types::index last, typename types::index stride_ - ) const -> subarray { - return sliced(first, last).strided(stride_); - } - - using index_range = typename subarray::index_range; - - constexpr auto range(index_range irng) const& -> decltype(auto) {return sliced(irng.front(), irng.front() + irng.size());} - constexpr auto range(index_range irng) && -> decltype(auto) {return std::move(*this).sliced(irng.front(), irng.front() + irng.size());} - constexpr auto range(index_range irng) & -> decltype(auto) {return sliced(irng.front(), irng.front() + irng.size());} - - constexpr auto is_flattable() const -> bool{return this->stride() == this->layout().sub().nelems();} - - friend constexpr auto flatted(subarray const& self) {return self.flatted();} - constexpr auto flatted() const& { - assert(is_flattable() && "flatted doesn't work for all layouts!"); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - multi::layout_t new_layout{this->layout().sub()}; - new_layout.nelems() *= this->size(); // TODO(correaa) : use immutable layout - return subarray{new_layout, types::base_}; - } - - void flattened() const = delete; - // { - // multi::biiteratorbegin())>> biit{this->begin(), 0, size(*(this->begin()))}; - // return basic_array(this->layout().sub, biit); - // } - - // TODO(correaa) : define a diagonal_aux - constexpr auto diagonal() && {return this->diagonal();} - - constexpr auto diagonal() & -> subarray { - using boost::multi::detail::get; - auto square_size = std::min(get<0>(this->sizes()), get<1>(this->sizes())); - multi::layout_t new_layout{(*this)({0, square_size}, {0, square_size}).layout().sub()}; - new_layout.nelems() += (*this)({0, square_size}, {0, square_size}).layout().nelems(); // TODO(correaa) : don't use mutation - new_layout.stride() += (*this)({0, square_size}, {0, square_size}).layout().stride(); // TODO(correaa) : don't use mutation - return {new_layout, types::base_}; - } - - template 1) and sizeof(Dummy*), int> =0> - constexpr auto diagonal() const& -> subarray { - auto square_size = std::min(std::get<0>(this->sizes()), std::get<1>(this->sizes())); - multi::layout_t new_layout{(*this)({0, square_size}, {0, square_size}).layout().sub()}; - new_layout.nelems() += (*this)({0, square_size}, {0, square_size}).layout().nelems(); - new_layout.stride() += (*this)({0, square_size}, {0, square_size}).layout().stride(); // cppcheck-suppress arithOperationsOnVoidPointer ; false positive D == 1 doesn't happen here - return {new_layout, types::base_}; - } - - friend constexpr auto diagonal(subarray const& self) {return self .diagonal();} - friend constexpr auto diagonal(subarray& self) {return self .diagonal();} - friend constexpr auto diagonal(subarray&& self) {return std::move(self).diagonal();} - - using partitioned_type = subarray; - using partitioned_const_type = subarray; - - private: - HD constexpr auto partitioned_aux(size_type n) const -> partitioned_type { - assert(n != 0); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - // vvv TODO(correaa) should be size() here? - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) normal in a constexpr function - assert( (this->layout().nelems() % n) == 0); // if you get an assertion here it means that you are partitioning an array with an incommunsurate partition - multi::layout_t new_layout{this->layout(), this->layout().nelems()/n, 0, this->layout().nelems()}; - new_layout.sub().nelems() /= n; - return {new_layout, types::base_}; - } - - public: - HD constexpr auto partitioned(size_type n) const& -> partitioned_const_type {return partitioned_aux(n);} - HD constexpr auto partitioned(size_type n) & -> partitioned_type {return partitioned_aux(n);} - HD constexpr auto partitioned(size_type n) && -> partitioned_type {return partitioned_aux(n);} - - friend HD constexpr auto partitioned(subarray const& self, size_type n) -> partitioned_const_type {return self .partitioned(n);} - friend HD constexpr auto partitioned(subarray & self, size_type n) -> partitioned_type {return self .partitioned(n);} - friend HD constexpr auto partitioned(subarray && self, size_type n) -> partitioned_type {return std::move(self).partitioned(n);} - - private: - HD constexpr auto chunked_aux(size_type count) const -> partitioned_type { - assert( this->size() % count == 0 ); - return partitioned_aux(this->size()/count); - } - - public: // in Mathematica this is called Partition https://reference.wolfram.com/language/ref/Partition.html in RangesV3 it is called chunk - HD constexpr auto chunked(size_type count) const& -> partitioned_const_type {return chunked_aux(count);} - HD constexpr auto chunked(size_type count) & -> partitioned_type {return chunked_aux(count);} - HD constexpr auto chunked(size_type count) && -> partitioned_type {return chunked_aux(count);} - - private: - constexpr auto reversed_aux() const -> subarray { - auto new_layout = this->layout(); - new_layout.reverse(); - return {new_layout, types::base_}; - } - - public: - constexpr auto reversed() const& -> basic_const_array {return reversed_aux();} - constexpr auto reversed() & -> subarray {return reversed_aux();} - constexpr auto reversed() && -> subarray {return reversed_aux();} - friend constexpr auto reversed(subarray const& self) -> basic_const_array {return self .reversed();} - friend constexpr auto reversed(subarray & self) -> subarray {return self .reversed();} - friend constexpr auto reversed(subarray && self) -> subarray {return std::move(self).reversed();} - - private: - HD constexpr auto transposed_aux() const -> subarray { - auto new_layout = this->layout(); - new_layout.transpose(); - return {new_layout, types::base_}; - } - - public: - HD constexpr auto transposed() const& -> basic_const_array {return transposed_aux();} - HD constexpr auto transposed() & -> subarray {return transposed_aux();} - HD constexpr auto transposed() && -> subarray {return transposed_aux();} - - friend HD /*constexpr*/ auto transposed(subarray const& self) -> basic_const_array {return self.transposed();} - friend HD /*constexpr*/ auto transposed(subarray & self) -> subarray {return self.transposed();} - friend HD /*constexpr*/ auto transposed(subarray && self) -> subarray {return std::move(self).transposed();} - - friend HD MULTI_NONV_CONSTEXPR - auto operator~ (subarray const& self) -> basic_const_array {return self.transposed();} - friend HD MULTI_NONV_CONSTEXPR - auto operator~ (subarray& self) -> subarray {return self.transposed();} - friend HD MULTI_NONV_CONSTEXPR - auto operator~ (subarray&& self) -> subarray {return std::move(self).transposed();} - - HD constexpr auto rotated() & -> subarray { - typename types::layout_t new_layout = this->layout(); - new_layout.rotate(); - return subarray{new_layout, types::base_}; - } - HD constexpr auto rotated() && -> subarray { - typename types::layout_t new_layout = this->layout(); - new_layout.rotate(); - return subarray{new_layout, types::base_}; - } - HD constexpr auto rotated() const& -> basic_const_array { - typename types::layout_t new_layout = this->layout(); - new_layout.rotate(); - typename basic_const_array::element_ptr const new_base_{types::base_}; - return basic_const_array{new_layout, new_base_}; - } - - friend constexpr auto rotated(subarray const& self) -> basic_const_array {return self .rotated();} - friend constexpr auto rotated(subarray & self) -> subarray {return self .rotated();} - friend /*constexpr*/ auto rotated(subarray && self) -> subarray {return std::move(self).rotated();} - - HD constexpr auto unrotated() & { - typename types::layout_t new_layout = this->layout(); - new_layout.unrotate(); - return subarray{new_layout, types::base_}; - } - HD constexpr auto unrotated() && { - typename types::layout_t new_layout = this->layout(); - new_layout.unrotate(); - return subarray{new_layout, types::base_}; - } - HD constexpr auto unrotated() const& { - typename types::layout_t new_layout = this->layout(); - new_layout.unrotate(); - return basic_const_array{new_layout, types::base_}; - } - friend constexpr auto unrotated(subarray const& self) {return self.unrotated();} - - constexpr auto operator|(typename subarray::size_type n) & -> decltype(auto) {return partitioned(n);} - constexpr auto operator|(typename subarray::size_type n) && -> decltype(auto) {return std::move(*this).partitioned(n);} - constexpr auto operator|(typename subarray::size_type n) const& -> decltype(auto) {return partitioned(n);} - - HD constexpr auto operator()() & -> subarray {return *this;} - HD constexpr auto operator()() && -> subarray {return this->operator()();} - HD constexpr auto operator()() const& -> basic_const_array {return {this->layout(), this->base()};} - - private: - template friend struct subarray; - - HD constexpr auto paren_aux() & -> subarray {return *this;} - HD constexpr auto paren_aux() && -> subarray {return this->operator()();} - HD constexpr auto paren_aux() const& -> basic_const_array {return {this->layout(), this->base()};} - - template - constexpr auto paren_aux(index_range irng, As... args) & { - // TODO(correaa) investigate how to make it HD - // return range(a).rotated().paren_aux(as...).unrotated(); // TODO(correaa) compact - // auto&& tmp = range(irng); - // auto&& tmp2 = - // std::move(tmp). - // rotated(); - // auto&& tmp3 = std::move(tmp2).paren_aux(args...); - // auto&& ret = std::move(tmp3).unrotated(); - // return std::move(tmp3).unrotated(); // std::move(ret); - return range(irng).rotated().paren_aux(args...).unrotated(); // std::move(ret); - } - template - constexpr auto paren_aux(index_range irng, As... args) && { - // TODO(correaa) investigate how to make it HD - // auto&& tmp = std::move(*this).range(irng); - // auto&& tmp2 = std::move(tmp).rotated().paren_aux(args...); - // return std::move(tmp2).unrotated(); - return std::move(*this).range(irng).rotated().paren_aux(args...).unrotated(); - } - template constexpr auto paren_aux(index_range rng, As... args) const& {return range(rng).rotated().paren_aux(args...).unrotated();} - - template constexpr auto paren_aux(intersecting_range inr, As... args) & -> decltype(auto) {return paren_aux(intersection(this->extension(), inr), args...);} - template constexpr auto paren_aux(intersecting_range inr, As... args) && -> decltype(auto) {return paren_aux(intersection(this->extension(), inr), args...);} - template constexpr auto paren_aux(intersecting_range inr, As... args) const& -> decltype(auto) {return paren_aux(intersection(this->extension(), inr), args...);} - - template HD constexpr auto paren_aux(index idx, As... args) & -> decltype(auto) {return operator[](idx).paren_aux(args...);} - template HD constexpr auto paren_aux(index idx, As... args) && -> decltype(auto) {return operator[](idx).paren_aux(args...);} - template HD constexpr auto paren_aux(index idx, As... args) const& -> decltype(auto) {return operator[](idx).paren_aux(args...);} - - public: - // vvv DO NOT remove default parameter `= irange` : the default template parameters below help interpret the expression `{first, last}` syntax as index ranges - template constexpr auto operator()(A1 arg1) const& -> decltype(auto) {return paren_aux(arg1);} // NOLINT(whitespace/line_length) pattern line - template constexpr auto operator()(A1 arg1, A2 arg2) const& -> decltype(auto) {return paren_aux(arg1, arg2);} // NOLINT(whitespace/line_length) pattern line - template constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3) const& -> decltype(auto) {return paren_aux(arg1, arg2, arg3);} // NOLINT(whitespace/line_length) pattern line - template constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) const& -> decltype(auto) {return paren_aux(arg1, arg2, arg3, arg4, args...);} // NOLINT(whitespace/line_length) pattern line - - template constexpr auto operator()(A1 arg1) & -> decltype(auto) {return paren_aux(arg1);} // NOLINT(whitespace/line_length) pattern line - template constexpr auto operator()(A1 arg1, A2 arg2) & -> decltype(auto) {return paren_aux(arg1, arg2);} // NOLINT(whitespace/line_length) pattern line - template /*[[gnu::pure]]*/ constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3) & -> decltype(auto) {return paren_aux(arg1, arg2, arg3);} // NOLINT(whitespace/line_length) pattern line - template constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) & -> decltype(auto) {return paren_aux(arg1, arg2, arg3, arg4, args...);} // NOLINT(whitespace/line_length) pattern line - - template constexpr auto operator()(A1 arg1) && -> decltype(auto) {return std::move(*this).paren_aux(arg1);} // NOLINT(whitespace/line_length) pattern line - template HD constexpr auto operator()(A1 arg1, A2 arg2) && -> decltype(auto) {return std::move(*this).paren_aux(arg1, arg2);} // NOLINT(whitespace/line_length) pattern line - template constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3) && -> decltype(auto) {return std::move(*this).paren_aux(arg1, arg2, arg3);} // NOLINT(whitespace/line_length) pattern line - template constexpr auto operator()(A1 arg1, A2 arg2, A3 arg3, A4 arg4, As... args) && -> decltype(auto) {return std::move(*this).paren_aux(arg1, arg2, arg3, arg4, args...);} // NOLINT(whitespace/line_length) pattern line - - private: - template constexpr auto apply_impl(Tuple const& tuple, std::index_sequence/*012*/) const& -> decltype(auto) {return this->operator()(std::get(tuple)...);} - template constexpr auto apply_impl(Tuple const& tuple, std::index_sequence/*012*/) & -> decltype(auto) {return this->operator()(std::get(tuple)...);} - template constexpr auto apply_impl(Tuple const& tuple, std::index_sequence/*012*/) && -> decltype(auto) {return std::move(*this).operator()(std::get(tuple)...);} - - public: - template constexpr auto apply(Tuple const& tuple) const& -> decltype(auto) {return apply_impl(tuple, std::make_index_sequence::value>());} - template constexpr auto apply(Tuple const& tuple) && -> decltype(auto) {return apply_impl(tuple, std::make_index_sequence::value>());} - template constexpr auto apply(Tuple const& tuple) & -> decltype(auto) {return apply_impl(tuple, std::make_index_sequence::value>());} - - using iterator = array_iterator; - using const_iterator = array_iterator; - using move_iterator = array_iterator; - - private: - HD constexpr explicit subarray(iterator begin, iterator end) - : subarray{ - layout_type{begin->layout(), begin.stride(), 0, begin.stride()*(end - begin)}, - begin.base() - } { - assert(begin.stride() == end.stride() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - assert(begin->layout() == end->layout()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - } - friend HD constexpr auto ref(iterator begin, iterator end) -> multi::subarray; - - public: - using ptr = subarray_ptr; - using const_ptr = subarray_ptr; - - constexpr auto addressof() && {return ptr{this->base_, this->layout()};} - - // NOLINTNEXTLINE(runtime/operator) //NOSONAR - constexpr auto operator&() && {return ptr {this->base_, this->layout()};} // NOLINT(google-runtime-operator) //NOSONAR - // NOLINTNEXTLINE(runtime/operator) //NOSONAR - constexpr auto operator&() & {return ptr {this->base_, this->layout()};} // NOLINT(google-runtime-operator) //NOSONAR - // NOLINTNEXTLINE(runtime/operator) //NOSONAR - constexpr auto operator&() const& {return const_ptr {this->base_, this->layout()};} // NOLINT(google-runtime-operator) //NOSONAR - - private: - HD constexpr auto begin_aux() const {return iterator{types::base_ , this->sub(), this->stride()};} - constexpr auto end_aux () const {return iterator{types::base_ + this->nelems(), this->sub(), this->stride()};} - - public: - HD constexpr auto begin() & {return begin_aux();} - constexpr auto end () & {return end_aux() ;} - friend HD /*constexpr*/ auto begin(subarray & self) {return self.begin();} - friend constexpr auto end (subarray & self) {return self.end ();} - - constexpr auto begin() && {return begin();} - constexpr auto end () && {return end() ;} - friend /*constexpr*/ auto begin(subarray && self) {return std::move(self).begin();} - friend /*constexpr*/ auto end (subarray && self) {return std::move(self).end() ;} - - constexpr auto begin() const& -> const_iterator {return begin_aux();} - constexpr auto end () const& -> const_iterator {return end_aux() ;} - friend /*constexpr*/ auto begin(subarray const& self) -> const_iterator { return self.begin(); } // NOLINT(whitespace/indent) constexpr doesn't work with nvcc friend - friend /*constexpr*/ auto end (subarray const& self) -> const_iterator { return self.end() ; } // NOLINT(whitespace/indent) constexpr doesn't work with nvcc friend - - HD constexpr auto cbegin() const& {return begin();} - /*fd*/ constexpr auto cend() const& {return end() ;} - friend constexpr auto cbegin(subarray const& self) {return self.cbegin();} - friend constexpr auto cend (subarray const& self) {return self.cend() ;} - - constexpr auto mbegin() & { return move_iterator{begin()}; } - constexpr auto mend() & { return move_iterator{end() }; } - friend constexpr auto mbegin(subarray & self) { return self.mbegin(); } - friend constexpr auto mend (subarray & self) { return self.mend() ; } - - constexpr auto mbegin() && {return mbegin();} - constexpr auto mend() && {return mend() ;} - friend constexpr auto mbegin(subarray && self) {return std::move(self).mbegin();} - friend constexpr auto mend (subarray && self) {return std::move(self).mend() ;} - - constexpr auto mbegin() const& -> const_iterator {return begin();} - constexpr auto mend() const& -> const_iterator {return end() ;} - friend constexpr auto mbegin(subarray const& self) {return self.mbegin();} - friend constexpr auto mend (subarray const& self) {return self.mend() ;} - - private: - constexpr auto home_aux() const -> cursor_t { - return {this->base(), this->strides()}; - } - - public: - constexpr auto home() const& -> cursor_t {return home_aux();} - constexpr auto home() && -> cursor_t {return home_aux();} - constexpr auto home() & -> cursor_t {return home_aux();} - - template constexpr auto assign(It first) & -> It {adl_copy_n(first, this->size(), begin()); std::advance(first, this->size()); return first;} - template constexpr auto assign(It first)&& -> It {return assign(first);} - - template< - class Range, - class = std::enable_if_t>, - class = decltype(adl_copy_n(adl_begin(std::declval()), std::declval(), std::declval())) - > - constexpr auto operator=(Range const& rng) & // check that you LHS is not read-only - -> subarray& { // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - assert(this->size() == rng.size()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - // MULTI_MARK_SCOPE(std::string{"multi::operator= D="}+std::to_string(D)+" from range to "+typeid(T).name() ); - // adl_copy_n(adl_begin(r), this->size(), begin()); - adl_copy(adl_begin(rng), adl_end(rng), begin()); - return *this; - } - template>> - constexpr auto operator=(Range const& rng) && -> subarray& {operator=(rng); return *this;} - - template - constexpr auto operator=(subarray const& other) && -> subarray& {operator=(other); return *this;} - - template - constexpr - auto operator=(subarray const& other) & -> subarray& { - assert(this->extension() == other.extension()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - // MULTI_MARK_SCOPE( std::string{"multi::operator= (D="}+std::to_string(D)+") from "+typeid(TT).name()+" to "+typeid(T).name() ); - this->elements() = other.elements(); -// if(this->is_empty()) {return *this;} -// if(this->num_elements() == this->nelems() and o.num_elements() == this->nelems() and this->layout() == o.layout()) { -// this->elements() = o.elements(); -//// adl_copy_n(o.base(), o.num_elements(), this->base()); -// } else if(o.stride() < (~o).stride()) { -// (~(*this)).elements() = o.elements(); -//// adl_copy_n( (~o).begin(), (~o).size(), (~(*this)).begin() ); -// } else { -// assign(o.begin()); -// } - return *this; - } - - constexpr - auto operator=(subarray const& other) & -> subarray& { - if(this == std::addressof(other)) {return *this;} // lints(cert-oop54-cpp) - assert(this->extension() == other.extension()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - // MULTI_MARK_SCOPE("multi::operator= [D="+std::to_string(D)+"] from "+typeid(T).name()+" to "+typeid(T).name() ); - elements() = other.elements(); -// if(this->num_elements() == this->nelems() and o.num_elements() == this->nelems() and this->layout() == o.layout()) { -// adl_copy_n(o.base(), o.num_elements(), this->base()); -// } else if(o.stride() < (~o).stride()) { -// adl_copy_n( (~o).begin(), (~o).size(), (~(*this)).begin() ); -// } else { -// assign(o.begin()); -// } - return *this; - } - - constexpr auto operator=(subarray const& other) && - -> subarray& { // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - if(this == std::addressof(other)) {return *this;} // lints(cert-oop54-cpp) - operator=(other); - return *this; // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - } - - template< - class Range, - std::enable_if_t>::value, int> =0, - // std::enable_if_t, int> =0, - class = decltype(Range(std::declval(), std::declval())) - > - constexpr explicit operator Range() const & {return Range(begin(), end());} // NOLINT(fuchsia-default-arguments-calls) for example std::vector(it, ti, alloc = {}) - - template constexpr void swap(Array&& other) && noexcept { - assert( std::move(*this).extension() == std::forward(other).extension() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - elements().swap(other.elements()); - // adl_swap_ranges(this->begin(), this->end(), adl_begin(std::forward(o))); - } - template constexpr void swap(A&& other) & noexcept {return swap(std::forward(other));} - - friend constexpr void swap(subarray&& self, subarray&& other) noexcept {std::move(self).swap(std::move(other));} - - template constexpr void swap(subarray const& self, Array&& other) {self.swap(std::forward(other));} // TODO(correaa) remove - template constexpr void swap(Array&& other, subarray const& self) {self.swap(std::forward(other));} - - template - friend constexpr auto operator==(subarray const& self, subarray const& other) -> bool { - return (self.extension() == other.extension()) and (self.elements() == other.elements()); - } - template - friend constexpr auto operator!=(subarray const& self, subarray const& other) -> bool { - return (self.extension() != other.extension()) or (self.elements() != other.elements()); - } - - constexpr auto operator==(subarray const& other) const -> bool { - return (this->extension() == other.extension()) and (this->elements() == other.elements()); - } - constexpr auto operator!=(subarray const& other) const -> bool { - return (this->extension() != other.extension()) or (this->elements() != other.elements()); - } - - private: - friend constexpr auto lexicographical_compare(subarray const& self, subarray const& other) -> bool { - if(self.extension().first() > other.extension().first()) {return true ;} - if(self.extension().first() < other.extension().first()) {return false;} - return adl_lexicographical_compare( - self.begin(), self.end(), - other.begin(), other.end() - ); - } - - public: - /*[[gnu::pure]]*/ constexpr auto operator< (subarray const& other) const& -> bool {return lexicographical_compare(*this, other);} - /*[[gnu::pure]]*/ constexpr auto operator<=(subarray const& other) const& -> bool {return *this == other or lexicographical_compare(*this, other);} - constexpr auto operator> (subarray const& other) const& -> bool {return other < *this;} - - template::template rebind> - constexpr auto static_array_cast() const -> subarray { - P2 p2{this->base_}; - return subarray{this->layout(), p2}; - } - template::template rebind, class... Args> - constexpr auto static_array_cast(Args&&... args) const -> subarray { // name taken from std::static_pointer_cast - return {this->layout(), P2{this->base(), std::forward(args)...}}; - } - - template - constexpr auto element_transformed(UF&& fun) const& { - return static_array_cast< - // std::remove_cv_t>>, - std::decay_t>, - transform_ptr< - // std::remove_cv_t>>, - std::decay_t>, - UF, element_const_ptr, std::invoke_result_t - > - >(std::forward(fun)); - } - template - constexpr auto element_transformed(UF&& fun) & { - return static_array_cast< - // std::remove_cv_t>>, - std::decay_t>, - transform_ptr< - // std::remove_cv_t>>, - std::decay_t>, - UF, element_ptr , std::invoke_result_t - > - >(std::forward(fun)); - } - template - constexpr auto element_transformed(UF&& fun) && {return element_transformed(std::forward(fun));} - - template< - class T2, class P2 = typename std::pointer_traits::template rebind, - class Element = typename subarray::element, - class PM = T2 Element::* - > - constexpr auto member_cast(PM member) const& -> subarray { - static_assert(sizeof(T)%sizeof(T2) == 0, - "array_member_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. " - "Use custom alignas structures (to the interesting member(s) sizes) or custom pointers to allow reintrepreation of array elements." - ); - - return subarray{this->layout().scale(sizeof(T)/sizeof(T2)), static_cast(&(this->base_->*member))}; - } - - template< - class T2, class P2 = typename std::pointer_traits::template rebind, - class Element = typename subarray::element, - class PM = T2 Element::* - > - constexpr auto member_cast(PM member) & -> subarray { - static_assert(sizeof(T)%sizeof(T2) == 0, - "array_member_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. " - "Use custom alignas structures (to the interesting member(s) sizes) or custom pointers to allow reintrepreation of array elements" - ); - - return subarray{this->layout().scale(sizeof(T)/sizeof(T2)), static_cast(&(this->base_->*member))}; - } - - template< - class T2, class P2 = typename std::pointer_traits::template rebind, - class Element = typename subarray::element, - class PM = T2 Element::* - > - constexpr auto member_cast(PM member) && -> subarray { - return this->member_cast(member); - } - - template::template rebind> - using rebind = subarray, D, P2>; - - template, class P2 = typename std::pointer_traits::template rebind> - constexpr auto const_array_cast() && -> rebind { - return {this->layout(), const_cast(this->base())}; // NOLINT(cppcoreguidelines-pro-type-const-cast) : to implement consts cast - } - - constexpr auto as_const() const { - return rebind{this->layout(), this->base()}; - } - constexpr auto moved() & {return rebind{this->layout(), element_move_ptr{this->base()}};} - constexpr auto moved() && {return moved();} - - constexpr auto element_moved() & {return rebind{this->layout(), element_move_ptr{this->base()}};} - constexpr auto element_moved() && {return element_moved();} - - private: - template - constexpr auto reinterpret_array_cast_aux() const -> rebind { - static_assert( sizeof(T)%sizeof(T2) == 0, - "error: reinterpret_array_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. Use custom pointers to allow reintrepreation of array elements in other cases" ); - - return { - this->layout().scale(sizeof(T)/sizeof(T2)), // NOLINT(bugprone-sizeof-expression) : sizes are compatible according to static assert above - reinterpret_pointer_cast(this->base()) // if ADL gets confused here (e.g. multi:: and thrust::) then adl_reinterpret_pointer_cast will be necessary - }; - } - - public: - template::template rebind> - constexpr auto reinterpret_array_cast() const& {return reinterpret_array_cast_aux().as_const();} - - template::template rebind> - constexpr auto reinterpret_array_cast() & {return reinterpret_array_cast_aux();} - - template::template rebind> - constexpr auto reinterpret_array_cast() && {return reinterpret_array_cast_aux();} - - template::template rebind > - constexpr auto reinterpret_array_cast(multi::size_type count) & -> subarray, D + 1, P2> { - static_assert( sizeof(T)%sizeof(T2) == 0, - "error: reinterpret_array_cast is limited to integral stride values"); - - assert( count > 0 ); - assert( sizeof(T) == sizeof(T2)*static_cast(count) ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - return { - layout_t{this->layout().scale(sizeof(T)/sizeof(T2)), 1, 0, count}.rotate(), // NOLINT(bugprone-sizeof-expression) T and T2 are size compatible (see static_assert above) - reinterpret_pointer_cast(this->base()) // if ADL gets confused here (e.g. multi:: and thrust::) then adl_reinterpret_pointer_cast will be necessary - }; - } - - template::template rebind > - constexpr auto reinterpret_array_cast(multi::size_type count) && -> subarray, D + 1, P2> {return reinterpret_array_cast(count);} - - template::template rebind > - constexpr auto reinterpret_array_cast(size_type count) const& -> subarray, D + 1, P2> { - static_assert( sizeof(T)%sizeof(T2) == 0, - "error: reinterpret_array_cast is limited to integral stride values"); - - assert( sizeof(T) == sizeof(T2)*static_cast(count) ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : checck implicit size compatibility - return { - layout_t{this->layout().scale(sizeof(T)/sizeof(T2)), 1, 0, count}.rotate(), - static_cast(static_cast(this->base())) - }; - } - - template - auto serialize(Archive& arxiv, unsigned int /*version*/) { - using AT = multi::archive_traits; - std::for_each(this->begin(), this->end(), [&](reference&& item) {arxiv & AT ::make_nvp("item", std::move(item));}); - // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & cereal::make_nvp("item", item);}); - // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & item ;}); - } -}; - -template struct array_iterator{}; - -template -struct array_iterator // NOLINT(fuchsia-multiple-inheritance) -: boost::multi::iterator_facade< - array_iterator, - Element, std::random_access_iterator_tag, - typename std::iterator_traits::reference, multi::difference_type -> -, multi::affine , multi::difference_type> -, multi::decrementable > -, multi::incrementable > -, multi::totally_ordered2, void> -{ - using affine = multi::affine, multi::difference_type>; - using difference_type = typename affine::difference_type; - - array_iterator() = default; - using layout_type = multi::layout_t<0>; - - template< - class Other, - decltype(multi::implicit_cast(typename Other::pointer{}))* = nullptr, - decltype(std::declval().base())* = nullptr - > - // cppcheck-suppress noExplicitConstructor ; because underlying pointer is implicitly convertible - HD constexpr/*mplct*/ array_iterator(Other const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to reproduce the implicitness of the argument - : data_{other.base()}, stride_{other.stride()} {} - - template< - class Other, - decltype(multi::explicit_cast(typename Other::pointer{}))* = nullptr, - decltype(std::declval().data_)* = nullptr - > - constexpr explicit array_iterator(Other const& other) - : data_{other.data_}, stride_{other.stride_} {} - - template friend struct array_iterator; - - constexpr explicit array_iterator(std::nullptr_t nil) : data_{nil} {} - constexpr explicit array_iterator(Ptr const& ptr) : data_{ptr} {} - - template< - class EElement, typename PPtr, - typename = decltype(multi::implicit_cast(std::declval>().data_)) - > - HD constexpr /*impl*/ array_iterator(array_iterator const& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to reproduce the implicitness of original pointer - : data_{other.data_}, stride_{other.stride_} {} - - constexpr explicit operator bool() const {return static_cast(this->data_);} - - HD constexpr auto operator[](typename array_iterator::difference_type n) const -> typename std::iterator_traits::reference { - return *((*this) + n); - } - - constexpr auto operator->() const -> Ptr {return data_;} - - using element = Element; - using element_ptr = Ptr; - using pointer = element_ptr; - using stride_type = multi::index; - - static constexpr dimensionality_type rank_v = 1; - using rank = std::integral_constant; - - constexpr auto operator<(array_iterator const& other) const -> bool {return distance_to(other) > 0;} - - HD explicit constexpr array_iterator(Ptr ptr, typename subarray::index stride) - : data_{ptr}, stride_{stride} {} - - private: - friend struct subarray; - - element_ptr data_{nullptr}; // TODO(correaa) : consider uninitialized pointer - stride_type stride_ = {1}; - - /*[[gnu::pure]]*/ - constexpr auto distance_to(array_iterator const& other) const -> difference_type { - assert(stride_==other.stride_ and (other.data_-data_)%stride_ == 0); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - return (other.data_ - data_)/stride_; - } - - public: - HD constexpr auto operator+(difference_type n) const -> array_iterator {array_iterator ret{*this}; ret+=n; return ret;} - - [[deprecated("use base() for iterator")]] - constexpr auto data() const -> element_ptr {return data_;} - - constexpr auto base() const& -> element_ptr {return data_;} - - friend MULTI_NONV_CONSTEXPR - auto base(array_iterator const& self) -> element_ptr {return self.base();} - - HD constexpr auto stride() const -> stride_type {return stride_;} - friend constexpr auto stride(array_iterator const& self) -> stride_type {return self.stride_;} - - constexpr auto operator++() -> array_iterator& {data_ += stride_; return *this;} - constexpr auto operator--() -> array_iterator& {data_ -= stride_; return *this;} - - friend constexpr auto operator==(array_iterator const& self, array_iterator const& other) -> bool {return self.data_ == other.data_;} -// friend constexpr auto operator!=(array_iterator const& a, array_iterator const& b) -> bool {return not(a.data_ == b.data_);} - - HD constexpr auto operator*() const -> typename std::iterator_traits::reference { return *data_; } // NOLINT(readability-const-return-type) - - constexpr auto operator-(array_iterator const& other) const -> difference_type {return -distance_to(other);} - - constexpr auto operator+=(difference_type n) -> array_iterator& {data_ += stride_*n; return *this;} - constexpr auto operator-=(difference_type n) -> array_iterator& {data_ -= stride_*n; return *this;} -}; - -template -using iterator = array_iterator; - -template -struct subarray -: array_types { - using types = array_types; - using types::types; - - using element = typename types::element; - using element_ref = typename std::iterator_traits::reference; - using element_cref = typename std::iterator_traits::reference; - using iterator = array_iterator; - - constexpr auto operator= (element const& elem) & -> subarray& { - // MULTI_MARK_SCOPE(std::string{"multi::operator= D=0 from "}+typeid(T).name()+" to "+typeid(T).name() ); - adl_copy_n(&elem, 1, this->base_); - return *this; - } - constexpr auto operator= (element const& elem) && -> subarray& { - operator=(elem); - return *this; // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - } - - constexpr auto operator==(element const& elem) const -> bool { - assert(this->num_elements() == 1); - return adl_equal(&elem, std::next(&elem, this->num_elements()), this->base()); - } - constexpr auto operator!=(element const& elem) const {return not operator==(elem);} - - template - constexpr - auto operator=(Range0 const& rng) & -> subarray& { - adl_copy_n(&rng, 1, this->base_); - return *this; - } - - constexpr auto elements_at(size_type idx [[maybe_unused]]) const& -> element_cref {assert(idx < this->num_elements()); return *(this->base_);} - constexpr auto elements_at(size_type idx [[maybe_unused]]) && -> element_ref {assert(idx < this->num_elements()); return *(this->base_);} - constexpr auto elements_at(size_type idx [[maybe_unused]]) & -> element_ref {assert(idx < this->num_elements()); return *(this->base_);} - - constexpr auto operator!=(subarray const& other) const {return not adl_equal(other.base_, other.base_ + 1, this->base_);} - constexpr auto operator==(subarray const& other) const {return adl_equal(other.base_, other.base_ + 1, this->base_);} - - using decay_type = typename types::element; - - constexpr auto operator()() const -> element_ref {return *(this->base_);} - - constexpr operator element_ref () && {return *(this->base_);} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax - constexpr operator element_ref () & {return *(this->base_);} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax - constexpr operator element_cref() const& {return *(this->base_);} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax - - template - auto serialize(Archive& arxiv, unsigned int const /*version*/) { - using AT = multi::archive_traits; - auto& element_ = *(this->base_); - arxiv & AT::make_nvp("element", element_); - // arxiv & cereal::make_nvp("element", element_); - // arxiv & element_ ; - } -}; - -template -struct subarray // NOLINT(fuchsia-multiple-inheritance) : to define operators via CRTP -// : multi::partially_ordered2, void> -: multi::random_iterable > -, array_types { - ~subarray() = default; // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - - static constexpr dimensionality_type rank_v = 1; - using rank = std::integral_constant; - - using types = array_types; - using types::types; - using layout_type = Layout; - using ref_ = subarray; - - using element_type = T; - - using element_ptr = typename types::element_ptr; - using element_const_ptr = typename std::pointer_traits::template rebind; - using element_move_ptr = multi::move_ptr; - using element_ref = typename types::element_ref; - using element_cref = typename std::iterator_traits::reference; - - using default_allocator_type = typename multi::pointer_traits::default_allocator_type; - - constexpr auto get_allocator() const -> default_allocator_type {return default_allocator_of(subarray::base());} - friend MULTI_NONV_CONSTEXPR - auto get_allocator(subarray const& self) -> default_allocator_type {return self.get_allocator();} - - using decay_type = array::default_allocator_type>; - - constexpr auto decay() const -> decay_type {return decay_type{*this};} - friend constexpr auto decay(subarray const& self) -> decay_type {return self.decay();} - - using basic_const_array = subarray< - T, 1, - typename std::pointer_traits::template rebind, - Layout - >; - - using const_reference = typename array_types::const_reference; - using reference = typename array_types:: reference; - - protected: - template constexpr void intersection_assign(A&& other)&& {intersection_assign(std::forward(other));} - template constexpr void intersection_assign(A&& other)& { - std::for_each( - intersection(types::extension(), extension(other)).begin(), - intersection(types::extension(), extension(other)).end() , - [&](auto const idx) {operator[](idx) = std::forward(other)[idx];} - ); - // for(auto const idx : intersection(types::extension(), extension(other))) { - // operator[](idx) = std::forward(other)[idx]; - // } - } - - subarray(subarray const&) = default; - - template friend struct subarray; - template friend struct static_array; - - template - friend constexpr auto static_array_cast(subarray const&) -> decltype(auto); - - template - friend constexpr auto reinterpret_array_cast(subarray&& self) { - return std::move(self).template reinterpret_array_cast::template rebind>(); - } - template - friend constexpr auto reinterpret_array_cast(subarray const& self) { - return self.template reinterpret_array_cast::template rebind>(); - } - - public: - friend constexpr auto sizes(subarray const& self) noexcept -> typename subarray::sizes_type {return self.sizes();} // needed by nvcc - friend constexpr auto size (subarray const& self) noexcept -> typename subarray::size_type {return self.size ();} // needed by nvcc - - constexpr auto operator+() const -> decay_type {return decay();} - - subarray(subarray&&) noexcept = default; // in C++ 14 this is necessary to return array references from functions -// in c++17 things changed and non-moveable non-copyable types can be returned from functions and captured by auto - - protected: - template friend struct subarray_ptr; - template friend struct array_iterator; - - public: - friend constexpr auto dimensionality(subarray const& /*self*/) -> dimensionality_type {return 1;} - - auto operator=(std::initializer_list values) && -> subarray& {operator=(values); return *this;} - auto operator=(std::initializer_list values) & -> subarray& { - assert( static_cast(values.size()) == this->size() ); - adl_copy_n(values.begin(), values.size(), begin()); - return *this; - } - - // NOLINTNEXTLINE(runtime/operator) - HD constexpr auto operator&() && { return subarray_ptr{this->base_, this->layout()}; } // NOLINT(google-runtime-operator) : taking address of a reference-like object should be allowed - // NOLINTNEXTLINE(runtime/operator) - HD constexpr auto operator&() & { return subarray_ptr{this->base_, this->layout()}; } // NOLINT(google-runtime-operator) : taking address of a reference-like object should be allowed - // NOLINTNEXTLINE(runtime/operator) - HD constexpr auto operator&() const& {return subarray_ptr{this->base_, this->layout()};} // NOLINT(google-runtime-operator) extend semantics - - HD constexpr void assign(std::initializer_list values) const {assert( values.size() == static_cast(this->size()) ); - assign(values.begin(), values.end()); - } - template - constexpr auto assign(It first) & -> It {adl_copy_n(first, this->size(), this->begin()); std::advance(first, this->size()); return first;} - template - constexpr auto assign(It first)&& -> It {return assign(first);} - template - constexpr void assign(It first, It last) & { - assert( std::distance(first, last) == this->size() ); (void)last; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - assign(first); - } - template - constexpr void assign(It first, It last)&& {assign(first, last);} - - auto operator=(subarray&& other) & noexcept(std::is_nothrow_copy_assignable_v) // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) //NOSONAR - -> subarray& { // lints(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - operator=(other); - return *this; // lints([cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - } - constexpr auto operator=(subarray const& other) & -> subarray& { - static_assert(std::is_copy_assignable_v, "assignment requires element-wise assignment"); // TODO(correaa) : make sfinae friendly - if(this == std::addressof(other)) {return *this;} - assert(this->extension() == other.extension()); - elements() = other.elements(); - return *this; - } - constexpr auto operator=(subarray const& other) && -> subarray& { - if(this == std::addressof(other)) {return *this;} // lints cert-oop54-cpp - operator=(other); return *this; - } - - private: - HD constexpr auto at_aux(index idx) const -> typename subarray::reference { // NOLINT(readability-const-return-type) fancy pointers can deref into const values to avoid assignment - // MULTI_ACCESS_ASSERT(this->extension().contains(i)&&"out of bounds"); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - auto ba = this->base(); // NOLINT(llvm-qualified-auto,readability-qualified-auto) - auto of = (idx*this->stride() - this->offset()); // NOLINT(llvm-qualified-auto,readability-qualified-auto) - auto pt = ba + of; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic,llvm-qualified-auto,readability-qualified-auto) - return *pt; // in C++17 this is allowed even with syntethic references - // return *(this->base() + (idx*this->stride() - this->offset())); // TODO(correaa) use this->base()[(i*this->stride() - this->offset())] - } - - public: - HD constexpr auto operator[](index idx) const& -> typename subarray::const_reference {return at_aux(idx);} // NOLINT(readability-const-return-type) fancy pointers can deref into const values to avoid assignment - HD constexpr auto operator[](index idx) & -> typename subarray:: reference {return at_aux(idx);} // NOLINT(readability-const-return-type) fancy pointers can deref into const values to avoid assignment - HD constexpr auto operator[](index idx) && -> typename subarray:: reference {return at_aux(idx);} // NOLINT(readability-const-return-type) fancy pointers can deref into const values to avoid assignment - - constexpr auto front() const& -> const_reference {return *begin();} - constexpr auto back() const& -> const_reference {return *std::prev(end(), 1);} - - constexpr auto front() && -> reference {return *begin();} - constexpr auto back() && -> reference {return *std::prev(end(), 1);} - - constexpr auto front() & -> reference {return *begin();} - constexpr auto back() & -> reference {return *std::prev(end(), 1);} - - private: - template - static constexpr auto apply_impl(Self&& self, Tuple const& tuple, std::index_sequence /*012*/) -> decltype(auto) { - return std::forward(self)(std::get(tuple)...); - } - - public: - template HD constexpr auto apply(Tuple const& tuple) const& -> decltype(auto) {return apply_impl( *this , tuple, std::make_index_sequence>());} - template HD constexpr auto apply(Tuple const& tuple) && -> decltype(auto) {return apply_impl(std::move(*this), tuple, std::make_index_sequence>());} - template constexpr auto apply(Tuple const& tuple) & -> decltype(auto) {return apply_impl( *this , tuple, std::make_index_sequence>());} - - template::value == 0), int> = 0> HD constexpr auto operator[](Tuple const& /*empty*/) const& -> decltype(auto) {return *this;} - template::value == 1), int> = 0> HD constexpr auto operator[](Tuple const& indices ) const& -> decltype(auto) {return operator[](std::get<0>(indices));} - template::value > 1), int> = 0> HD constexpr auto operator[](Tuple const& indices ) const& - ->decltype(operator[](std::get<0>(indices))[detail::tuple_tail(indices)]) { - return operator[](std::get<0>(indices))[detail::tuple_tail(indices)]; } - - HD constexpr auto elements_at(size_type idx) const& -> decltype(auto) {assert(idx < this->num_elements()); return operator[](idx);} - HD constexpr auto elements_at(size_type idx) && -> decltype(auto) {assert(idx < this->num_elements()); return operator[](idx);} - HD constexpr auto elements_at(size_type idx) & -> decltype(auto) {assert(idx < this->num_elements()); return operator[](idx);} - - constexpr auto reindexed(index first) && {return reindexed(first);} - constexpr auto reindexed(index first) & { - typename types::layout_t new_layout = this->layout(); - new_layout.reindex(first); - return subarray{new_layout, types::base_}; - } - - private: - constexpr auto take_aux(difference_type count) const { - assert( count <= this->size() ); // calculating size is expensive that is why - typename types::layout_t const new_layout{ - this->layout().sub(), - this->layout().stride(), - this->layout().offset(), - this->stride()*count - }; - return subarray{new_layout, this->base()}; - } - - public: - constexpr auto take(difference_type count) const& -> basic_const_array {return take_aux(count);} - constexpr auto take(difference_type count) && -> subarray {return take_aux(count);} - constexpr auto take(difference_type count) & -> subarray {return take_aux(count);} - - private: - constexpr auto drop_aux(difference_type count) const -> subarray { - assert( count <= this->size() ); - typename types::layout_t const new_layout{ - this->layout().sub(), - this->layout().stride(), - this->layout().offset(), - this->stride()*(this->size() - count) - }; - return subarray{new_layout, this->base() + (count*this->layout().stride() - this->layout().offset())}; - } - - public: - constexpr auto drop(difference_type count) const& -> basic_const_array {return drop_aux(count);} - constexpr auto drop(difference_type count) && -> subarray {return drop_aux(count);} - constexpr auto drop(difference_type count) & -> subarray {return drop_aux(count);} - - private: - HD /*[[gnu::pure]]*/ constexpr auto sliced_aux(index first, index last) const { - typename types::layout_t new_layout = this->layout(); - if(this->is_empty()) { - assert(first == last); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - new_layout.nelems() = 0; // TODO(correaa) : don't use mutation - } else { - (new_layout.nelems() /= this->size())*=(last - first); - } - return subarray{new_layout, this->base() + (first*this->layout().stride() - this->layout().offset())}; - } - - public: - using elements_iterator = elements_iterator_t; - using celements_iterator = elements_iterator_t; - - using elements_range = elements_range_t; - using const_elements_range = elements_range_t; - - private: - constexpr auto elements_aux() const {return elements_range{this->base(), this->layout()};} - - public: - constexpr auto elements() & -> elements_range {return elements_aux();} - constexpr auto elements() && -> elements_range {return elements_aux();} - constexpr auto elements() const& -> const_elements_range {return const_elements_range{this->base(), this->layout()};} // TODO(correaa) simplify - - constexpr auto celements() const -> const_elements_range {return elements_aux();} - - constexpr auto hull() const -> std::pair { - return {std::min(this->base(), this->base() + this->hull_size()), std::abs(this->hull_size())}; - } - - HD constexpr auto sliced(index first, index last) const& -> basic_const_array {return sliced_aux(first, last);} - HD /*[[gnu::pure]]*/ constexpr auto sliced(index first, index last) & -> subarray {return sliced_aux(first, last);} - HD constexpr auto sliced(index first, index last) && -> subarray {return sliced_aux(first, last);} - - /*[[gnu::pure]]*/ constexpr auto blocked(index first, index last)& -> subarray { - return sliced(first, last).reindexed(first); - } - /*[[gnu::pure]]*/ constexpr auto stenciled(typename subarray::index_extension ext) -> subarray { - return blocked(ext.start(), ext.finish()); - } - - private: - constexpr auto strided_aux(difference_type diff) const -> subarray { - typename types::layout_t const new_layout = {this->layout().sub(), this->layout().stride()*diff, this->layout().offset(), this->layout().nelems()}; - return {new_layout, types::base_}; - } - - public: - constexpr auto strided(difference_type diff) const& -> basic_const_array {return strided_aux(diff);} - constexpr auto strided(difference_type diff) && -> subarray {return strided_aux(diff);} - constexpr auto strided(difference_type diff) & -> subarray {return strided_aux(diff);} - - HD constexpr auto sliced(index first, index last, difference_type stride) const& -> basic_const_array {return sliced(first, last).strided(stride);} - HD constexpr auto sliced(index first, index last, difference_type stride) && -> subarray {return sliced(first, last).strided(stride);} - HD constexpr auto sliced(index first, index last, difference_type stride) & -> subarray {return sliced(first, last).strided(stride);} - - HD constexpr auto range(index_range const& rng) & {return sliced(rng.front(), rng.last());} - HD constexpr auto range(index_range const& rng) && {return std::move(*this).sliced(rng.front(), rng.last());} - HD constexpr auto range(index_range const& rng) const& {return sliced(rng.front(), rng.last());} - - HD constexpr auto operator()() const& -> basic_const_array {return {this->layout(), this->base()};} - HD constexpr auto operator()() && -> subarray {return *this;} - HD constexpr auto operator()() & -> subarray {return *this;} - - HD constexpr auto operator()(index_range const& rng) & {return range(rng);} - HD constexpr auto operator()(index_range const& rng) && {return std::move(*this).range(rng);} - HD constexpr auto operator()(index_range const& rng) const& {return range(rng);} - - HD constexpr auto operator()(index idx) & -> decltype(auto) {return operator[](idx);} - HD constexpr auto operator()(index idx) && -> decltype(auto) {return std::move(*this).operator[](idx);} - HD constexpr auto operator()(index idx) const& -> decltype(auto) {return operator[](idx);} - - private: - HD constexpr auto paren_aux() & {return operator()();} - HD constexpr auto paren_aux() && {return operator()();} - HD constexpr auto paren_aux() const& {return operator()();} - - HD constexpr auto paren_aux(index_range const& rng) & {return range(rng);} - HD constexpr auto paren_aux(index_range const& rng) && {return range(rng);} - HD constexpr auto paren_aux(index_range const& rng) const& {return range(rng);} - - HD constexpr auto paren_aux(index idx) & -> decltype(auto) {return operator[](idx);} - HD constexpr auto paren_aux(index idx) && -> decltype(auto) {return operator[](idx);} - HD constexpr auto paren_aux(index idx) const& -> decltype(auto) {return operator[](idx);} - - constexpr auto paren_aux(intersecting_range const& rng) & -> decltype(auto) {return paren_aux(intersection(this->extension(), rng));} - constexpr auto paren_aux(intersecting_range const& rng) && -> decltype(auto) {return std::move(*this).paren_aux(intersection(this->extension(), rng));} - constexpr auto paren_aux(intersecting_range const& rng) const& -> decltype(auto) {return paren_aux(intersection(this->extension(), rng));} - - public: - constexpr auto operator()(intersecting_range const& isrange) & -> decltype(auto) {return paren_aux(isrange);} - constexpr auto operator()(intersecting_range const& isrange) && -> decltype(auto) {return std::move(*this).paren_aux(isrange);} - constexpr auto operator()(intersecting_range const& isrange) const& -> decltype(auto) {return paren_aux(isrange);} - - template - constexpr auto operator()(Args&&... args) & - ->decltype(paren(*this, std::forward(args)...)) { - return paren(*this, std::forward(args)...); } - - template - constexpr auto operator()(Args&&... args) && - ->decltype(paren(std::move(*this), std::forward(args)...)) { - return paren(std::move(*this), std::forward(args)...); } - - template - constexpr auto operator()(Args&&... args) const& - ->decltype(paren(*this, std::forward(args)...)) { - return paren(*this, std::forward(args)...); } - - using partitioned_type = subarray; - using partitioned_const_type = subarray; - - private: - HD constexpr auto partitioned_aux(size_type size) const -> partitioned_type { - assert( size != 0 ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - assert( (this->layout().nelems() % size) == 0 ); // TODO(correaa) remove assert? truncate left over? (like mathematica) // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - multi::layout_t<2> new_layout{this->layout(), this->layout().nelems()/size, 0, this->layout().nelems()}; - new_layout.sub().nelems() /= size; // TODO(correaa) : don't use mutation - return {new_layout, types::base_}; - } - - public: - HD constexpr auto partitioned(size_type size) const& -> partitioned_const_type {return partitioned_aux(size);} - HD constexpr auto partitioned(size_type size) & -> partitioned_type {return partitioned_aux(size);} - HD constexpr auto partitioned(size_type size) && -> partitioned_type {return partitioned_aux(size);} - - private: - HD constexpr auto chunked_aux(size_type size) const -> partitioned_type { - assert( this->size() % size == 0 ); - return partitioned_aux(this->size()/size); - } - - public: // in Mathematica this is called Partition https://reference.wolfram.com/language/ref/Partition.html in RangesV3 it is called chunk - HD constexpr auto chunked(size_type size) const& -> partitioned_const_type {return chunked_aux(size);} - HD constexpr auto chunked(size_type size) & -> partitioned_type {return chunked_aux(size);} - HD constexpr auto chunked(size_type size) && -> partitioned_type {return chunked_aux(size);} - - private: - constexpr auto reversed_aux() const -> subarray { - auto new_layout = this->layout(); - new_layout.reverse(); - return {new_layout, types::base_}; - } - - public: - constexpr auto reversed() const& -> basic_const_array {return reversed_aux();} - constexpr auto reversed() & -> subarray {return reversed_aux();} - constexpr auto reversed() && -> subarray {return reversed_aux();} - - friend constexpr auto reversed(subarray const& self) -> basic_const_array {return self .reversed();} - friend constexpr auto reversed(subarray & self) -> subarray {return self .reversed();} - friend constexpr auto reversed(subarray && self) -> subarray {return std::move(self).reversed();} - - friend constexpr auto rotated(subarray const& self) -> decltype(auto) {return self. rotated();} - friend constexpr auto unrotated(subarray const& self) -> decltype(auto) {return self.unrotated();} - - constexpr auto rotated() & -> decltype(auto) {return operator()();} - constexpr auto rotated() && -> decltype(auto) {return operator()();} - constexpr auto rotated() const& -> decltype(auto) {return operator()();} - - HD constexpr auto unrotated() const& -> decltype(auto) {return operator()();} - HD constexpr auto unrotated() && -> decltype(auto) {return operator()();} - HD constexpr auto unrotated() & -> decltype(auto) {return operator()();} - - using iterator = typename multi::array_iterator; - using const_iterator = typename multi::array_iterator; - using move_iterator = array_iterator; - - template< - class Range, - std::enable_if_t>::value, int> =0, - // std::enable_if_t, int> =0, - class = decltype(Range(std::declval(), std::declval())) - > - constexpr explicit operator Range() const & {return Range(begin(), end());} // NOLINT(fuchsia-default-arguments-calls) e.g. std::vector(it, it, alloc = {}) - - private: - HD constexpr explicit subarray(iterator begin, iterator end) - : subarray { - layout_type{ {}/*begin->layout()*/, begin.stride(), 0, begin.stride()*(end - begin)}, - begin.base() - } { - assert(begin.stride() == end.stride() ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - // assert(begin->layout() == end->layout()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - } - friend constexpr auto ref(iterator begin, iterator end) -> multi::subarray; - - constexpr HD auto begin_aux() const {return iterator{this->base_ , this->stride()};} - constexpr auto end_aux () const {return iterator{this->base_ + types::nelems(), this->stride()};} - - public: - constexpr HD auto begin() const& -> const_iterator {return begin_aux();} - constexpr auto begin() & -> iterator {return begin_aux();} - constexpr auto begin() && -> iterator {return begin_aux();} - - constexpr auto mbegin() & {return move_iterator{begin()};} - constexpr auto mend () & {return move_iterator{end ()};} - - constexpr auto mbegin() && {return move_iterator{begin()};} - constexpr auto mend () && {return move_iterator{end ()};} - - constexpr auto end () const& -> const_iterator {return end_aux();} - constexpr auto end () & -> iterator {return end_aux();} - constexpr auto end () && -> iterator {return end_aux();} - - friend MULTI_NONV_CONSTEXPR auto begin(subarray const& self) -> const_iterator {return self .begin();} - friend MULTI_NONV_CONSTEXPR auto begin(subarray & self) -> iterator {return self .begin();} - friend MULTI_NONV_CONSTEXPR auto begin(subarray && self) -> iterator {return std::move(self).begin();} - - friend MULTI_NONV_CONSTEXPR auto end (subarray const& self) -> const_iterator {return self .end() ;} - friend MULTI_NONV_CONSTEXPR auto end (subarray & self) -> iterator {return self .end() ;} - friend MULTI_NONV_CONSTEXPR auto end (subarray && self) -> iterator {return std::move(self).end() ;} - - HD constexpr auto cbegin() const& -> const_iterator {return begin();} - constexpr auto cend () const& -> const_iterator {return end() ;} - - friend HD /*constexpr*/ auto cbegin(subarray const& self) {return self.cbegin();} - friend constexpr auto cend (subarray const& self) {return self.cend() ;} - - template constexpr auto operator=(subarray const& other) && -> subarray& {operator=( other ); return *this;} - template constexpr auto operator=(subarray const& other) & -> subarray& { - assert(other.extensions() == this->extensions()); - elements() = other.elements(); - return *this; - } - - template constexpr auto operator=(subarray && other) && -> subarray& {operator=(std::move(other)); return *this;} - template constexpr auto operator=(subarray && other) & -> subarray& { - assert(this->extensions() == other.extensions()); - elements() = std::move(other).elements(); - return *this; - } - - template< - class Range, - class = std::enable_if_t> // , - // class = decltype(adl_copy_n(adl_begin(std::declval()), std::declval(), std::declval())) - > - constexpr auto momo(Range const& rng) & // TODO(correaa) check that you LHS is not read-only? - -> subarray& { // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - assert(this->size() == adl_size(rng)); - adl_copy_n(adl_begin(rng), adl_size(rng), begin()); - // adl_copy(adl_begin(rng), adl_end(rng), begin()); - return *this; - } - - template< - class Range, - class = std::enable_if_t> // , - // class = decltype(adl_copy_n(adl_begin(std::declval()), std::declval(), std::declval())) - > - constexpr auto operator=(Range const& rng) & // TODO(correaa) check that you LHS is not read-only? - -> subarray& { // lints(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) - assert(this->size() == adl_size(rng)); - adl_copy_n(adl_begin(rng), adl_size(rng), begin()); - // adl_copy(adl_begin(rng), adl_end(rng), begin()); - return *this; - } - template>> - constexpr auto operator=(Range const& rng) && -> subarray& {operator=(rng); return *this;} - - template constexpr auto assign(It first) && - ->decltype(adl_copy_n(first, std::declval(), std::declval()), void()) { - return adl_copy_n(first, this-> size() , std::move(*this).begin()), void(); } - - template - friend constexpr auto operator==(subarray const& self, subarray const& other) -> bool { - return self.extension() == other.extension() and self.elements() == other.elements(); - } - template - friend constexpr auto operator!=(subarray const& self, subarray const& other) -> bool { - return self.extension() != other.extension() or self.elements() != other.elements(); - } - - friend constexpr auto operator< (subarray const& self, subarray const& other) -> bool {return lexicographical_compare(self, other);} - friend constexpr auto operator<=(subarray const& self, subarray const& other) -> bool {return lexicographical_compare(self, other) or self == other;} - - constexpr void swap(subarray&& other) && noexcept { - assert(this->extension() == other.extension()); - adl_swap_ranges(this->elements().begin(), this->elements().end(), std::move(other).elements().begin()); - } - friend constexpr void swap(subarray&& self, subarray&& other) noexcept {std::move(self).swap(std::move(other));} - - template>>> friend constexpr void swap(subarray&& self, A&& other) noexcept {std::move(self).swap(std::forward(other));} - template>>> friend constexpr void swap(A&& other, subarray&& self) noexcept {std::move(self).swap(std::forward(other));} - - private: - template - /*[[gnu::pure]]*/ static constexpr auto lexicographical_compare(A1 const& self, A2 const& other) -> bool { - if(extension(self).first() > extension(other).first()) {return true ;} - if(extension(self).first() < extension(other).first()) {return false;} - return adl_lexicographical_compare(adl_begin(self), adl_end(self), adl_begin(other), adl_end(other)); - } - - public: - template::template rebind> - constexpr auto static_array_cast() const -> subarray { // name taken from std::static_pointer_cast - return {this->layout(), static_cast(this->base())}; - } - template::template rebind, class... Args> - constexpr auto static_array_cast(Args&&... args) const -> subarray { // name taken from std::static_pointer_cast - return {this->layout(), P2{this->base(), std::forward(args)...}}; - } - - template - constexpr auto element_transformed(UF&& fun) const& { - return static_array_cast< - // std::remove_cv_t>>, - std::decay_t>, - transform_ptr< - // std::remove_cv_t>>, - std::decay_t>, - UF, element_const_ptr, std::invoke_result_t - > - >(std::forward(fun)); - } - template - constexpr auto element_transformed(UF&& fun) & { - return static_array_cast< - // std::remove_cv_t>>, - std::decay_t>, - transform_ptr< - // std::remove_cv_t>>, - std::decay_t>, - UF, element_ptr , std::invoke_result_t - > - >(std::forward(fun)); - } - template - constexpr auto element_transformed(UF&& fun) && {return element_transformed(std::forward(fun));} - - template< - class T2, class P2 = typename std::pointer_traits::template rebind, - class Element = typename subarray::element, - class PM = T2 std::decay_t::* - > - constexpr auto member_cast(PM member) const -> subarray { - static_assert(sizeof(T)%sizeof(T2) == 0, - "array_member_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. " - "Use custom alignas structures (to the interesting member(s) sizes) or custom pointers to allow reintrepreation of array elements" - ); - -#if defined(__GNUC__) and (not defined(__INTEL_COMPILER)) - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret is what the function does. alternative for GCC/NVCC - auto&& r1 = (*(reinterpret_cast(subarray::base_))).*member; // ->*pm; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) TODO(correaa) find a better way - auto* p1 = &r1; P2 p2 = reinterpret_cast(p1); -#else - auto p2 = static_cast(&(this->base_->*member)); // this crashes nvcc 11.2-11.4 and some? gcc compiler -#endif - return {this->layout().scale(sizeof(T)/sizeof(T2)), p2}; - } - - constexpr auto moved() & {return subarray{this->layout(), element_move_ptr{this->base()}};} - constexpr auto moved() && {return moved();} - - constexpr auto element_moved() & {return subarray{this->layout(), element_move_ptr{this->base()}};} - constexpr auto element_moved() && {return element_moved();} - - template::template rebind> - constexpr auto reinterpret_array_cast() const& -> subarray, 1, P2> { // TODO(correaa) : use rebind for return type - static_assert( sizeof(T)%sizeof(T2)== 0, - "error: reinterpret_array_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. Use custom pointers to allow reintrepreation of array elements in other cases"); - - return {this->layout().scale(sizeof(T)/sizeof(T2)), reinterpret_pointer_cast(this->base())}; - } - - template::template rebind > - constexpr auto reinterpret_array_cast(size_type n) const& -> subarray, 2, P2> { // TODO(correaa) : use rebind for return type - static_assert( sizeof(T)%sizeof(T2)== 0, - "error: reinterpret_array_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. Use custom pointers to allow reintrepreation of array elements in other cases"); - - return subarray, 2, P2>{ - layout_t<2>{this->layout().scale(sizeof(T)/sizeof(T2)), 1, 0, n}, - reinterpret_pointer_cast(this->base()) - }.rotated(); - } - - // TODO(correaa) : rename to reinterpret_pointer_cast? - template::template rebind > - constexpr auto reinterpret_array_cast(size_type n)& -> subarray, 2, P2> { - static_assert( sizeof(T)%sizeof(T2)== 0, - "error: reinterpret_array_cast is limited to integral stride values, therefore the element target size must be multiple of the source element size. Use custom pointers to allow reintrepreation of array elements in other cases"); - - return subarray, 2, P2>{ - layout_t<2>{this->layout().scale(sizeof(T)/sizeof(T2)), 1, 0, n}, - reinterpret_pointer_cast(this->base()) - }.rotated(); - } - template::template rebind > - constexpr auto reinterpret_array_cast(size_type n)&& -> subarray, 2, P2> { - return this->reinterpret_array_cast(n); - } - - template - constexpr auto fill(TT const& value) & -> decltype(auto) { - return adl_fill_n(this->begin(), this->size(), value), *this; - } - constexpr auto fill()& -> decltype(auto) {return fill(typename subarray::element_type{});} - - template - constexpr auto fill(TT const& value) && -> decltype(auto) {return std::move(this->fill(value));} - constexpr auto fill() && -> decltype(auto) { - return std::move(*this).fill(typename subarray::element_type{}); - } - - template - void serialize(Archive& arxiv, unsigned /*version*/) { - using AT = multi::archive_traits; - std::for_each(this->begin(), this->end(), [&](reference& item) {arxiv & AT ::make_nvp("item", item);}); - // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & cereal::make_nvp("item", item);}); - // std::for_each(this->begin(), this->end(), [&](auto&& item) {arxiv & item ;}); - } -}; - -template -constexpr auto static_array_cast(Array&& self, Args&&... args) -> decltype(auto) { - return std::forward(self).template static_array_cast(std::forward(args)...); -} - -template -struct array_ref // TODO(correaa) : inheredit from multi::partially_ordered2, void>? -: subarray -{ - ~array_ref() = default; // lints(cppcoreguidelines-special-member-functions) - - using layout_type = typename array_ref::types::layout_t; - - protected: - constexpr array_ref() noexcept : subarray{{}, nullptr} {} - - using iterator = typename subarray::iterator; - - public: // lints(hicpp-use-equals-delete,modernize-use-equals-delete) - array_ref(iterator, iterator) = delete; - - friend constexpr auto sizes(array_ref const& self) noexcept -> typename array_ref::sizes_type {return self.sizes();} // needed by nvcc - friend constexpr auto size (array_ref const& self) noexcept -> typename array_ref::size_type {return self.size ();} // needed by nvcc - - protected: - [[deprecated("references are not copyable, use auto&&")]] - array_ref(array_ref const&) = default; // don't try to use `auto` for references, use `auto&&` or explicit value type - - public: - #if defined(__NVCC__) - array_ref(array_ref&&) noexcept = default; // this needs to be public in nvcc c++17 - #else - array_ref(array_ref&&) = delete; - #endif - - template{}>, decltype(multi::explicit_cast(std::declval()))* = nullptr> - constexpr explicit array_ref(array_ref&& other) - : subarray{other.layout(), ElementPtr{other.base()}} {} - - template{}>, decltype(multi::implicit_cast(std::declval()))* = nullptr> - // cppcheck-suppress noExplicitConstructor ; to allow terse syntax - constexpr /*implicit*/ array_ref(array_ref&& other) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - : subarray{other.layout(), ElementPtr{other.base()}} {} - - constexpr explicit array_ref(typename array_ref::element_ptr dat, typename array_ref::extensions_type extensions) noexcept // TODO(correa) eliminate this ctor - : subarray{typename array_ref::types::layout_t{extensions}, dat} {} - - constexpr array_ref(typename array_ref::extensions_type extensions, typename array_ref::element_ptr dat) noexcept - : subarray{typename array_ref::types::layout_t{extensions}, dat} {} - - // template< - // class TT, std::size_t N, - // std::enable_if_t())), ElementPtr>, int> =0 // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) support legacy c-arrays - // > - // // cppcheck-suppress noExplicitConstructor ; to allow terse syntax and because a reference to c-array can be represented as an array_ref - // constexpr array_ref( // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax and because a reference to c-array can be represented as an array_ref - // TT(&array)[N] // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - // ) - // : array_ref( - // multi::data_elements(array), - // extensions(array) - // ) {} - - template< - class Array, - std::enable_if_t>, int> =0, - std::enable_if_t())), ElementPtr>, int> =0 // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) support legacy c-arrays - > - // cppcheck-suppress noExplicitConstructor ; to allow terse syntax and because a reference to c-array can be represented as an array_ref - constexpr array_ref( // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax and because a reference to c-array can be represented as an array_ref - Array& array // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - ) - : array_ref( - multi::data_elements(array), - extensions(array) - ) {} - - template =0> - // cppcheck-suppress noExplicitConstructor ; to allow terse syntax and because a reference to c-array can be represented as an array_ref - constexpr array_ref( // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : to allow terse syntax and because a reference to c-array can be represented as an array_ref - T& elem // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - ) - : array_ref(&elem, {}) {} - -// this ctor makes memcheck complain about memmory used after scope - template, int> =0> - // cppcheck-suppress noExplicitConstructor - array_ref(std::initializer_list const& il) : array_ref(il.begin(), typename array_ref::extensions_type{static_cast(il.size())}) {} - - template, int> =0> - array_ref(std::initializer_list&& il) = delete; - - using subarray::operator=; - - private: - template constexpr auto copy_elements(It first) { - return adl_copy_n(first, array_ref::num_elements(), array_ref::data_elements()); - } - - public: - HD constexpr auto data_elements() const& -> typename array_ref::element_ptr {return array_ref::base_;} - - template> ,int> =0> - constexpr auto operator=(array_ref const& other) && -> array_ref& { - assert(this->extensions() == other.extensions()); - array_ref::copy_elements(other.data_elements()); - return *this; - } - - constexpr auto operator=(array_ref const& other) & -> array_ref& { - if(this == std::addressof(other)) {return *this;} // lints(cert-oop54-cpp) - // TODO(correaa) assert on extensions, not on num elements - assert(this->num_elements() == other.num_elements()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - array_ref::copy_elements(other.data_elements()); - return *this; - } - - constexpr auto operator=(array_ref const& other) && -> array_ref& { - if(this == std::addressof(other)) {return *this;} // lints(cert-oop54-cpp) - operator=(other); - return *this; - } - - constexpr auto operator=(array_ref&& other) & // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) //NOSONAR - -> array_ref& { - if(this == std::addressof(other)) {return *this;} // lints(cert-oop54-cpp) - operator=(std::as_const(other)); - return *this; - } - constexpr auto operator=(array_ref&& other) && // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - -> array_ref& { - if(this == std::addressof(other)) {return *this;} // lints(cert-oop54-cpp) - operator=(std::as_const(other)); - return *this; - } - - template -// constexpr - auto operator=(array_ref const& other)& -> array_ref& { - assert( this->extensions() == other.extensions() ); - // MULTI_MARK_SCOPE(std::string{"multi::operator= D="}+std::to_string(D)+" from "+typeid(TT).name()+" to "+typeid(T).name() ); - adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements()); - return *this; - } - - template - constexpr auto operator=(array_ref const& other) && -> array_ref& { - this->operator=(other); - return *this; // lints (cppcoreguidelines-c-copy-assignment-signature) - } - - using elements_type = array_ref; - using celements_type = array_ref; - - private: - constexpr auto elements_aux() const { - return elements_type{ - this->data_elements(), - typename elements_type::extensions_type{multi::iextension{this->num_elements()}} - }; - } - - public: - constexpr auto elements() const& -> celements_type {return elements_aux();} - constexpr auto elements() & -> elements_type {return elements_aux();} - constexpr auto elements() && -> elements_type {return elements_aux();} - - friend constexpr auto elements(array_ref & self) -> elements_type {return self . elements();} - friend constexpr auto elements(array_ref && self) -> elements_type {return std::move(self). elements();} - friend constexpr auto elements(array_ref const& self) -> celements_type {return self . elements();} - - constexpr auto celements() const& {return celements_type{array_ref::data_elements(), array_ref::num_elements()};} - friend constexpr auto celements(array_ref const& self) {return self.celements();} - - template - /*[[gnu::pure]]*/ friend constexpr auto operator==(array_ref const& self, array_ref const& other) -> bool { - if(self.extensions() != other.extensions()) {return false;} // TODO(correaa) : or assert? - return adl_equal(other.data_elements(), other.data_elements() + self.num_elements(), self.data_elements()); - } - template - /*[[gnu::pure]]*/ friend constexpr auto operator!=(array_ref const& self, array_ref const& other) -> bool { - if(self.extensions() != other.extensions()) {return true;} // TODO(correaa) : or assert? - return not adl_equal(other.data_elements(), other.data_elements() + self.num_elements(), self.data_elements()); - } - - HD constexpr auto data_elements() && -> typename array_ref::element_ptr {return array_ref::base_;} - friend constexpr auto data_elements(array_ref&& self) -> typename array_ref::element_ptr {return std::move(self).data_elements();} - - // data() is here for compatibility with std::vector - template = 0> constexpr auto data() const& {return data_elements();} - template = 0> constexpr auto data() && {return data_elements();} - template = 0> constexpr auto data() & {return data_elements();} - - // TODO(correaa) : find a way to use [[deprecated("use data_elements()")]] for friend functions - friend constexpr auto data(array_ref const& self) -> typename array_ref::element_ptr {return self .data_elements();} - friend constexpr auto data(array_ref & self) -> typename array_ref::element_ptr {return self .data_elements();} - friend constexpr auto data(array_ref && self) -> typename array_ref::element_ptr {return std::move(self).data_elements();} - - using decay_type = typename array_ref::decay_type; - - constexpr auto decay() const& -> decay_type const& {return static_cast(*this);} - friend constexpr auto decay(array_ref const& self) -> decay_type const& {return self.decay();} - - private: - template - void check_sizes() const { - if(static_cast(std::get

(this->sizes())) != static_cast(std::extent(DD)>::value)) { - throw std::bad_cast{}; - } - if constexpr(DD + 1 != D) { - check_sizes(); - } - } - template - constexpr auto to_carray() const -> TTN& { - check_sizes(); - return reinterpret_cast(*array_ref::base_); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - } - - public: - template, int> = 0> - constexpr explicit operator TTN const&() const& { return to_carray(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - template, int> = 0> - constexpr explicit operator TTN&() && { return to_carray(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - template, int> = 0> - constexpr explicit operator TTN&() & { return to_carray(); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - private: - template - auto serialize_structured(Ar& arxiv, unsigned int const version) { - subarray::serialize(arxiv, version); - } - template - auto serialize_flat(Archive& arxiv, unsigned int const /*version*/) { - using AT = multi::archive_traits; - arxiv & AT::make_nvp("elements", AT::make_array(this->data_elements(), static_cast(this->num_elements()))); - } -// template> -// auto serialize_binary_if(std::true_type, Ar& ar) { -// ar & AT::make_nvp("binary_data", AT::make_binary_object(this->data_elements(), static_cast(this->num_elements())*sizeof(typename array_ref::element))); -// } -// template -// auto serialize_binary_if(std::false_type, Ar& ar) {return serialize_flat(ar);} - - public: - template - auto serialize(Archive& arxiv, unsigned int const version) { - serialize_flat(arxiv, version); -// serialize_structured(ar, version); -// switch(version) { -// case static_cast( 0): return serialize_flat(arxiv); -// case static_cast(-1): return serialize_structured(arxiv, version); -// // case 2: return serialize_binary_if(std::is_trivially_copy_assignable{}, arxiv); -// default: -// if( this->num_elements() <= version ){serialize_structured(arxiv, version);} -// else {serialize_flat (arxiv );} -// } - } -}; - -template -using array_cref = array_ref< - std::decay_t, D, - typename std::pointer_traits::template rebind ->; - -template -using array_mref = array_ref< - std::decay_t, D, - std::move_iterator ->; - -template -constexpr auto ref( - TT(&arr)[N] // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) interact with legacy // NOSONAR -) { - return array_ref, std::rank_v>(arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) interact with legacy -} - -template -struct array_ptr -: subarray_ptr -, typename array_ref::layout_t> { - using basic_ptr = subarray_ptr, typename array_ref::layout_t>; - - constexpr array_ptr(Ptr data, multi::extensions_t extensions) - : basic_ptr{data, multi::layout_t{extensions}} {} - - constexpr explicit array_ptr(std::nullptr_t nil) : array_ptr{nil, multi::extensions_t{}} {} - - template - constexpr explicit array_ptr(CArray* data) : array_ptr{data_elements(*data), extensions(*data)} {} - - template< - class TT, std::size_t N, - std::enable_if_t())), Ptr>,int> =0 // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) support legacy c-arrays - > - // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) array_ptr is more general than pointer c-array - constexpr array_ptr(TT(*array)[N]) : array_ptr{data_elements(*array), extensions(*array)} {} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) support legacy c-arrays // NOSONAR - - constexpr auto operator*() const { - return array_ref{this->base(), (*this)->extensions()}; - } -}; - -template -class array_ptr { // TODO(correaa) make it private mutable member - mutable multi::array_ref ref_; - - public: - constexpr explicit array_ptr(Ptr dat, typename multi::array_ref::extensions_type extensions) : ref_(dat, extensions) {} - constexpr explicit array_ptr(Ptr dat) : array_ptr(dat, typename multi::array_ref::extensions_type{}) {} - - constexpr explicit operator bool() const {return this->base();} - constexpr explicit operator Ptr () const {return this->base();} - - friend constexpr auto operator==(array_ptr const& self, array_ptr const& other) -> bool {return self.ref_.base() == other.ref_.base();} - friend constexpr auto operator!=(array_ptr const& self, array_ptr const& other) -> bool {return self.ref_.base() != other.ref_.base();} - - constexpr auto operator* () const -> multi::array_ref& {return ref_;} // NOLINT(cppcoreguidelines-pro-type-const-cast) : TODO(correaa) make ref base class a mutable member - constexpr auto operator->() const -> multi::array_ref* {return &ref_;} // NOLINT(cppcoreguidelines-pro-type-const-cast) : TODO(correaa) make ref base class a mutable member -}; - -template -constexpr auto addressof(TT(&array)[N]) { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - return array_ptr< - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - std::decay_t>, static_cast(std::rank{}), std::remove_all_extents_t* - >{&array}; -} - -template -using array_cptr = array_ptr::template rebind>; - -template -constexpr auto make_array_ref(P data, multi::extensions_t extensions) { - return array_ref::value_type, D, P>(data, extensions); -} - -template auto make_array_ref(P data, extensions_t<0> exts) {return make_array_ref<0>(data, exts);} -template auto make_array_ref(P data, extensions_t<1> exts) {return make_array_ref<1>(data, exts);} -template auto make_array_ref(P data, extensions_t<2> exts) {return make_array_ref<2>(data, exts);} -template auto make_array_ref(P data, extensions_t<3> exts) {return make_array_ref<3>(data, exts);} -template auto make_array_ref(P data, extensions_t<4> exts) {return make_array_ref<4>(data, exts);} -template auto make_array_ref(P data, extensions_t<5> exts) {return make_array_ref<5>(data, exts);} - -// In ICC you need to specify the dimensionality in make_array_ref -// #if defined(__INTEL_COMPILER) -// template -// auto make_array_ref(P p, std::initializer_list il){return make_array_ref(p, detail::to_tuple(il));} -// template -// auto make_array_ref(P p, std::initializer_list il){return make_array_ref(p, detail::to_tuple(il));} -// #endif - -#if defined(__cpp_deduction_guides) - -template::value_type> // pointer_traits doesn't have ::value_type -array_ptr(It)->array_ptr; - -template::value_type> // pointer_traits doesn't have ::value_type -array_ptr(It, index_extensions<0>)->array_ptr; - -template::value_type> -array_ptr(It, index_extensions<1>)->array_ptr; -template::value_type> -array_ptr(It, index_extensions<2>)->array_ptr; -template::value_type> -array_ptr(It, index_extensions<3>)->array_ptr; - -template< - class T, std::size_t N, - typename V = std::remove_all_extents_t, std::size_t D = std::rank_v // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility -> -array_ptr(T(*)[N])->array_ptr(D)>; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - -template array_ref(Ptr, index_extensions<0>) -> array_ref::value_type, 0, Ptr>; -template array_ref(Ptr, index_extensions<1>) -> array_ref::value_type, 1, Ptr>; -template array_ref(Ptr, index_extensions<2>) -> array_ref::value_type, 2, Ptr>; -template array_ref(Ptr, index_extensions<3>) -> array_ref::value_type, 3, Ptr>; -template array_ref(Ptr, index_extensions<4>) -> array_ref::value_type, 4, Ptr>; -template array_ref(Ptr, index_extensions<5>) -> array_ref::value_type, 5, Ptr>; - -template array_ref(It, Tuple)->array_ref::value_type, std::tuple_size::value, It>; -#endif - -// TODO(correaa) move to utility -template -constexpr auto rotated(const T(&array)[N]) noexcept { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - return multi::array_ref, std::rank{}, decltype(base(array))>( // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - base(array), extensions(array) - ).rotated(); -} -template -constexpr auto rotated(T(&array)[N]) noexcept { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - return multi::array_ref, std::rank{}, decltype(base(array))>( // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : backwards compatibility - base(array), extensions(array) - ).rotated(); -} - -template -constexpr auto operator/(RandomAccessIterator data, multi::extensions_t extensions) --> multi::array_ptr::value_type, D, RandomAccessIterator> -{return {data, extensions};} - -template -constexpr auto is_subarray_aux(subarray const&) -> std::true_type; -constexpr auto is_subarray_aux(... ) -> std::false_type; - -template struct is_subarray: decltype(is_subarray_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - -template 1)>, class = decltype((void)adl_begin(*In{}), adl_end(*In{}))> -constexpr auto uninitialized_copy -// require N>1 (this is important because it forces calling placement new on the pointer -(In first, In last, multi::array_iterator dest) { - while(first != last) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - adl_uninitialized_copy(adl_begin(*first), adl_end(*first), adl_begin(*dest)); - ++first; - ++dest; - } - return dest; -} - -// begin and end for forwarding reference are needed in this namespace -// to overwrite the behavior of std::begin and std::end -// which take rvalue-references as const-references. - -template auto begin(T&& rng) -> decltype(std::forward(rng).begin()) {return std::forward(rng).begin();} -template auto end (T&& rng) -> decltype(std::forward(rng).end() ) {return std::forward(rng).end() ;} - -template -auto transposed(T(&array)[N][M]) -> decltype(auto) {return ~multi::array_ref(array);} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - -} // end namespace boost::multi - -#ifndef MULTI_SERIALIZATION_ARRAY_VERSION -#define MULTI_SERIALIZATION_ARRAY_VERSION 0 // NOLINT(cppcoreguidelines-macro-usage) gives user opportunity to select serialization version //NOSONAR -#endif - -// #define MULTI_SERIALIZATION_ARRAY_VERSION 0 // save data as flat array -// #define MULTI_SERIALIZATION_ARRAY_VERSION -1 // save data as structured nested labels array -// #define MULTI_SERIALIZATION_ARRAY_VERSION 16 // any other value, structure for N <= 16, flat otherwise N > 16 - -#endif // MULTI_ARRAY_REF_HPP_ diff --git a/include/multi/complex.hpp b/include/multi/complex.hpp deleted file mode 100644 index 5a249e848..000000000 --- a/include/multi/complex.hpp +++ /dev/null @@ -1,210 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa - -#ifndef MULTI_COMPLEX_HPP -#define MULTI_COMPLEX_HPP - -#include "array_ref.hpp" - -// #include "detail/fix_complex_traits.hpp" - -#include -#include // for forward - -namespace boost { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat -namespace multi { - -constexpr class adl_conj_t { - template constexpr auto _(priority<1>/**/, As&&... args) const JUSTRETURN( std:: conj(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( conj(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).conj(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<3>{}, std::forward(args)...)) -} adl_conj; - -constexpr class adl_real_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std::real(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( real(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).real(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<3>{}, std::forward(args)...)) -} adl_real; - -constexpr class adl_imag_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std::imag(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( imag(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).imag(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<3>{}, std::forward(args)...)) -} adl_imag; - -struct real_t; -struct imag_t; - -template -struct _complex { // NOLINT(readability-identifier-naming) deprecating this - using value_type = ValueType; - - private: - value_type re; - value_type im; - - public: - _complex() = default; - - constexpr explicit _complex(value_type real) : re{real}, im{value_type{0}} {} - constexpr _complex(value_type real, value_type imag) // NOLINT(bugprone-easily-swappable-parameters) - : re{real}, im{imag} {} - - constexpr explicit _complex(std::complex const& other) : re{other.real()}, im{other.imag()} {} - - template< - class T, - std::enable_if_t< - sizeof(T)==2*sizeof(value_type) and - std::is_assignable().real())>{} and - std::is_assignable().imag())>{}, int - > =0 - > - constexpr explicit operator T const&() const& { - return reinterpret_cast(*this); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - } - template< - class T, - std::enable_if_t< - sizeof(T)==2*sizeof(value_type) and - std::is_assignable().real())>{} and - std::is_assignable().imag())>{}, int - > = 0 - > - constexpr explicit operator T&()& {return reinterpret_cast(*this);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - - constexpr auto std() const& -> std::complex const& { - return reinterpret_cast const&>(*this); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - } - constexpr auto std() & -> std::complex & { - return reinterpret_cast &>(*this); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - } - - friend constexpr auto abs(_complex const& self) {return abs(self.std());} - friend constexpr auto operator-(_complex const& self, _complex const& other) - -> _complex{return self.std() - other.std();} - - constexpr auto real() & -> value_type & {return re;} - constexpr auto real() const& -> value_type const& {return re;} - - constexpr auto imag() & -> value_type & {return im;} - constexpr auto imag() const& -> value_type const& {return im;} - - template constexpr auto operator+=(Real const& other)&->decltype(re += other, *this) {return re += other, *this;} - template constexpr auto operator-=(Real const& other)&->decltype(re -= other, *this) {return re -= other, *this;} - template constexpr auto operator*=(Real const& other)&->decltype(re *= other, im *= other, *this) {return re *= other, im *= other, *this;} - template constexpr auto operator/=(Real const& other)&->decltype(re /= other, im /= other, *this) {return re /= other, im /= other, *this;} - - template constexpr auto operator+=(Complex const& other)&->decltype(re += other.re, im += other.im, *this) {return re += other.re, im += other.im, *this;} - template constexpr auto operator-=(Complex const& other)&->decltype(re -= other.re, im -= other.im, *this) {return re -= other.re, im -= other.im, *this;} -}; - -struct real_t { - template::element, typename ValueType = typename E::value_type> - constexpr auto operator()(Array&& array) const - ->decltype(std::forward(array).template reinterpret_array_cast<_complex>().template member_cast(&_complex::real)) { - return std::forward(array).template reinterpret_array_cast<_complex>().template member_cast(&_complex::real); } - template::value_type, - std::enable_if_t< - sizeof(T)==2*sizeof(ValueType) and - std::is_assignable()))>{} and - std::is_assignable()))>{}, int - > =0 - > - constexpr auto operator()(T& value) const -> ValueType& {return reinterpret_cast&>(value).real;} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : TODO(correaa) : t[0] - template::value_type, - std::enable_if_t< - sizeof(T)==2*sizeof(ValueType) and - std::is_assignable()))>{} and - std::is_assignable()))>{}, int - > =0 - > - auto operator()(T const& value) const -> ValueType const& { - return reinterpret_cast const&>(value).real; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : TODO(correaa) : t[0] - } -}; - -struct imag_t { - template::element, typename ValueType = typename E::value_type> - constexpr auto operator()(Array&& array) const - ->decltype(std::forward(array).template reinterpret_array_cast<_complex>().template member_cast(&_complex::imag)) { - return std::forward(array).template reinterpret_array_cast<_complex>().template member_cast(&_complex::imag); } - template::value_type, - std::enable_if_t< - sizeof(T) == 2*sizeof(ValueType) and - std::is_assignable()))>{} and - std::is_assignable()))>{}, int - > =0 - > - constexpr auto operator()(T& value) const -> ValueType& { - return reinterpret_cast&>(value).imag; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : TODO(correaa) : t[1] - } - template::value_type, - std::enable_if_t< - sizeof(T)==2*sizeof(ValueType) and - std::is_assignable()))>{} and - std::is_assignable()))>{}, int - > =0 - > - constexpr auto operator()(T const& value) const -> ValueType const&{ - return reinterpret_cast const&>(value).imag; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : TODO(correaa) : t[1] - } -}; - -//[[maybe_unused]] static constexpr real_t real; -//[[maybe_unused]] static constexpr imag_t imag; - -} // end namespace multi -} // end namespace boost - -// static_assert( boost::multi::is_trivially_default_constructible>::value ); -// static_assert( boost::multi::is_trivially_default_constructible>::value ); - -// static_assert( boost::multi::is_trivial>::value ); -// static_assert( boost::multi::is_trivial>::value ); - - -#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ - -#include -#include "array.hpp" - -namespace multi = boost::multi; - -template void what(T&&)=delete; - -int main() { - - using complex = multi::complex; - - multi::array A = { - { {1. , 2.}, {3., 4.} }, - { {22., 33.}, {5., 9.} } - }; - - { - auto&& Areal = A.member_cast(&multi::complex::re); - auto&& Aimag = A.member_cast(&multi::complex::im); - - assert( Areal[1][0] == 22. ); - assert( Aimag[1][0] == 33. ); - } { - auto&& Areal = A.member_cast(&multi::complex::re); - auto&& Aimag = A.member_cast(&multi::complex::im); - - assert( Areal[1][0] == 22. ); - assert( Aimag[1][0] == 33. ); - } -} - -#endif -#endif diff --git a/include/multi/config/ASSERT.hpp b/include/multi/config/ASSERT.hpp deleted file mode 100644 index dacf15b4b..000000000 --- a/include/multi/config/ASSERT.hpp +++ /dev/null @@ -1,16 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_CONFIG_ASSERT_HPP_ -#define MULTI_CONFIG_ASSERT_HPP_ - -#include - -#if defined(MULTI_ACCESS_NDEBUG) or defined(__CUDACC__) - #define MULTI_ACCESS_ASSERT(Expr) -#else - // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) this is for very inefficient asserts - #define MULTI_ACCESS_ASSERT(Expr) assert(Expr) -#endif - -#endif // MULTI_CONFIG_ASSERT_HPP_ diff --git a/include/multi/config/DELETE.hpp b/include/multi/config/DELETE.hpp deleted file mode 100644 index 93f826c6f..000000000 --- a/include/multi/config/DELETE.hpp +++ /dev/null @@ -1,17 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2021 Alfredo A. Correa - -#ifndef MULTI_CONFIG_DELETE_HPP_ -#define MULTI_CONFIG_DELETE_HPP_ - -namespace boost::multi { - -template struct disable_if_impl{}; -template struct disable_if_impl{using type = T;}; - -template using disable_if = typename disable_if_impl::type; - -} // end namespace boost::multi - -#define DELETE(ConD) boost::multi::disable_if =0 // NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) remove -#endif // MULTI_CONFIG_DELETE_HPP_ diff --git a/include/multi/config/DEPRECATED.hpp b/include/multi/config/DEPRECATED.hpp deleted file mode 100644 index b5a588891..000000000 --- a/include/multi/config/DEPRECATED.hpp +++ /dev/null @@ -1,59 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -#ifndef MULTI_CONFIG_DEPRECATED_HPP_ -#define MULTI_CONFIG_DEPRECATED_HPP_ -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef __has_cpp_attribute -#define __has_cpp_attribute(name) 0 -#endif - -#define DEPRECATED(MsG) [[deprecated]] - -// #ifdef __NVCC__ -// #define DEPRECATED(MsG) __attribute__((deprecated)) -// #else -// #if __has_cpp_attribute(deprecated) -// #define DEPRECATED(MsG) [[deprecated(MsG)]] -// #else -// #define DEPRECATED(MsG) -// #endif -// #endif - -#if not defined(__INTEL_COMPILER) -#define BEGIN_NO_DEPRECATED \ -\ -_Pragma("GCC diagnostic push") \ -_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \ -\ - -#else -#define BEGIN_NO_DEPRECATED \ -_Pragma("warning push") \ -_Pragma("warning disable 1786") \ - -#endif - -#if not defined(__INTEL_COMPILER) -#define END_NO_DEPRECATED \ -\ -_Pragma("GCC diagnostic pop") \ -\ - -#else -#define END_NO_DEPRECATED \ -\ -_Pragma("warning pop") \ -\ - -#endif - -#define BEGIN_CUDA_SLOW BEGIN_NO_DEPRECATED -#define END_CUDA_SLOW END_NO_DEPRECATED - -#define NO_DEPRECATED(ExpR) \ - BEGIN_NO_DEPRECATED \ - ExpR \ - END_NO_DEPRECATED - -#endif // MULTI_CONFIG_DEPRECATED_HPP_ - diff --git a/include/multi/config/MARK.hpp b/include/multi/config/MARK.hpp deleted file mode 100644 index fb00bb6c4..000000000 --- a/include/multi/config/MARK.hpp +++ /dev/null @@ -1,15 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2022 Alfredo A. Correa -// #ifndef MULTI_CONFIG_MARK_HPP -// #define MULTI_CONFIG_MARK_HPP - -// NOLINT(build/header_guard) -#ifndef MULTI_MARK_SCOPE // NOLINT(llvm-header-guard) this is a configuration header, can be included many times - #ifdef CALI_CXX_MARK_SCOPE - #define MULTI_MARK_SCOPE(MsG) CALI_CXX_MARK_SCOPE(MsG) - #else - #define MULTI_MARK_SCOPE(MsG) ((void)0) // NOLINT(cppcoreguidelines-macro-usage) to mark scopes - #endif -#endif - -// #endif diff --git a/include/multi/config/MAYBE_UNUSED.hpp b/include/multi/config/MAYBE_UNUSED.hpp deleted file mode 100644 index 7028d1ee2..000000000 --- a/include/multi/config/MAYBE_UNUSED.hpp +++ /dev/null @@ -1,26 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2022 Alfredo A. Correa - -#ifndef MULTI_CONFIG_MAYBE_UNUSED_HPP_ -#define MULTI_CONFIG_MAYBE_UNUSED_HPP_ - -#ifndef __has_cpp_attribute -#define __has_cpp_attribute(name) 0 -#endif - -#if (__has_cpp_attribute(maybe_unused)) and (__cplusplus>=201703L) - #define MULTI_MAYBE_UNUSED [[maybe_unused]] // NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) check if this is really necessary in C++17 -#elif __has_cpp_attribute(gnu::unused) - #define MULTI_MAYBE_UNUSED [[gnu::unused]] // NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) check if this is really necessary in C++17 -#elif __has_cpp_attribute(__attribute__((unused))) - #define MULTI_MAYBE_UNUSED __attribute__((unused)) // NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) check if this is really necessary in C++17 -#else - #define MULTI_MAYBE_UNUSED // NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) check if this is really necessary in C++17 -#endif - -#ifndef MAYBE_UNUSED - #define MAYBE_UNUSED MULTI_MAYBE_UNUSED // NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) check if this is really necessary in C++17 -#endif - -#endif // MULTI_CONFIG_MAYBE_UNUSED_HPP_ - diff --git a/include/multi/config/NODISCARD.hpp b/include/multi/config/NODISCARD.hpp deleted file mode 100644 index 159b296bd..000000000 --- a/include/multi/config/NODISCARD.hpp +++ /dev/null @@ -1,39 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_CONFIG_NODISCARD_HPP_ -#define MULTI_CONFIG_NODISCARD_HPP_ - -#ifndef __has_cpp_attribute -#define __has_cpp_attribute(name) 0 -#endif - -#ifndef NODISCARD -#if defined(__NVCC__) - #define NODISCARD(MsG) -#elif (__has_cpp_attribute(nodiscard) and (__cplusplus>=201703L)) - #if (__has_cpp_attribute(nodiscard)>=201907) and (__cplusplus>201703L) - #define NODISCARD(MsG) [[nodiscard]] // [[nodiscard(MsG)]] in c++20 empty message is not allowed with paren - #else - #define NODISCARD(MsG) [[nodiscard]] // NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) check if this is needed in C++17 - #endif -#elif __has_cpp_attribute(gnu::warn_unused_result) - #define NODISCARD(MsG) [[gnu::warn_unused_result]] -#else - #define NODISCARD(MsG) -#endif -#endif - -#ifndef NODISCARD_CLASS - #if(__has_cpp_attribute(nodiscard) and not defined(__NVCC__) and (not defined(__clang__) or (defined(__clang__) and (__cplusplus >= 202002L)))) - #if (__has_cpp_attribute(nodiscard)>=201907) - #define NODISCARD_CLASS(MsG) [[nodiscard_(MsG)]] - #else - #define NODISCARD_CLASS(MsG) [[nodiscard]] - #endif - #else - #define NODISCARD_CLASS(MsG) - #endif -#endif - -#endif // MULTI_CONFIG_NODISCARD_HPP_ diff --git a/include/multi/config/NO_UNIQUE_ADDRESS.hpp b/include/multi/config/NO_UNIQUE_ADDRESS.hpp deleted file mode 100644 index f7696f5be..000000000 --- a/include/multi/config/NO_UNIQUE_ADDRESS.hpp +++ /dev/null @@ -1,19 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_CONFIG_NO_UNIQUE_ADDRESS_HPP_ -#define MULTI_CONFIG_NO_UNIQUE_ADDRESS_HPP_ - -#ifndef __has_cpp_attribute -#define __has_cpp_attribute(name) 0 -#endif - -#if __has_cpp_attribute(no_unique_address) >=201803 and not defined(__NVCC__) and not defined(__PGI) - // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) this macro will be needed until C++20 - #define MULTI_NO_UNIQUE_ADDRESS [[no_unique_address]] -#else - // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) this macro will be needed until C++20 - #define MULTI_NO_UNIQUE_ADDRESS -#endif - -#endif // MULTI_CONFIG_NO_UNIQUE_ADDRESS_HPP_ diff --git a/include/multi/config/VERSION.hpp b/include/multi/config/VERSION.hpp deleted file mode 100644 index 784642eac..000000000 --- a/include/multi/config/VERSION.hpp +++ /dev/null @@ -1,13 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022-2023 Alfredo A. Correa - -#ifndef MULTI_CONFIG_VERSION_HPP_ -#define MULTI_CONFIG_VERSION_HPP_ - -// NOLINTBEGIN(cppcoreguidelines-macro-usage,modernize-macro-to-enum) indicate library version -#define MULTI_VERSION_MAJOR 0 -#define MULTI_VERSION_MINOR 81 -#define MULTI_VERSION_MICRO 0 -// NOLINTEND(cppcoreguidelines-macro-usage,modernize-macro-to-enum) - -#endif // MULTI_CONFIG_VERSION_HPP_ diff --git a/include/multi/detail/adl.hpp b/include/multi/detail/adl.hpp deleted file mode 100644 index 044f80012..000000000 --- a/include/multi/detail/adl.hpp +++ /dev/null @@ -1,633 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa - -#ifndef MULTI_DETAIL_ADL_HPP -#define MULTI_DETAIL_ADL_HPP -#pragma once - -#include // std::size_t -#include // std::conditional_t -#include - -// #include - -#if defined(__NVCC__) || defined(__HIP_PLATFORM_AMD__) -#include -#include -#include -#include -#include -#endif - -#include // for std::copy, std::copy_n, std::equal, etc -#include // for begin, end -#include // for uninitialized_copy, etc - -#define BOOST_MULTI_DEFINE_ADL(FuN) /*NOLINT(cppcoreguidelines-macro-usage) TODO(correaa) consider replacing for all ADL'd operations*/ \ -namespace boost { \ -namespace multi { \ -namespace adl { \ - namespace custom {template struct FuN##_t;} __attribute__((unused)) \ - static constexpr class FuN##_t { \ - template [[deprecated]] auto _(priority<0>, As&&... args) const = delete; \ - template auto _(priority<1>, As&&... args) const DECLRETURN(std::FuN(std::forward(args)...)) \ - template auto _(priority<2>, As&&... args) const DECLRETURN( FuN(std::forward(args)...)) \ - template auto _(priority<3>, T&& t, As&&... args) const DECLRETURN(std::forward(t).FuN(std::forward(args)...)) \ - template auto _(priority<4>, As&&... args) const DECLRETURN(custom::FuN##_t::_(std::forward(args)...)) \ - public: \ - template auto operator()(As&&... args) const-> decltype(_(priority<4>{}, std::forward(args)...)) {return _(priority<4>{}, std::forward(args)...);} \ - } (FuN); \ -} /* end namespace adl */ \ -} /* end namespace multi */ \ -} /* end namespace boost */ - -namespace boost::multi { - -template struct priority : std::conditional_t> {}; - -#define DECLRETURN(ExpR) -> decltype(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing -#define JUSTRETURN(ExpR) {return ExpR;} // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing - -constexpr class adl_copy_n_t { - template constexpr auto _(priority<0>/**/, As&&... args) const DECLRETURN(std:: copy_n( std::forward(args)...)) -#if defined(__NVCC__) - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN(::thrust:: copy_n( std::forward(args)...)) -#endif - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( copy_n( std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::decay_t:: copy_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).copy_n( std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_copy_n; - -[[maybe_unused]] constexpr class adl_move_t { - template constexpr auto _(priority<0>/**/, As&&... args) const DECLRETURN( std:: move( std::forward(args)...)) -#if defined(__NVCC__) // there is no thrust::move algorithm - template constexpr auto _(priority<1>/**/, It first, It last, As&&... args) const DECLRETURN( thrust::copy(std::make_move_iterator(first), std::make_move_iterator(last), std::forward(args)...)) -#endif - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( move( std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::decay_t:: move(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).move( std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_move; - -constexpr class adl_fill_n_t { - template< class... As> constexpr auto _(priority<0>/**/, As&&... args) const DECLRETURN( std:: fill_n (std::forward(args)...)) -#if defined(__NVCC__) - template< class... As> constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( thrust:: fill_n (std::forward(args)...)) -#endif - template< class... As> constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( fill_n (std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::decay_t:: fill_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).fill_n (std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_fill_n; - -constexpr class adl_equal_t { - template< class...As> /*[[gnu::pure]]*/ constexpr auto _(priority<1>/**/, As&&...args) const DECLRETURN( std:: equal( std::forward(args)...)) -#if defined(__NVCC__) - template< class...As> constexpr auto _(priority<2>/**/, As&&...args) const DECLRETURN( ::thrust:: equal( std::forward(args)...)) -#endif - template< class...As> /*[[gnu::pure]]*/ constexpr auto _(priority<3>/**/, As&&...args) const DECLRETURN( equal( std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&...args) const DECLRETURN( std::decay_t:: equal(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<5>/**/, T&& arg, As&&...args) const DECLRETURN( std::forward(arg).equal( std::forward(args)...)) - - public: - template /*[[gnu::pure]]*/ constexpr auto operator()(As&&...args) const DECLRETURN(_(priority<5>{}, std::forward(args)...)) -} adl_equal; - -template struct adl_custom_copy; - -template > void copy(As...) = delete; - -constexpr class adl_copy_t { - template::reference, typename std::iterator_traits::reference>> - > - constexpr auto _(priority<1>/**/, InputIt first, InputIt last, OutputIt d_first) const DECLRETURN(std::copy(first, last, d_first)) -#if defined(__NVCC__) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( thrust::copy(std::forward(args)...)) -#endif - template< class... As> constexpr auto _(priority<3>/**/, As&&... args) const DECLRETURN( copy(std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t::copy(std::forward(arg), std::forward(args)...)) -// template constexpr auto _(priority<5>/**/, As&&... args) const DECLRETURN(boost::multi::adl_custom_copy...>::copy(std::forward(as)...)) - template constexpr auto _(priority<6>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).copy(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN( _(priority<6>{}, std::forward(args)...) ) \ -} adl_copy; - -namespace adl { - namespace custom {template struct fill_t;} - static constexpr class fill_t { - template auto _(priority<1>/**/, As&&... args) const DECLRETURN( std:: fill (std::forward(args)...)) - template auto _(priority<2>/**/, As&&... args) const DECLRETURN( fill (std::forward(args)...)) - template auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).fill (std::forward(args)...)) - template auto _(priority<4>/**/, As&&... args) const DECLRETURN(custom:: fill_t::_(std::forward(args)...)) - - public: - template auto operator()(As&&... args) const DECLRETURN(_(priority<5>{}, std::forward(args)...)) - } fill [[maybe_unused]]; -} // end namespace adl - -template -struct alloc_construct_elem_t { - Alloc* palloc_; - template auto operator()(T&& ptr) const - ->decltype(std::allocator_traits::construct(*palloc_, std::addressof(ptr))) { - return std::allocator_traits::construct(*palloc_, std::addressof(ptr)); } -}; - -namespace xtd { - -template // this one goes last!!! -constexpr auto to_address(T const& ptr) noexcept; - -template -constexpr auto me_to_address(priority<0> /**/, T const& ptr) noexcept - -> decltype(to_address(ptr.operator->())) { - return to_address(ptr.operator->()); -} - -template -constexpr auto me_to_address(priority<1> /**/, T const& ptr) noexcept - -> decltype(std::pointer_traits::to_address(ptr)) { - return std::pointer_traits::to_address(ptr); -} - -template{}, int> = 0> -constexpr auto me_to_address(priority<2>/**/, T const& ptr) noexcept -> T { - static_assert(not std::is_function_v, "!"); - return ptr; -} - -template // this one goes last!!! -constexpr auto to_address(T const& ptr) noexcept -->decltype(me_to_address(priority<2>{}/**/, ptr)) { - return me_to_address(priority<2>{} , ptr); } - -template::value_type, typename = decltype(std::addressof(*ForwardIt{})), typename = decltype(Value())> -auto alloc_uninitialized_value_construct_n(Alloc& alloc, ForwardIt first, Size count) -> ForwardIt { -// ->std::decay_t::construct(alloc, std::addressof(*first), Value()), first)> - ForwardIt current = first; - try { - for (; count > 0 ; ++current, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::construct(alloc, std::addressof(*current), Value()); // !!!!!!!!!!!!!! if you are using std::complex type consider making complex default constructible (e.g. by type traits) - } - // ::new (static_cast(std::addressof(*current))) Value(); - return current; - } catch(...) { - for(; current != first; ++first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::destroy(alloc, std::addressof(*first)); - } - throw; - } -} - -// #if defined __NVCC__ // in place of global -Xcudafe \"--diag_suppress=implicit_return_from_non_void_function\" -// #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ -// #pragma nv_diagnostic push -// #pragma nv_diag_suppress = code_is_unreachable -// #else -// #pragma diagnostic push -// #pragma diag_suppress = code_is_unreachable -// #endif -// #elif defined __NVCOMPILER -// #pragma diagnostic push -// #pragma diag_suppress = code_is_unreachable -// #endif -template::value_type> -auto alloc_uninitialized_default_construct_n(Alloc& alloc, ForwardIt first, Size count) --> std::decay_t::construct(alloc, std::addressof(*first)), first)> { - if(std::is_trivially_default_constructible_v) { - std::advance(first, count); - return first; - } - using alloc_traits = std::allocator_traits; - ForwardIt current = first; - try { - // return std::for_each_n(first, count, [&](T& elem) { alloc_traits::construct(alloc, std::addressof(elem)); ++current; }); - // workadoung for gcc 8.3.1 in Lass - std::for_each(first, first + count, [&](T& elem) { alloc_traits::construct(alloc, std::addressof(elem)); ++current; }); - return first + count; - } - // LCOV_EXCL_START // TODO(correaa) add test - catch(...) { - std::for_each(first, current, [&](T& elem) { alloc_traits::destroy(alloc, std::addressof(elem)); }); - throw; - } - // LCOV_EXCL_STOP - // return current; -} -// #if defined __NVCC__ -// #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ -// #pragma nv_diagnostic pop -// #else -// #pragma diagnostic pop -// #endif -// #elif defined __NVCOMPILER -// #pragma diagnostic pop -// #endif - -} // end namespace xtd - -template struct alloc_destroy_elem_t { - Alloc* palloc_; - template constexpr auto operator()(T&& ptr) const { // ->decltype(std::allocator_traits::construct(*palloc_, std::forward(t)...)){ - return std::allocator_traits::destroy(*palloc_, std::addressof(ptr)); - } -}; - -template::value_type> -constexpr auto destroy_n(BidirIt first, Size count) -->std::decay_t { - first += count; - for(; count != 0; --first, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::addressof(*(first-1))->~T(); - } - return first; -} - -template::value_type> -constexpr auto alloc_destroy_n(Alloc& alloc, BidirIt first, Size count) -->std::decay_t { - first += count; - for (; count != 0; --first, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::destroy(alloc, std::addressof(*(first - 1))); - } - return first; -} - -constexpr class adl_uninitialized_copy_t { - template // sfinae friendy std::uninitialized_copy - [[nodiscard]] constexpr auto _(priority<1>/**/, InIt first, InIt last, FwdIt d_first) const DECLRETURN( std::uninitialized_copy(first, last, d_first)) -// #if defined(__NVCC__) -// template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( thrust::uninitialized_copy( std::forward(args)...)) -// #endif - template constexpr auto _(priority<3>/**/, As&&... args) const DECLRETURN( uninitialized_copy(std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: uninitialized_copy(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).uninitialized_copy(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<5>{}, std::forward(args)...)) -} adl_uninitialized_copy; - -namespace xtd { - -//template::value_type> -//auto uninitialized_copy_n(InputIt first, Size count, ForwardIt d_first) -//->std::decay_t(std::addressof(*d_first))) Value(*first), d_first)> { -// ForwardIt current = d_first; -// try { -// for (; count > 0; ++first, (void) ++current, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm -// ::new (static_cast(std::addressof(*current))) Value(*first); -// } -// } catch(...) { -// for(; d_first != current; ++d_first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm -// d_first->~Value(); -// } -// throw; -// } -// return current; -//} - -//template::value_type> -//auto uninitialized_move_n(InputIt first, Size count, ForwardIt d_first) -//->std::decay_t(std::addressof(*d_first))) Value(std::move(*first)), d_first)> { -// ForwardIt current = d_first; -// try { -// return std::for_each_n(first, count, [&](auto& elem) { ::new (static_cast(std::addressof(*current))) Value(std::move(*first));; ++current; }); -// for (; count > 0; ++first, (void) ++current, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm -// ::new (static_cast(std::addressof(*current))) Value(std::move(*first)); -// } -// } catch(...) { -// for(; d_first != current; ++d_first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm -// d_first->~Value(); -// } -// throw; -// } -// return current; -//} - -} // end namespace xtd - -constexpr class adl_uninitialized_copy_n_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std::uninitialized_copy_n(std::forward(args)...)) -#if defined(__NVCC__) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( thrust::copy_n( std::forward(args)...)) -#endif - template constexpr auto _(priority<3>/**/, As&&... args) const DECLRETURN( uninitialized_copy_n(std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::decay_t:: uninitialized_copy_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).uninitialized_copy_n(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const {return _(priority<5>{}, std::forward(args)...);} // TODO(correaa) this might trigger a compiler crash with g++ 7.5 because of operator&() && overloads -} adl_uninitialized_copy_n; - -constexpr class adl_uninitialized_move_n_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std:: uninitialized_move_n(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( uninitialized_move_n(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::decay_t:: uninitialized_move_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).uninitialized_move_n(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const {return _(priority<4>{}, std::forward(args)...);} -} adl_uninitialized_move_n; - -namespace xtd { - -template -constexpr auto alloc_uninitialized_copy_n(std::allocator& /*alloc*/, InputIt first, Size count, ForwardIt d_first) { - return adl_uninitialized_copy_n(first, count, d_first);} - -template -constexpr auto alloc_uninitialized_move_n(std::allocator& /*alloc*/, InputIt first, Size count, ForwardIt d_first) { - return adl_uninitialized_move_n(first, count, d_first);} - -template -auto alloc_uninitialized_copy_n(Alloc& alloc, InputIt first, Size count, ForwardIt d_first) { - ForwardIt current = d_first; - try { - for(; count > 0; ++first, ++current, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::construct(alloc, std::addressof(*current), *first); - } - return current; - } catch(...) { - for(; d_first != current; ++d_first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::destroy(alloc, std::addressof(*d_first)); - } - throw; - } -} - -template -auto alloc_uninitialized_move_n(Alloc& alloc, InputIt first, Size count, ForwardIt d_first) { - ForwardIt current = d_first; - try { - for(; count > 0; ++first, ++current, --count) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::construct(alloc, std::addressof(*current), std::move(*first)); - } - return current; - } catch(...) { - for(; d_first != current; ++d_first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::destroy(alloc, std::addressof(*d_first)); - } - throw; - } -} - -template -constexpr auto alloc_uninitialized_copy(std::allocator&/*allocator*/, InputIt first, InputIt last, ForwardIt d_first) { - return adl_uninitialized_copy(first, last, d_first); -} - -template())), class=std::enable_if_t::value_type, typename std::iterator_traits::reference>>> -auto alloc_uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, ForwardIt d_first) { -// ->std::decay_t // problematic in clang-11 + gcc-9 - ForwardIt current = d_first; - try { - for(; first != last; ++first, (void)++current) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits>::construct(alloc, std::addressof(*current), *first); - } - return current; - } catch(...) { - for(; d_first != current; ++d_first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits>::destroy(alloc, std::addressof(*d_first)); - } - throw; - } -} - -template -auto alloc_uninitialized_fill_n(Alloc& alloc, ForwardIt first, Size n, T const& value) -->std::decay_t::construct(alloc, std::addressof(*first), value), first)> { - ForwardIt current = first; // using std::to_address; - try { - for(; n > 0; ++current, --n) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::construct(alloc, std::addressof(*current), value); - } - return current; - } catch(...) { - for(; first != current; ++first) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - std::allocator_traits::destroy(alloc, std::addressof(*first)); - } - throw; - } -} -} // end namespace xtd - -[[maybe_unused]] constexpr class adl_distance_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std:: distance(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( distance(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: distance(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).distance(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_distance; - -constexpr class adl_begin_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std::begin(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( begin(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t::begin(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).begin(std::forward(args)...)) - - public: - template [[nodiscard]] constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_begin; - -constexpr class adl_size_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std::size(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( size(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t::size(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).size(std::forward(args)...)) - - public: - template [[nodiscard]] constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_size; - -constexpr class adl_end_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std:: end(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( end(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: end(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).end(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_end; - -constexpr class adl_swap_ranges_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std:: swap_ranges(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( swap_ranges(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: swap_ranges(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).swap_ranges(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_swap_ranges; - -constexpr class adl_lexicographical_compare_t { - template /*[[gnu::pure]]*/ constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std:: lexicographical_compare(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( lexicographical_compare(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: lexicographical_compare(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).lexicographical_compare(std::forward(args)...)) - - public: - template /*[[gnu::pure]]*/ constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_lexicographical_compare; - -constexpr class adl_uninitialized_value_construct_n_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std:: uninitialized_value_construct_n(std::forward(args)...)) // TODO(correaa) use boost alloc_X functions? - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( uninitialized_value_construct_n(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t::uninitialized_value_construct_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).uninitialized_value_construct_n(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const {return (_(priority<4>{}, std::forward(args)...));} -} adl_uninitialized_value_construct_n; - -[[maybe_unused]] constexpr class adl_alloc_uninitialized_value_construct_n_t { - template constexpr auto _(priority<1>/**/, Alloc&& /*alloc*/, As&&... args) const DECLRETURN( adl_uninitialized_value_construct_n(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( xtd:: alloc_uninitialized_value_construct_n(std::forward(args)...)) // TODO(correaa) use boost alloc_X functions? - template constexpr auto _(priority<3>/**/, As&&... args) const DECLRETURN( alloc_uninitialized_value_construct_n(std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: alloc_uninitialized_value_construct_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).alloc_uninitialized_value_construct_n(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const {return (_(priority<5>{}, std::forward(args)...));} -} adl_alloc_uninitialized_value_construct_n; - -constexpr class adl_uninitialized_default_construct_n_t { - template constexpr auto _(priority<1>/**/, As&&... args) const {return std:: uninitialized_default_construct_n( std::forward(args)...);} - // #if defined(__NVCC__) - // template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( thrust::uninitialized_default_construct_n( std::forward(args)...)) - // #endif - template constexpr auto _(priority<3>/**/, As&&... args) const DECLRETURN( uninitialized_default_construct_n( std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: uninitialized_default_construct_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).uninitialized_default_construct_n( std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const {return (_(priority<5>{}, std::forward(args)...));} -} adl_uninitialized_default_construct_n; - -[[maybe_unused]] constexpr class adl_alloc_uninitialized_default_construct_n_t { - template constexpr auto _(priority<1>/**/, Alloc&&/*unused*/, As&&... args) const JUSTRETURN( adl_uninitialized_default_construct_n(std::forward(args)...)) - #if defined(__NVCC__) - template constexpr auto _(priority<2>/**/, Alloc&& alloc, It first, Size n ) const DECLRETURN( thrust::detail::default_construct_range(std::forward(alloc), first, n)) - #endif - template constexpr auto _(priority<3>/**/, As&&... args) const DECLRETURN( xtd:: alloc_uninitialized_default_construct_n( std::forward(args)...)) // TODO(correaa) use boost alloc_X functions? - template constexpr auto _(priority<4>/**/, As&&... args) const DECLRETURN( alloc_uninitialized_default_construct_n( std::forward(args)...)) - template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: alloc_uninitialized_default_construct_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<6>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).alloc_uninitialized_default_construct_n( std::forward(args)...)) - public: - template constexpr auto operator()(As&&... args) const {return (_(priority<6>{}, std::forward(args)...));} -} adl_alloc_uninitialized_default_construct_n; - -constexpr class destroy_n_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( multi:: destroy_n (std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( destroy_n (std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t:: destroy_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).destroy_n (std::forward(args)...)) -public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<4>{}, std::forward(args)...)) -} adl_destroy_n; - -[[maybe_unused]] constexpr class alloc_destroy_n_t { - template constexpr auto _(priority<1>/**/, Alloc&&/*unused*/, As&&... args) const DECLRETURN( adl_destroy_n (std::forward(args)...)) -#if defined(__NVCC__) - template constexpr auto _(priority<2>/**/, Alloc& alloc, It first, Size n) const DECLRETURN( (thrust::detail::destroy_range(alloc, first, first + n))) -#endif -#if defined(__HIP_PLATFORM_AMD__) - template constexpr auto _(priority<3>/**/, Alloc& alloc, It first, Size n) const DECLRETURN( (thrust::detail::destroy_range(alloc, first, first + n))) -#endif - template< class... As> constexpr auto _(priority<4>/**/, As&&... args) const DECLRETURN(multi:: alloc_destroy_n (std::forward(args)...)) // TODO(correaa) use boost alloc_X functions? - template< class... As> constexpr auto _(priority<5>/**/, As&&... args) const DECLRETURN( alloc_destroy_n (std::forward(args)...)) - template constexpr auto _(priority<6>/**/, T&& arg, As&&... args) const DECLRETURN(std::decay_t:: alloc_destroy_n(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<7>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).alloc_destroy_n (std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<7>{}, std::forward(args)...)) -} adl_alloc_destroy_n; - -constexpr class adl_alloc_uninitialized_copy_t { - template constexpr auto _(priority<1>/**/, Alloc&&/*alloc*/, As&&... args) const DECLRETURN( adl_uninitialized_copy(std::forward(args)...)) -// TODO(correaa) : remove T from below? - template constexpr auto _(priority<2>/**/, T&& arg, As&&... args) const DECLRETURN( xtd::alloc_uninitialized_copy(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( alloc_uninitialized_copy(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<4>/**/, T&& arg, As&&... args) const DECLRETURN( std::decay_t::alloc_uninitialized_copy(std::forward(arg), std::forward(args)...)) - template constexpr auto _(priority<5>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).alloc_uninitialized_copy(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const DECLRETURN(_(priority<5>{}, std::forward(args)...)) -} adl_alloc_uninitialized_copy; - -[[maybe_unused]] constexpr class alloc_uninitialized_copy_n_t { - template constexpr auto _(priority<1>/**/, Alloc&&/*alloc*/, As&&... args) const DECLRETURN( uninitialized_copy_n(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( xtd::alloc_uninitialized_copy_n(std::forward(args)...)) -#if defined(__NVCC__) - template constexpr auto _(priority<3>/**/, Alloc&&/*alloc*/, As&&... as) const DECLRETURN( thrust::uninitialized_copy_n(std::forward(as)...)) -#endif -// TODO(correaa) revise - template constexpr auto _(priority<4>/**/, Alloc&&/*alloc*/, As&&... args) const DECLRETURN( uninitialized_copy_n(std::forward(args)...)) - template constexpr auto _(priority<5>/**/, As&&... args) const DECLRETURN( alloc_uninitialized_copy_n(std::forward(args)...)) - template constexpr auto _(priority<6>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).alloc_uninitialized_copy_n(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const {return _(priority<6>{}, std::forward(args)...);} -} adl_alloc_uninitialized_copy_n; - -[[maybe_unused]] constexpr class alloc_uninitialized_move_n_t { -// TODO(correaa) : fallback to no alloc version - template constexpr auto _(priority<1>/**/, As&&... args) const {return( xtd:: alloc_uninitialized_move_n(std::forward(args)...));} - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( alloc_uninitialized_move_n(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN(std::forward(arg).alloc_uninitialized_move_n(std::forward(args)...)) - - public: - template constexpr auto operator()(As&&... args) const {return _(priority<3>{}, std::forward(args)...);} \ -} adl_alloc_uninitialized_move_n; - -constexpr class uninitialized_fill_n_t { - template constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( std:: uninitialized_fill_n(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, As&&... args) const DECLRETURN( uninitialized_fill_n(std::forward(args)...)) - template constexpr auto _(priority<3>/**/, T&& arg, As&&... args) const DECLRETURN( std::forward(arg).uninitialized_fill_n(std::forward(args)...)) - - public: - template constexpr auto operator()(T1&& arg, As&&... args) const DECLRETURN(_(priority<3>{}, arg, std::forward(args)...)) -} adl_uninitialized_fill_n; - -[[maybe_unused]] constexpr class alloc_uninitialized_fill_n_t { - template< class... As> constexpr auto _(priority<1>/**/, As&&... args) const DECLRETURN( xtd::alloc_uninitialized_fill_n(std::forward(args)...)) - template constexpr auto _(priority<2>/**/, Alloc&&/*alloc*/, As&&... args) const DECLRETURN( adl_uninitialized_fill_n(std::forward(args)...)) - template< class... As> constexpr auto _(priority<3>/**/, As&&... args) const DECLRETURN( alloc_uninitialized_fill_n(std::forward(args)...)) - template constexpr auto _(priority<4>/**/, Alloc&& alloc , As&&... args) const DECLRETURN( std::forward(alloc).alloc_uninitialized_fill_n(std::forward(args)...)) - public: - template constexpr auto operator()(T1&& arg, As&&... args) const DECLRETURN(_(priority<4>{}, arg, std::forward(args)...)) -} adl_alloc_uninitialized_fill_n; - -template -struct recursive { - template - static constexpr auto alloc_uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, ForwardIt dest){ - using std::begin; using std::end; - while(first!=last) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - recursive::alloc_uninitialized_copy(alloc, begin(*first), end(*first), begin(*dest)); - ++first; - ++dest; - } - return dest; - } -}; - -template<> struct recursive<1> { - template - static auto alloc_uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, ForwardIt dest){ - return adl_alloc_uninitialized_copy(alloc, first, last, dest); - } -}; - -} // end namespace boost::multi -#endif diff --git a/include/multi/detail/config.hpp b/include/multi/detail/config.hpp deleted file mode 100644 index 7a1acca46..000000000 --- a/include/multi/detail/config.hpp +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2019-2021 Alfredo A. Correa - -#ifndef MULTI_ADAPTORS_BLAS_TEST_CONFIG_HPP -#define MULTI_ADAPTORS_BLAS_TEST_CONFIG_HPP - -#define CUDA_FOUND 1 - -#endif - diff --git a/include/multi/detail/cuda/ptr.hpp b/include/multi/detail/cuda/ptr.hpp deleted file mode 100644 index 17df9faab..000000000 --- a/include/multi/detail/cuda/ptr.hpp +++ /dev/null @@ -1,318 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS -(echo "#include\""$0"\"" > $0.cpp) && nvcc --compiler-options -std=c++14,-Wall,-Wextra,-Wpedantic`#,-Wfatal-errors` -D_TEST_BOOST_MULTI_DETAIL_MEMORY_CUDA_PTR $0.cpp -o $0x && $0x && rm $0x $0.cpp; exit -#endif - -#ifndef BOOST_MULTI_DETAIL_MEMORY_CUDA_PTR_HPP -#define BOOST_MULTI_DETAIL_MEMORY_CUDA_PTR_HPP - -#include // cudaError_t - -#include -#include // nullptr_t -#include // random_access_iterator_tag - -#include // is_const - -namespace boost { -namespace multi {namespace detail { -namespace memory {namespace cuda { - -template struct ref; - -//template class allocator; - -template struct ptr; - -template -class ptr { - protected: - using impl_t = Ptr; - impl_t impl_; - - private: - template friend class allocator; - template friend class ptr; - template{}>> - ptr(ptr const& p) : impl_{const_cast(impl_)}{} - template friend ptr const_pointer_cast(ptr const&); - - public: - explicit ptr(impl_t impl) : impl_{impl}{} - ptr() = default; - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialize from nullptr - ptr(std::nullptr_t n) : impl_{n} {} - - template().impl_})> - // cppcheck-suppress noExplicitConstructor ; TODO(correaa) : implement implicit propagation - ptr(Other const& o) : impl_{o.impl_}{} - - ptr& operator=(ptr const&) = default; - auto operator==(ptr const& other) const{return impl_==other.impl_;} - auto operator!=(ptr const& other) const{return impl_!=other.impl_;} - - using element_type = typename std::pointer_traits::element_type; - using difference_type = typename std::pointer_traits::difference_type; - using value_type = T; - using pointer = ptr; - using reference = ref; - using iterator_category = typename std::iterator_traits::iterator_category; -// using iterator_concept = typename std::iterator_traits::iterator_concept; - explicit operator bool() const{return impl_;} - - ptr& operator++(){++impl_; return *this;} - ptr& operator--(){--impl_; return *this;} - ptr operator++(int){auto tmp = *this; ++(*this); return tmp;} - ptr operator--(int){auto tmp = *this; --(*this); return tmp;} - ptr& operator+=(typename ptr::difference_type n){impl_+=n; return *this;} - ptr& operator-=(typename ptr::difference_type n){impl_+=n; return *this;} - ptr operator+(typename ptr::difference_type n) const{return ptr{impl_ + n};} - ptr operator-(typename ptr::difference_type n) const{return ptr{impl_ - n};} - ref operator*() const{return {*this};} - ref operator[](difference_type n){return *operator+(n);}//*((*this)+n);} - friend ptr to_address(ptr const& p){return p;} - typename ptr::difference_type operator-(ptr const& other) const{return impl_-other.impl_;} -}; - -template -class ptr{ - using T = void const; - using impl_t = Ptr; - impl_t impl_; - template friend class ptr; - template friend ptr const_pointer_cast(ptr const&); - explicit ptr(impl_t impl) : impl_{impl}{} - - public: - ptr() = default; - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialize from nullptr - ptr(std::nullptr_t n) : impl_{n}{} - - template().impl_})> - // cppcheck-suppress noExplicitConstructor ; any pointer is convertible to void pointer - ptr(Other const& o) : impl_{o.impl_}{} - - ptr& operator=(ptr const&) = default; - - using pointer = ptr; - using element_type = typename std::pointer_traits::element_type; - using difference_type = void;//typename std::pointer_traits::difference_type; - explicit operator bool() const{return impl_;} - auto operator==(ptr const& other) const{return impl_==other.impl_;} - auto operator!=(ptr const& other) const{return impl_!=other.impl_;} - friend ptr to_address(ptr const& p){return p;} -}; - -template -struct ptr{ - protected: - using T = void; - using impl_t = Ptr; - impl_t impl_; - - private: - explicit ptr(impl_t impl) : impl_{impl} {} - ptr(ptr const& p) : impl_{const_cast(p.impl_)} {} - template friend ptr const_pointer_cast(ptr const&); - template friend class ptr; - - public: - ptr() = default; - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialize from nullptr - ptr(std::nullptr_t n) : impl_{n} {} - - template().impl_})> - // cppcheck-suppress noExplicitConstructor ; any pointer is convertible to void pointer - ptr(Other const& o) : impl_{o.impl_} {} - - ptr& operator=(ptr const&) = default; - - auto operator==(ptr const& other) const{return impl_==other.impl_;} - auto operator!=(ptr const& other) const{return impl_!=other.impl_;} - - using pointer = ptr; - using element_type = typename std::pointer_traits::element_type; - using difference_type = void;// typename std::pointer_traits::difference_type; - - explicit operator bool() const{return impl_;} - friend ptr to_address(ptr const& p){return p;} -}; - -template -ptr const_pointer_cast(ptr const& p){return {p.impl_};} - -template struct overload{}; //template<> struct overload<>{}; -template -struct overload : F, Fs...{ - overload(F f, Fs... fs) : F{std::move(f)}, Fs{std::move(fs)}...{} - using F::operator(); -}; -template -overload make_overload(Fs&&... fs){return {std::forward(fs)...};} - -template struct ref; - -template<> struct ref{}; - -template -struct ref : private ptr{ - using value_type = T; - using reference = value_type&; - using pointer = ptr; - -private: - explicit ref(pointer p) : ptr{std::move(p)}{} - friend class ptr; - ptr operator&(){return *this;} - struct skeleton_t { - std::array buff; // char buff[sizeof(T)]; - T* p_; - explicit skeleton_t(T* p) : p_{p} {cudaError_t s = cudaMemcpy(buff.data(), p_, buff.size(), cudaMemcpyDeviceToHost); assert(s == cudaSuccess);} - operator T&() && {return reinterpret_cast(buff);} - void conditional_copyback_if_not(std::false_type) const { - cudaError_t s = cudaMemcpy(p_, buff.data(), buff.size(), cudaMemcpyHostToDevice); assert(s == cudaSuccess); - } - void conditional_copyback_if_not(std::true_type) const {} - ~skeleton_t(){conditional_copyback_if_not(std::is_const{});} - }; - skeleton_t skeleton()&&{return {this->impl_};} - - public: - // cppcheck-suppress noExplicitConstructor ; bug in cppcheck 2.3 - ref(ref&& r) : ptr{r}{} - ref& operator=(ref const&)& = delete; - - private: - ref& move_assign(ref&& other, std::true_type)&{ - cudaError_t s = cudaMemcpy(this->impl_, other.impl_, sizeof(T), cudaMemcpyDeviceToDevice); assert(s == cudaSuccess); - return *this; - } - ref& move_assign(ref&& other, std::false_type)&{ - cudaError_t s = cudaMemcpy(this->impl_, other.impl_, sizeof(T), cudaMemcpyDeviceToDevice); assert(s == cudaSuccess); - return *this; - } -public: - ref&& operator=(ref&& other)&&{return std::move(move_assign(std::move(other), std::is_trivially_copy_assignable{}));} -private: -public: - template - auto operator+(Other&& o)&& - ->decltype(std::move(*this).skeleton() + std::forward(o)) { - return std::move(*this).skeleton() + std::forward(o); } -// template, ref>{}> > -// friend auto operator+(Self&& self, O&& o) -// ->decltype(std::forward(self).skeleton() + std::forward(o)){ -// return std::forward(self).skeleton() + std::forward(o);} - ref&& operator=(value_type const& t) && { - make_overload( - [&](std::true_type ) {cudaError_t s= cudaMemcpy(this->impl_, std::addressof(t), sizeof(T), cudaMemcpyHostToDevice); assert(s == cudaSuccess);}, - [&](std::false_type) { - std::array buff; // char buff[sizeof(T)]; - cudaError_t s1 = cudaMemcpy(buff.data(), this->impl_, buff.size(), cudaMemcpyDeviceToHost); assert(s1 == cudaSuccess); - reinterpret_cast(buff) = t; - cudaError_t s2 = cudaMemcpy(this->impl_, buff.data(), buff.size(), cudaMemcpyHostToDevice); assert(s2 == cudaSuccess); - } - )(std::is_trivially_copy_assignable{}); - return std::move(*this); - } - template - decltype(auto) operator==(ref&& other) && { - std::array buff1; // char buff1[sizeof(T)]; - cudaError_t s1 = cudaMemcpy(buff1.data(), this->impl_, buff1.size(), cudaMemcpyDeviceToHost); assert(s1 == cudaSuccess); - std::array buff2; // char buff2[sizeof(Other)]; - cudaError_t s2 = cudaMemcpy(buff2.data(), other.impl_, buff2.size(), cudaMemcpyDeviceToHost); assert(s2 == cudaSuccess); - return reinterpret_cast(buff1) == reinterpret_cast(buff2); - } - template - decltype(auto) operator!=(ref&& other) && { - std::array buff1; // char buff1[sizeof(T)]; - cudaError_t s1 = cudaMemcpy(buff1.data(), this->impl_, buff1.size(), cudaMemcpyDeviceToHost); assert(s1 == cudaSuccess); - std::array buff2; // char buff2[sizeof(Other)]; - cudaError_t s2 = cudaMemcpy(buff2.data(), other.impl_, buff2.size(), cudaMemcpyDeviceToHost); assert(s2 == cudaSuccess); - return reinterpret_cast(buff1) != reinterpret_cast(buff2); - } - operator T() && { - static_assert(not std::is_same{}, "!"); - std::array buff; // char buff[sizeof(T)]; - cudaError_t s = cudaMemcpy(buff.data(), this->impl_, buff.size(), cudaMemcpyDeviceToHost); assert(s == cudaSuccess ); - return std::move(reinterpret_cast(buff)); - } - template()+=std::declval())> - decltype(auto) operator+=(Other&& o) && {std::move(*this).skeleton()+=o; return *this;} - template()-=std::declval())> - decltype(auto) operator-=(Other&& o) && {std::move(*this).skeleton()-=o;} - friend void swap(ref&& a, ref&& b) {T tmp = std::move(a); a = std::move(b); b = std::move(tmp);} - decltype(auto) operator++() && {++(std::move(*this).skeleton()); return *this;} - decltype(auto) operator--() && {--(std::move(*this).skeleton()); return *this;} -}; - -} // end namespace cuda -} // end namespace memory -} // end namespace detail - -} // end namespace multi -} // end namespace boost - -#ifdef _TEST_BOOST_MULTI_DETAIL_MEMORY_CUDA_PTR - -#include -#include -#include "../../../multi/array.hpp" -#include "../cuda/allocator.hpp" - -namespace boost{ -namespace multi{namespace cuda{ - template - using array = multi::array>; -}} -} - -namespace multi = boost::multi; -namespace cuda = multi::detail::memory::cuda; - -void add_one(double& d){d += 1.;} -template -void add_one(T&& t){std::forward(t) += 1.;} - -int main() { - - static_assert(std::is_same>::element_type, double>{}, "!"); - cuda::allocator calloc; - cuda::ptr p = calloc.allocate(100); - cuda::ptr v = p; - cuda::ptr vc{v}; - v = const_pointer_cast(vc); - assert( vc == v ); - std::pointer_traits::rebind pc = p; // cuda::ptr pc = p; - assert( pc == p ); - using cuda::const_pointer_cast; - auto end = p + 100; - auto rbegin = std::make_reverse_iterator(end); - auto rend = std::make_reverse_iterator(p); - std::transform(rbegin, rend, rbegin, [](auto&& e){return std::forward(e) + 99.;}); - assert( p[11] == 99. ); - p[33] = 123.; - p[99] = 321.; -// p[33] += 1; - add_one(p[33]); - double p33 = p[33]; - assert( p33 == 124. ); - assert( p[33] == 124. ); - assert( p[33] == p[33] ); - swap(p[33], p[99]); - assert( p[99] == 124. ); - assert( p[33] == 321. ); - std::cout << p[33] << std::endl; - calloc.deallocate(p, 100); - - multi::array> arr2(multi::array::extensions_type{100l}, 999.); - - assert(size(arr2) == 100); -} -#endif -#endif diff --git a/include/multi/detail/cuda/test/cuda_stack.cpp b/include/multi/detail/cuda/test/cuda_stack.cpp deleted file mode 100644 index cbcbe1b54..000000000 --- a/include/multi/detail/cuda/test/cuda_stack.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS -nvcc -ccbin cuda-c++ -std=c++14 $0 -o $0x && $0x && rm -f $0x; exit -#endif - -#include "../../../../multi/array.hpp" -#include "../../../../multi/detail/stack_allocator.hpp" -#include "../../../../multi/detail/cuda/allocator.hpp" - -#include - -namespace multi = boost::multi; -namespace cuda = multi::detail::memory::cuda; - -using std::cout; - -int main(){ - { - std::size_t stack_size = 4000; - multi::stack_buffer> buf{stack_size}; - for(int i = 0; i != 3; ++i){ - cout<<"pass "<< i << std::endl; - { - multi::array>> A({2, 10}, &buf); - multi::array>> B({3, 10}, &buf); - multi::array>> C({4, 10}, &buf); - for(int j = 0; j != 100; ++j) - multi::array>> D({4, 10}, &buf); - B[1][1] = 33.; - B[2][2] = 33.; - assert( B[1][1] == B[2][2] ); - } - cout - <<" size: "<< buf.size() - <<"\n hits: "<< buf.hits() - <<"\n misses "<< buf.misses() - <<"\n allocated(bytes) "<< buf.allocated_bytes() - <<"\n deallocated(bytes) "<< buf.deallocated_bytes() - <<"\n max_needed(bytes) "<< buf.max_needed() - <<"\n stack recovered(bytes) " << buf.stack_recovered() - << std::endl - ; - assert( buf.allocated_bytes() == buf.deallocated_bytes() ); - if(buf.max_needed() > buf.size()) buf.reset(buf.max_needed()); - } - } - assert( cuda::allocation_counter::n_allocations == 1 ); -} - diff --git a/include/multi/detail/fix_complex_traits_.hpp b/include/multi/detail/fix_complex_traits_.hpp deleted file mode 100644 index 9171ce08d..000000000 --- a/include/multi/detail/fix_complex_traits_.hpp +++ /dev/null @@ -1,47 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022-2023 Alfredo A. Correa - -#ifndef MULTI_DETAIL_FIX_COMPLEX_TRAITS_HPP -#define MULTI_DETAIL_FIX_COMPLEX_TRAITS_HPP - - -#include -#include - -#include -#include - -namespace boost::multi { - -#ifndef NDEBUG -// NOLINTNEXTLINE(clang-diagnostic-#pragma-messages) -#pragma message "By including this header, the behavior of initialization of std::complex in multi::array's changes. std::complex elements will not be initialized." -#endif - -template -inline constexpr bool force_element_trivial_default_construction> = std::is_trivially_default_constructible_v; - -template struct is_trivially_default_constructible> : std::is_trivially_default_constructible {}; -template struct is_trivial> : std::is_trivial {}; - -template constexpr bool is_trivially_default_constructible_v = boost::multi::is_trivially_default_constructible::value; -template constexpr bool is_trivial_v = boost::multi::is_trivial::value; - -} // end namespace boost::multi - -static_assert(not std::is_trivially_default_constructible<::std::complex>::value); -static_assert(not std::is_trivially_default_constructible<::std::complex>::value); - -static_assert(boost::multi::is_trivially_default_constructible<::std::complex>::value); -static_assert(boost::multi::is_trivially_default_constructible<::std::complex>::value); - -static_assert(boost::multi::is_trivial<::std::complex>::value); -static_assert(boost::multi::is_trivial<::std::complex>::value); - -static_assert(std::is_trivially_assignable<::std::complex&, ::std::complex>::value); -static_assert(std::is_trivially_assignable<::std::complex&, ::std::complex>::value); - -static_assert(std::is_trivially_copyable<::std::complex>::value); -static_assert(std::is_trivially_copyable<::std::complex>::value); - -#endif diff --git a/include/multi/detail/generic_allocator.hpp b/include/multi/detail/generic_allocator.hpp deleted file mode 100644 index 52b2f7fe2..000000000 --- a/include/multi/detail/generic_allocator.hpp +++ /dev/null @@ -1,110 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2021 Alfredo A. Correa - -#ifndef BOOST_MULTI_DETAIL_GENERIC_ALLOCATOR_HPP -#define BOOST_MULTI_DETAIL_GENERIC_ALLOCATOR_HPP - -#include "../detail/memory.hpp" - -#include -#include - -#if __cplusplus > 201703L -#include -#endif -// static_assert(__cpp_lib_experimental_memory_resources==201402, "!"); -#include // for forward - -namespace boost { -namespace multi { - -template -auto allocator_of(MR& mr) -->decltype(mr->allocator()) { - return mr->allocator(); } - -inline std::allocator& allocator_of(...) { - static std::allocator instance; - return instance; -} - -template -class generic_allocator{ - using memory_resource_type = MemoryResource; - memory_resource_type* mr_; - template friend class generic_allocator; - - public: - using value_type = T; - using pointer = typename std::pointer_traits()->allocate(0))>::template rebind; - using difference_type = typename std::pointer_traits::difference_type; - using size_type = std::make_unsigned_t; - - generic_allocator() : mr_{nullptr} {} - - // cppcheck-suppress noExplicitConstructor ; allocators are pointers to memory resources - generic_allocator(memory_resource_type* mr) : mr_{mr} {} // NOLINT(runtime/explicit) - - template - generic_allocator(generic_allocator const& other) - : mr_{other.mr_} {} - - bool operator==(generic_allocator const& o) const {return mr_ == o.mr_;} - bool operator!=(generic_allocator const& o) const {return not(o==*this);} - - pointer allocate(size_type n) { - if(n and !mr_) throw std::bad_alloc{}; - return static_cast(mr_->allocate(n*sizeof(value_type))); - } - void deallocate(pointer p, size_type n) { - if(n==0 and p == nullptr) return; - mr_->deallocate(p, n*sizeof(value_type)); - } - template - void construct(pointer p, Args&&... args) { -// ->decltype(allocator_traits()))>>::construct(allocator_of(*mr_), p, std::forward(args)...)){ - // mr_->allocator().construct(p, std::forward(args)...); - // using TA = allocator_traits>; - allocator_traits>::construct(allocator_of(mr_), p, std::forward(args)...); - } - decltype(auto) destroy(pointer p){ - // mr_->allocator().destroy(p); - allocator_traits>::destroy(allocator_of(mr_), p); - } -}; - -} // end namespace multi -} // end namespace boost - -#ifdef _TEST_BOOST_MULTI_DETAIL_GENERIC_ALLOCATOR - -#include -#include "../array.hpp" -#include - -namespace multi = boost::multi; -using std::cout; - -int main() { -#if 1 - multi::generic_allocator ga(std::pmr::get_default_resource()); - double* p = ga.allocate(1); - std::allocator_traits>::construct(ga, p, 8.); -// ga.construct(p, 8.); - assert( *p == 8. ); - - std::vector> v(100, std::pmr::get_default_resource()); -// std::vector v(100, 1.2, multi::allocator{}); // needs C++17 CTAD - multi::array> m({2, 4}, 0., std::pmr::get_default_resource()); -// multi::array m({2,4}, 0., pmr::get_default_resource()); // needs C++17 CTAD - m[1][3] = 99.; - assert( m[1][3] == 99. ); -#endif -} -#endif -#endif - diff --git a/include/multi/detail/index_range.hpp b/include/multi/detail/index_range.hpp deleted file mode 100644 index 0ca62ff25..000000000 --- a/include/multi/detail/index_range.hpp +++ /dev/null @@ -1,318 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2022 Alfredo A. Correa - -#ifndef MULTI_DETAIL_INDEX_RANGE_HPP -#define MULTI_DETAIL_INDEX_RANGE_HPP - -#include "../../multi/detail/serialization.hpp" -#include "../../multi/detail/tuple_zip.hpp" -#include "../../multi/detail/types.hpp" - -#include // for min -#include // for std::random_iterator_tag // std::reverse_iterator -#include // for numeric_limits -#include // for forward - -namespace boost::multi { - -using boost::multi::detail::tuple; - -template< - class Self, - class ValueType, class AccessCategory, - class Reference = ValueType&, class DifferenceType = typename std::pointer_traits::difference_type, class Pointer = ValueType* -> -class iterator_facade { - using self_type = Self; - [[nodiscard]] constexpr auto self() & {return static_cast(*this);} - [[nodiscard]] constexpr auto self() const& {return static_cast(*this);} - - public: - using value_type = ValueType; - using reference = Reference; - using pointer = Pointer; - using difference_type = DifferenceType; - using iterator_category = AccessCategory; - - friend constexpr auto operator!=(self_type const& self, self_type const& other) {return not(self == other);} - - friend constexpr auto operator<=(self_type const& self, self_type const& other) {return (self < other) or (self == other);} - friend constexpr auto operator> (self_type const& self, self_type const& other) {return not(self <= other);} - friend constexpr auto operator>=(self_type const& self, self_type const& other) {return not(self < other);} - - constexpr auto operator-(difference_type n) const {return self_type{self()} -= n;} - constexpr auto operator+(difference_type n) const {return self_type{self()} += n;} - friend constexpr auto operator+(difference_type n, self_type const& self) {return self + n;} - - friend constexpr auto operator++(self_type& self, int) -> self_type {self_type ret = self; ++self; return ret;} - friend constexpr auto operator--(self_type& self, int) -> self_type {self_type ret = self; --self; return ret;} - - constexpr auto operator[](difference_type n) const {return *(self() + n);} -}; - -template, class Minus = std::minus<> > -class range { - IndexType first_ = {}; - IndexTypeLast last_ = first_; - - public: - template // , class ArT = multi::archive_traits> - void serialize(Archive& arxiv, unsigned /*version*/) { - arxiv & multi::archive_traits::make_nvp("first", first_); - // arxiv & BOOST_SERIALIZATION_NVP( first_); - // arxiv & cereal:: make_nvp("first", first_); - // arxiv & CEREAL_NVP( first_); - // arxiv & first_ ; - - arxiv & multi::archive_traits::make_nvp("last" , last_ ); - // arxiv & BOOST_SERIALIZATION_NVP( last_ ); - // arxiv & cereal:: make_nvp("last" , last_ ); - // arxiv & CEREAL_NVP( last_ ); - // arxiv & last_ ; - } - - using value_type = IndexType; - using difference_type = decltype(IndexTypeLast{} - IndexType{}); // std::make_signed_t; - using size_type = difference_type; - using const_reference = value_type; - using reference = const_reference; - using const_pointer = value_type; - using pointer = value_type; - - range() = default; - - template, value_type>> > - // cxxcheck-suppress internalAstError ; because bug in cppcheck - constexpr explicit range(Range&& other) - : first_{std::forward(other).first()}, last_{std::forward(other).last()} {} - - constexpr range(IndexType first, IndexTypeLast last) noexcept - : first_{first}, last_{last} {} - - constexpr range(IndexType first, IndexTypeLast last, Plus /*plus*/, Minus /*minus*/) noexcept - : first_{first}, last_{last} {} - - class const_iterator : public boost::multi::iterator_facade< - const_iterator, - value_type, std::random_access_iterator_tag, - const_reference, difference_type - > { - typename const_iterator::value_type curr_; - constexpr explicit const_iterator(value_type current) : curr_{current} {} - friend class range; - - public: - const_iterator() = default; - - constexpr auto operator==(const_iterator const& other) const -> bool {return curr_ == other.curr_;} - constexpr auto operator< (const_iterator const& other) const -> bool {return curr_ < other.curr_;} - - constexpr auto operator++() -> const_iterator& {++curr_; return *this;} - constexpr auto operator--() -> const_iterator& {--curr_; return *this;} - - constexpr auto operator-=(typename const_iterator::difference_type n) -> const_iterator& {curr_ -= n; return *this;} - constexpr auto operator+=(typename const_iterator::difference_type n) -> const_iterator& {curr_ += n; return *this;} - - constexpr auto operator-(const_iterator const& other) const {return curr_ - other.curr_;} - constexpr auto operator*() const -> typename const_iterator::reference {return curr_;} - }; - - using iterator = const_iterator ; - using reverse_iterator = std::reverse_iterator< iterator>; - using const_reverse_iterator = std::reverse_iterator; - - [[nodiscard]] constexpr auto first() const -> const_reference {return first_;} - [[nodiscard]] constexpr auto last() const -> const_reference {return last_ ;} - - constexpr auto operator[](difference_type n) const -> const_reference {return first() + n;} - - [[nodiscard]] constexpr auto front() const -> const_reference {return first() ;} - [[nodiscard]] constexpr auto back() const -> const_reference {return last() - 1;} - - [[nodiscard]] constexpr auto cbegin() const {return const_iterator{first_};} - [[nodiscard]] constexpr auto cend() const {return const_iterator{last_ };} - - [[nodiscard]] constexpr auto rbegin() const {return reverse_iterator{end() };} - [[nodiscard]] constexpr auto rend() const {return reverse_iterator{begin()};} - - [[nodiscard]] constexpr auto begin() const -> const_iterator {return cbegin();} - [[nodiscard]] constexpr auto end() const -> const_iterator {return cend() ;} - - constexpr auto is_empty() const& noexcept {return first_ == last_;} - friend constexpr auto is_empty(range const& self) noexcept {return self.is_empty();} - - [[nodiscard]] - constexpr auto empty() const& noexcept {return is_empty();} - friend constexpr auto empty(range const& self) noexcept {return self.empty();} - - constexpr auto size() const& noexcept -> size_type {return last_ - first_;} - friend constexpr auto size(range const& self) noexcept -> size_type {return self.size();} - - friend constexpr auto begin(range const& self) {return self.begin();} - friend constexpr auto end (range const& self) {return self.end() ;} - - friend constexpr auto operator==(range const& self, range const& other) { - return (self.empty() and other.empty()) or (self.first_ == other.first_ and self.last_ == other.last_); - } - friend constexpr auto operator!=(range const& self, range const& other) {return not(self == other);} - - [[nodiscard]] constexpr auto find(value_type const& value) const -> range::const_iterator { - if(value >= last_ or value < first_) { - return end(); - } - return begin() + (value - front()); - } - template [[nodiscard]] constexpr auto contains(Value const& value) const {return (value >=first_) and (value < last_);} - template [[nodiscard]] constexpr auto count (Value const& value) const -> value_type {return contains(value);} - - friend constexpr auto intersection(range const& self, range const& other) { - using std::max; using std::min; - auto new_first = max(self.first(), other.first()); - auto new_last = min(self.last() , other.last() ); - new_first = min(new_first, new_last); - return range{new_first, new_last}; - } - [[nodiscard]] constexpr auto contains(value_type const& value) const {return value >= first_ and value < last_;} -}; - -template // , class Plus = std::plus<>, class Minus = std::minus<> > -range(IndexType, IndexTypeLast) -> range; // #3 - -template -constexpr auto make_range(IndexType first, IndexTypeLast last) -> range { - return {first, last}; -} - -template -class intersecting_range { - range impl_{std::numeric_limits::min(), std::numeric_limits::max()}; - - constexpr intersecting_range() = default; // MSVC 19.07 needs constexpr to initialize ALL later - static constexpr auto make(IndexType first, IndexType last) -> intersecting_range { - intersecting_range ret; ret.impl_ = range{first, last}; return ret; - } - friend constexpr auto intersection(intersecting_range const& self, range const& other) { - return intersection(self.impl_, other); - } - friend constexpr auto intersection(range const& other, intersecting_range const& self) { - return intersection(other, self.impl_); - } - friend constexpr auto operator<(intersecting_range const& self, IndexType end) { - return intersecting_range::make(self.impl_.first(), end); - } - friend constexpr auto operator<=(IndexType first, intersecting_range const& self) { - return intersecting_range::make(first, self.impl_.last()); - } - - public: - constexpr auto operator*() const& -> intersecting_range const& {return *this;} - static constexpr auto all() noexcept {return intersecting_range{};} -}; - -[[maybe_unused]] constexpr intersecting_range<> ALL = intersecting_range<>::all(); -[[maybe_unused]] constexpr intersecting_range<> _ = ALL; // NOLINT(readability-identifier-length) -[[maybe_unused]] constexpr intersecting_range<> U = ALL; // NOLINT(readability-identifier-length) -[[maybe_unused]] constexpr intersecting_range<> ooo = ALL; - -[[maybe_unused]] constexpr intersecting_range<> V = U; // NOLINT(readability-identifier-length) -[[maybe_unused]] constexpr intersecting_range<> A = V; // NOLINT(readability-identifier-length) - -//[[maybe_unused]] constexpr intersecting_range<> https://www.compart.com/en/unicode/U+2200 = V; - -template() + 1)> -struct extension_t : public range { - using range::range; - - constexpr extension_t(IndexType first, IndexTypeLast last) noexcept - : range{first, last} {} - - // cppcheck-suppress noExplicitConstructor ; because syntax convenience // NOLINTNEXTLINE(runtime/explicit) - constexpr extension_t(IndexType last) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) because syntax convenience - : range(0, last) {} - - constexpr extension_t() noexcept : range() {} - - friend constexpr auto size(extension_t const& self) -> typename extension_t::size_type {return self.size();} - -// template -// friend auto operator<<(OStream& os, extension_t const& self) -> decltype(os<<"[]") { -// if(self.empty()) { -// return os << static_cast const&>(self); -// } -// if(self.first() == 0) { -// return os <<"["<< self.last() <<"]"; -// } -// return os << static_cast const&>(self); -// } - - [[nodiscard]] constexpr auto start () const -> IndexType {return this->first();} - [[nodiscard]] constexpr auto finish() const -> IndexType {return this->last ();} - - friend constexpr auto operator==(extension_t const& self, extension_t const& other) {return static_cast const&>(self) == static_cast const&>(other);} - friend constexpr auto operator!=(extension_t const& self, extension_t const& other) {return static_cast const&>(self) != static_cast const&>(other);} - - friend constexpr auto intersection(extension_t const& ex1, extension_t const& ex2) -> extension_t { - using std::max; using std::min; - auto first = max(ex1.first(), ex2.first()); - auto const last = min(ex1.last() , ex2.last() ); - first = min(first, last); - return extension_t{first, last}; - } -}; - -template -extension_t(IndexType, IndexTypeLast) -> extension_t; - -template -extension_t(IndexType) -> extension_t; - -template() + 1)> -constexpr auto make_extension_t(IndexType first, IndexTypeLast last) -> extension_t { - return {first, last}; -} - -template -constexpr auto make_extension_t(IndexTypeLast last) {return make_extension_t(IndexTypeLast{0}, last);} - -using index_range = range; -using index_extension = extension_t; -using iextension = index_extension; -using irange = index_range; - -namespace detail { - -template -struct append_to_type_seq {}; - -template class TT> -struct append_to_type_seq > { - using type = TT; -}; - -template class TT> -struct repeat { - using type = typename - append_to_type_seq< - T, - typename repeat::type - >::type; -}; - -template class TT> -struct repeat { - using type = TT<>; -}; - -} // end namespace detail - -template using index_extensions = typename detail::repeat::type; - -template -constexpr auto contains(index_extensions const& iex, Tuple const& tup) { -// using detail::head; -// using detail::tail; - return contains(head(iex), head(tup)) and contains(tail(iex), tail(tup)); -} - -} // end namespace boost::multi -#endif // MULTI_DETAIL_INDEX_RANGE_HPP diff --git a/include/multi/detail/layout.hpp b/include/multi/detail/layout.hpp deleted file mode 100644 index cce66be40..000000000 --- a/include/multi/detail/layout.hpp +++ /dev/null @@ -1,710 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2022 Alfredo A. Correa - -#ifndef MULTI_DETAIL_LAYOUT_HPP -#define MULTI_DETAIL_LAYOUT_HPP - -#include "index_range.hpp" - -#include "tuple_zip.hpp" - -#include "../../multi/config/ASSERT.hpp" - -#include "../../multi/detail/operators.hpp" - -#include // for make_signed_t -#include // for swap - -#if defined(__NVCC__) -#define HD __host__ __device__ -#else -#define HD -#endif - -namespace boost::multi { - -namespace detail { - -template -constexpr auto tuple_tail_impl(Tuple&& t, std::index_sequence /*012*/) { // NOLINT(readability-identifier-length) std naming - (void)t; // workaround bug warning in nvcc - using boost::multi::detail::get; - return boost::multi::detail::tuple{std::forward(t))>(get(t))...}; -} - -template -constexpr auto tuple_tail(Tuple&& t) // NOLINT(readability-identifier-length) std naming -->decltype(tuple_tail_impl(t, std::make_index_sequence> - 1U>())) { - return tuple_tail_impl(t, std::make_index_sequence> - 1U>()); } - -} // end namespace detail - -template struct layout_t; - -template -struct extensions_t { -// using base_ = std::decay_t()), std::declval::base_>()))>; - using base_ = boost::multi::detail::tuple_prepend_t::base_>; - - private: - base_ impl_; - - public: - static constexpr dimensionality_type dimensionality = D; - - extensions_t() = default; - using nelems_type = multi::index; - - template = 0> - // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) - constexpr extensions_t(multi::size_t size) : extensions_t{index_extension{size}} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : allow terse syntax - - template = 0> - // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) - constexpr extensions_t(index_extension ext1) : impl_{ext1} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax - - template = 0> - constexpr extensions_t(index_extension ext1, index_extension ext2) : impl_{ext1, ext2} {} - - template = 0> - constexpr extensions_t(index_extension ext1, index_extension ext2, index_extension ext3) : impl_{ext1, ext2, ext3} {} - - template = 0> - constexpr extensions_t(index_extension ext1, index_extension ext2, index_extension ext3, index_extension ext4) noexcept : impl_{ext1, ext2, ext3, ext4} {} - - template = 0> - constexpr extensions_t(index_extension ext1, index_extension ext2, index_extension ext3, index_extension ext4, index_extension ext5) : impl_{ext1, ext2, ext3, ext4, ext5} {} - - template = 0> - constexpr extensions_t(index_extension ext1, index_extension ext2, index_extension ext3, index_extension ext4, index_extension ext5, index_extension ext6) : impl_{ext1, ext2, ext3, ext4, ext5, ext6} {} - - template{}}), std::enable_if_t = 0> - // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) - constexpr extensions_t(tuple extensions) : impl_{std::move(extensions)} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - template{}}), std::enable_if_t = 0> - // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) - constexpr extensions_t(tuple extensions) : impl_{std::move(extensions)} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - template{}}), std::enable_if_t = 0> - // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) - constexpr extensions_t(tuple extensions) : impl_{std::move(extensions)} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - template{}}), std::enable_if_t = 0> - // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) - constexpr extensions_t(tuple extensions) : impl_{std::move(extensions)} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - template - constexpr explicit extensions_t(tuple const& tup) - : extensions_t(tup, std::make_index_sequence(D)>()) {} - - constexpr extensions_t(index_extension const& extension, typename layout_t::extensions_type const& other) - : extensions_t(tuple{extension, other.base()}) {} - - constexpr auto base() const& -> base_ const& {return impl_;} - - friend constexpr auto operator*(index_extension const& extension, extensions_t const& self) -> extensions_t { - return extensions_t{tuple{extension, self.base()}}; - } - - friend HD auto operator==(extensions_t const& self, extensions_t const& other) {return self.impl_ == other.impl_;} - friend HD auto operator!=(extensions_t const& self, extensions_t const& other) {return self.impl_ != other.impl_;} - -// using indices_type = decltype(tuple_cat(make_tuple(multi::index{}), typename extensions_t::indices_type{})); - using indices_type = multi::detail::tuple_prepend_t::indices_type>; - - [[nodiscard]] constexpr auto from_linear(nelems_type const& n) const -> indices_type { - auto const sub_num_elements = extensions_t{tail(this->base())}.num_elements(); - assert( sub_num_elements != 0 ); - return multi::detail::ht_tuple(n/sub_num_elements, extensions_t{tail(this->base())}.from_linear(n%sub_num_elements)); - } - - friend constexpr auto operator%(nelems_type idx, extensions_t const& extensions) {return extensions.from_linear(idx);} - - constexpr explicit operator bool() const {return not layout_t{*this}.empty();} - - template - constexpr auto to_linear(index const& idx, Indices const&... rest) const { - auto const sub_extensions = extensions_t{tail(this->base())}; - return idx*sub_extensions.num_elements() + sub_extensions.to_linear(rest...); - } - template - constexpr auto operator()(index idx, Indices... rest) const {return to_linear(idx, rest...);} - - constexpr auto operator[](index idx) const - ->decltype(std::declval()[idx]) { - return impl_ [idx]; } - - template - constexpr auto next_canonical(index& idx, Indices&... rest) const -> bool { // NOLINT(google-runtime-references) idx is mutated - if(extensions_t{tail(this->base())}.next_canonical(rest...)) {++idx;} - if(idx == head(impl_).last()) { - idx = head(impl_).first(); - return true; - } - return false; - } - template - constexpr auto prev_canonical(index& idx, Indices&... rest) const -> bool { // NOLINT(google-runtime-references) idx is mutated - if(extensions_t{tail(this->base())}.prev_canonical(rest...)) {--idx;} - if(idx < head(impl_).first()) { - idx = head(impl_).back(); - return true; - } - return false; - } - - private: - template - void serialize_impl(Archive& arxiv, std::index_sequence /*unused012*/) { - using boost::multi::detail::get; - (void)std::initializer_list{(arxiv & multi::archive_traits::make_nvp("extension", get(impl_)) , 0U)...}; - // (void)std::initializer_list{(arxiv & boost::serialization:: make_nvp("extension", std::get(impl_)) , 0U)...}; - // (void)std::initializer_list{(arxiv & cereal:: make_nvp("extension", std::get(impl_)) , 0U)...}; - // (void)std::initializer_list{(arxiv & std::get(impl_) , 0U)...}; - } - - public: - template - void serialize(Archive& arxiv, const unsigned int /*version*/) { - serialize_impl(arxiv, std::make_index_sequence(D)>()); - } - - private: - template(std::declval())...})> - constexpr extensions_t(Array const& tup, std::index_sequence /*unused012*/) : impl_{boost::multi::detail::get(tup)...} {} - - static constexpr auto multiply_fold() -> size_type {return static_cast(1);} - static constexpr auto multiply_fold(size_type const& size) -> size_type {return static_cast(size);} - template - static constexpr auto multiply_fold(size_type const& size, As const&... rest) -> size_type {return static_cast(size)*static_cast(multiply_fold(rest...));} // TODO(correaa) revise casts - - template constexpr auto num_elements_impl(std::index_sequence /*unused012*/) const -> size_type { - using boost::multi::detail::get; - return static_cast(multiply_fold(static_cast(get(impl_).size())...)); - } - - public: - constexpr auto num_elements() const -> size_type { - return static_cast(num_elements_impl(std::make_index_sequence(D)>())); - } - friend constexpr auto intersection(extensions_t const& self, extensions_t const& other) -> extensions_t{ - using boost::multi::detail::get; - return extensions_t{ - tuple{ - index_extension{intersection(get<0>(self.impl_), get<0>(other.impl_))}, - intersection( extensions_t{tail(self.base())}, extensions_t{tail(other.base())} ).base() - } - }; - } - - template - friend constexpr auto get(extensions_t const& self) -> typename std::tuple_element::type { - using boost::multi::detail::get; - return get(self.base()); - } - -}; - -template<> struct extensions_t<0> { - using base_ = tuple<>; - - private: - base_ impl_; - - public: - static constexpr dimensionality_type dimensionality = 0; // TODO(correaa): consider deprecation - - using rank = std::integral_constant; - - using nelems_type = index; - - explicit extensions_t(tuple<> const& tup) : impl_{tup} {} - - extensions_t() = default; - - constexpr auto base() const -> base_ const& {return impl_;} - - template void serialize(Archive&/*ar*/, unsigned /*version*/) {} - - static constexpr auto num_elements() /*const*/ -> size_type {return 1;} - - using indices_type = tuple<>; - - [[nodiscard]] static constexpr auto from_linear(nelems_type const& n) /*const*/ -> indices_type { - assert(n == 0); (void)n; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : constexpr function - return indices_type{}; - } - friend constexpr auto operator%(nelems_type const& n, extensions_t const& /*s*/) -> tuple<> {return /*s.*/from_linear(n);} - - static constexpr auto to_linear() /*const*/ -> difference_type {return 0;} - constexpr auto operator()() const {return to_linear();} - - constexpr void operator[](index) const = delete; - - static constexpr auto next_canonical() /*const*/ -> bool {return true;} - static constexpr auto prev_canonical() /*const*/ -> bool {return true;} - - friend constexpr auto intersection(extensions_t const& /*x1*/, extensions_t const& /*x2*/) -> extensions_t {return {};} - - constexpr HD auto operator==(extensions_t const& /*other*/) const {return true ;} - constexpr HD auto operator!=(extensions_t const& /*other*/) const {return false;} - - template - friend constexpr auto get(extensions_t const& self) -> typename std::tuple_element::type { - using boost::multi::detail::get; - return get(self.base()); - } -}; - -template<> struct extensions_t<1> { - using base_ = tuple; - - private: - base_ impl_; - - public: - static constexpr auto dimensionality = 1; // TODO(correaa): consider deprecation - - using nelems_type = index; - - // cppcheck-suppress noExplicitConstructor ; to allow terse syntax (compatible with std::vector(int) constructor - constexpr extensions_t(multi::size_t size) : impl_{multi::index_extension{0, size}} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - template - // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) - constexpr extensions_t(tuple extensions) : impl_{static_cast(head(extensions))} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - // cppcheck-suppress noExplicitConstructor ; to allow passing tuple // NOLINTNEXTLINE(runtime/explicit) - constexpr extensions_t(multi::index_extension const& other) : impl_{other} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) allow terse syntax - - constexpr explicit extensions_t(base_ tup) : impl_{tup} {} - - extensions_t() = default; - constexpr auto base() const -> base_ const& {return impl_;} - - HD constexpr auto operator==(extensions_t const& other) const -> bool {return impl_ == other.impl_;} - HD constexpr auto operator!=(extensions_t const& other) const -> bool {return impl_ != other.impl_;} - - constexpr auto num_elements() const -> size_type { - return head(impl_).size(); - } - - using indices_type = multi::detail::tuple; - - [[nodiscard]] constexpr auto from_linear(nelems_type const& n) const -> indices_type { // NOLINT(readability-convert-member-functions-to-static) TODO(correaa) - // assert(n <= num_elements()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in constexpr function - // return std::make_tuple(n); - // return std::tuple{n}; - return indices_type{n}; - } - - friend - constexpr auto operator%(nelems_type idx, extensions_t const& extensions) - -> multi::detail::tuple { - return extensions.from_linear(idx); - } - - static constexpr auto to_linear(index const& idx) -> difference_type /*const*/ {return idx;} - constexpr auto operator()(index const& idx) const -> difference_type {return to_linear(idx);} - constexpr auto operator[](index idx) const { - return multi::detail::tuple{std::get<0>(impl_)[idx]}; - } - - template - constexpr auto next_canonical(index& idx) const -> bool { // NOLINT(google-runtime-references) idx is mutated - ++idx; - using boost::multi::detail::get; - if(idx == get<0>(impl_).last()) { - idx = get<0>(impl_).first(); - return true; - } - return false; - } - constexpr auto prev_canonical(index& idx) const -> bool { // NOLINT(google-runtime-references) idx is mutated - --idx; - using boost::multi::detail::get; - if(idx == get<0>(impl_).first() - 1) { - idx = get<0>(impl_).back(); - return true; - } - return false; - } - - friend auto intersection(extensions_t const& self, extensions_t const& other) { - return extensions_t{ - intersection( - boost::multi::detail::get<0>(self .impl_), - boost::multi::detail::get<0>(other.impl_) - ) - }; - } - template - void serialize(Archive& arxiv, unsigned /*version*/) { - using boost::multi::detail::get; - auto& extension_ = get<0>(impl_); - arxiv & multi::archive_traits::make_nvp("extension", extension_); - // arxiv & boost::serialization:: make_nvp("extension", extension ); - // arxiv & cereal:: make_nvp("extension", extension ); - // arxiv & extension ; - } - - template - friend constexpr auto get(extensions_t const& self) -> typename std::tuple_element::type { - using boost::multi::detail::get; - return get(self.base()); - } -}; - -template using iextensions = extensions_t; - -template -constexpr auto array_size_impl(const boost::multi::extensions_t&) - -> std::integral_constant(D)>; - -} // end namespace boost::multi - -// NOLINTBEGIN(cert-dcl58-cpp) to implement structured bindings -namespace std { - - template - struct tuple_size> : std::integral_constant(D)> {}; - - template - struct tuple_element> { - using type = typename std::tuple_element::base_>::type; - }; - - template - constexpr auto get(boost::multi::extensions_t const& self) -> typename std::tuple_element>::type { - using boost::multi::detail::get; - return get(self.base()); - } - -} // end namespace std -// NOLINTEND(cert-dcl58-cpp) define stuff in STD - -namespace boost::multi { - -struct monostate : equality_comparable { - friend HD constexpr auto operator==(monostate const& /*self*/, monostate const& /*other*/) {return true;} -}; - -template -struct layout_t<0, SSize> -: multi::equality_comparable > -{ - using dimensionality_type = multi::dimensionality_type; - using rank = std::integral_constant; - - using size_type = SSize; - using difference_type = std::make_signed_t; - using index = difference_type; - using index_extension = multi::index_extension; - using index_range = multi::range; - - using sub_type = monostate; - using stride_type = monostate; - using offset_type = index; - using nelems_type = index; - - using strides_type = tuple<>; - using offsets_type = tuple<>; - using nelemss_type = tuple<>; - - using extension_type = void; - - using extensions_type = extensions_t; - using sizes_type = tuple<>; - - static constexpr dimensionality_type rank_v = rank::value; - static constexpr dimensionality_type dimensionality = rank_v; // TODO(correaa) : consider deprecation - - friend constexpr auto dimensionality(layout_t const& /*self*/) {return rank_v;} - - private: - sub_type sub_ = {}; // TODO(correaa) use [[no_unique_address]] in C++20 - stride_type stride_ = {}; // TODO(correaa) use [[no_unique_address]] in C++20 - offset_type offset_ = 0; - nelems_type nelems_ = 1; // TODO(correaa) : or std::numeric_limits::max(); ? - - template friend struct layout_t; - - public: - layout_t() = default; - HD constexpr explicit layout_t(extensions_type const& /*nil*/) {} - - HD constexpr layout_t(sub_type sub, stride_type stride, offset_type offset, nelems_type nelems) // NOLINT(bugprone-easily-swappable-parameters) - : sub_{sub}, stride_{stride}, offset_{offset}, nelems_{nelems} {} - - [[nodiscard]] constexpr auto extensions() const {return extensions_type{};} - friend constexpr auto extensions(layout_t const& self) {return self.extensions();} - - [[nodiscard]] constexpr auto num_elements() const {return nelems_;} - friend constexpr auto num_elements(layout_t const& self) {return self.num_elements();} - - [[nodiscard]] constexpr auto sizes() const {return tuple<>{};} - friend constexpr auto sizes(layout_t const& self) {return self.sizes();} - - [[nodiscard]] constexpr auto strides() const {return strides_type{};} - [[nodiscard]] constexpr auto offsets() const {return offsets_type{};} - [[nodiscard]] constexpr auto nelemss() const {return nelemss_type{};} - - constexpr auto operator()() const {return offset_;} - constexpr explicit operator offset_type() const {return offset_;} - - constexpr auto stride() const -> stride_type = delete; - constexpr auto offset() const -> offset_type {return offset_;} - constexpr auto nelems() const -> nelems_type {return nelems_;} - constexpr auto sub() const -> sub_type = delete; - - constexpr auto size() const -> size_type = delete; - constexpr auto extension() const -> extension_type = delete; - - constexpr auto is_empty() const noexcept -> bool = delete; // or {return false;} or return nelems_ == 0; - [[nodiscard]] - constexpr auto empty() const noexcept {return nelems_ == 0;} - friend - constexpr auto empty(layout_t const& self) noexcept {return self.empty();} - - constexpr auto is_compact() const -> bool = delete; - - constexpr auto base_size() const -> size_type {return 0;} - constexpr auto origin() const -> offset_type {return 0;} - - constexpr auto reverse() -> layout_t& {return *this;} - constexpr auto scale(size_type /*size*/) const {return *this;} - -// friend constexpr auto operator!=(layout_t const& self, layout_t const& other) {return not(self == other);} - friend HD constexpr auto operator==(layout_t const& self, layout_t const& other) { - return - std::tie(self .sub_, self .stride_, self .offset_, self .nelems_) - == std::tie(other.sub_, other.stride_, other.offset_, other.nelems_) - ; - } - constexpr auto operator< (layout_t const& other) const -> bool { - return std::tie(offset_, nelems_) < std::tie(other.offset_, other.nelems_); - } - - constexpr auto rotate() -> layout_t& {return *this;} - constexpr auto unrotate() -> layout_t& {return *this;} - - constexpr auto hull_size() const -> size_type {return num_elements();} // not in bytes -}; - -template -struct layout_t -: multi::equality_comparable> -{ - using dimensionality_type = multi::dimensionality_type; - using rank = std::integral_constant; - - using sub_type = layout_t; - using size_type = SSize; - using difference_type = std::make_signed_t; - using index = difference_type; - - using index_extension = multi::index_extension; - using index_range = multi::range; - using stride_type = index; - using offset_type = index; - using nelems_type = index; - - using strides_type = typename boost::multi::detail::tuple_prepend::type; - using offsets_type = typename boost::multi::detail::tuple_prepend::type; - using nelemss_type = typename boost::multi::detail::tuple_prepend::type; - - using extension_type = index_extension; // not index_range! - - using extensions_type = extensions_t; - using sizes_type = typename boost::multi::detail::tuple_prepend::type; - - static constexpr dimensionality_type rank_v = rank::value; - static constexpr dimensionality_type dimensionality = rank_v; // TODO(correaa): consider deprecation - - friend constexpr auto dimensionality(layout_t const& /*self*/) {return rank_v;} - - private: - sub_type sub_ = {}; - stride_type stride_ = 1; // or std::numeric_limits::max()? - offset_type offset_ = 0; - nelems_type nelems_ = 0; - - template friend struct layout_t; - - public: - layout_t() = default; - HD constexpr explicit layout_t(extensions_type const& extensions) : - sub_{ - std::apply( - [](auto const&... subextensions) {return multi::extensions_t{subextensions...};}, - detail::tail(extensions.base()) - ) - }, - stride_{sub_.num_elements()}, - offset_{boost::multi::detail::get<0>(extensions.base()).first()*stride_}, - nelems_{boost::multi::detail::get<0>(extensions.base()).size()*sub().num_elements()} - {} - - HD constexpr layout_t(sub_type sub, stride_type stride, offset_type offset, nelems_type nelems) // NOLINT(bugprone-easily-swappable-parameters) - : sub_{sub}, stride_{stride}, offset_{offset}, nelems_{nelems} {} - - constexpr auto origin() const {return sub_.origin() - offset_;} - - private: - constexpr auto at_aux(index idx) const { - return sub_type{sub_.sub_, sub_.stride_, sub_.offset_ + offset_ + idx*stride_, sub_.nelems_}(); - } - - public: - constexpr auto operator[](index idx) const {return at_aux(idx);} - - template - constexpr auto operator()(index idx, Indices... rest) const {return operator[](idx)(rest...);} - constexpr auto operator()(index idx) const {return at_aux(idx);} - constexpr auto operator()() const {return *this;} - - HD constexpr auto sub() & -> sub_type & {return sub_ ;} - HD constexpr auto sub() const& -> sub_type const& {return sub_ ;} - friend HD constexpr auto sub(layout_t const& self) -> sub_type const& {return self.sub();} - - HD constexpr auto nelems() & -> nelems_type & {return nelems_ ;} - HD constexpr auto nelems() const& -> nelems_type const& {return nelems_ ;} - friend HD constexpr auto nelems(layout_t const& self) -> nelems_type const& {return self.nelems();} - - constexpr HD auto nelems(dimensionality_type dim) const {return (dim != 0)?sub_.nelems(dim - 1):nelems_;} - - friend HD constexpr auto operator==(layout_t const& self, layout_t const& other) -> bool { - return - std::tie(self .sub_, self .stride_, self .offset_, self. nelems_) - == std::tie(other.sub_, other.stride_, other.offset_, other.nelems_) - ; - } - constexpr HD auto operator< (layout_t const& other) const -> bool { - return - std::tie( sub_, stride_, offset_, nelems_) - < std::tie(other.sub_, other.stride_, other.offset_, other.nelems_) - ; - } - - constexpr auto reindex(index idx) -> layout_t& {offset_ = idx*stride_; return *this;} - template - constexpr auto reindex(index idx, Indices... rest) -> layout_t& {reindex(idx).rotate().reindex(rest...).unrotate(); return *this;} - - constexpr auto num_elements() const noexcept -> size_type {return size()*sub_.num_elements();} - friend constexpr auto num_elements(layout_t const& self) noexcept -> size_type {return self.num_elements();} - - constexpr auto is_empty() const noexcept {return nelems_ == 0;} - friend constexpr auto is_empty(layout_t const& self) noexcept {return self.is_empty();} - - constexpr auto empty() const noexcept {return is_empty();} - - friend constexpr auto size(layout_t const& self) noexcept -> size_type {return self.size();} - /*[[gnu::pure]]*/ - constexpr auto size() const noexcept -> size_type { - // if(nelems_ == 0) {return 0;} - // MULTI_ACCESS_ASSERT(stride_); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - if(nelems_ != 0) {MULTI_ACCESS_ASSERT(stride_ != 0);} - return nelems_ == 0?0:nelems_/stride_; - } - - constexpr HD auto stride() -> stride_type & {return stride_;} - constexpr HD auto stride() const -> stride_type const& {return stride_;} - - friend HD constexpr auto stride(layout_t const& self) -> index {return self.stride();} - - HD constexpr auto strides() const -> strides_type {return strides_type{stride(), sub_.strides()};} - friend HD constexpr auto strides(layout_t const& self) -> strides_type {return self.strides();} - - constexpr HD auto offset(dimensionality_type dim) const -> index {return (dim != 0)?sub_.offset(dim - 1):offset_;} - HD constexpr auto offset() const -> index {return offset_;} - friend HD constexpr auto offset(layout_t const& self) -> index {return self.offset();} - constexpr HD auto offsets() const {return boost::multi::detail::tuple{offset(), sub_.offsets()};} - constexpr HD auto nelemss() const {return boost::multi::detail::tuple{nelems(), sub_.nelemss()};} - - constexpr auto base_size() const {using std::max; return max(nelems_, sub_.base_size());} - - constexpr auto is_compact() const& {return base_size() == num_elements();} - friend constexpr auto is_compact(layout_t const& self) {return self.is_compact();} - - constexpr auto shape() const& -> decltype(auto) {return sizes();} - friend constexpr auto shape(layout_t const& self) -> decltype(auto) {return self.shape();} - - constexpr HD auto sizes() const noexcept {return tuple{size(), sub_.sizes()};} - - friend constexpr auto extension(layout_t const& self) {return self.extension();} - [[nodiscard]] constexpr auto extension() const -> extension_type { - if(nelems_ == 0) {return index_extension{};} - assert(stride_ != 0); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) : normal in a constexpr function - assert(offset_ % stride_ == 0); - assert(nelems_ % stride_ == 0); - return index_extension{offset_/stride_, (offset_ + nelems_)/stride_}; - } - - constexpr auto extensions() const {return extensions_type{tuple{extension(), sub_.extensions().base()}};} // tuple_cat(make_tuple(extension()), sub_.extensions().base())};} - friend constexpr auto extensions(layout_t const& self) -> extensions_type {return self.extensions();} - -// [[deprecated("use get(m.extensions()")]] // TODO(correaa) redeprecate, this is commented to give a smaller CI output - constexpr auto extension(dimensionality_type dim) const {return std::apply([](auto... extensions) {return std::array(D)>{extensions...};}, extensions().base()).at(static_cast(dim));} -// [[deprecated("use get(m.strides()) ")]] // TODO(correaa) redeprecate, this is commented to give a smaller CI output - constexpr auto stride (dimensionality_type dim) const {return std::apply([](auto... strides ) {return std::array(D)>{strides ...};}, strides () ).at(static_cast(dim));} -// [[deprecated("use get(m.sizes()) ")]] // TODO(correaa) redeprecate, this is commented to give a smaller CI output -// constexpr auto size (dimensionality_type dim) const {return std::apply([](auto... sizes ) {return std::array(D)>{sizes ...};}, sizes () ).at(static_cast(dim));} - - template - constexpr auto partition(Size const& count) -> layout_t& { - using std::swap; - stride_ *= count; - nelems_ *= count; - sub_.partition(count); - return *this; - } - - constexpr auto transpose() -> layout_t& { - using std::swap; - swap(stride_, sub_.stride_); - swap(offset_, sub_.offset_); - swap(nelems_, sub_.nelems_); - return *this; - } - constexpr auto reverse() -> layout_t& { - unrotate(); - sub_.reverse(); - return *this; - } - - constexpr auto rotate() -> layout_t& {if constexpr(D > 1) {transpose(); sub_. rotate();} return *this;} - constexpr auto unrotate() -> layout_t& {if constexpr(D > 1) {sub_.unrotate(); transpose();} return *this;} - - constexpr auto hull_size() const -> size_type { - if(is_empty()) {return 0;} - return std::abs(size()*stride())>std::abs(sub_.hull_size())?size()*stride():sub_.hull_size(); - } - - constexpr auto scale(size_type factor) const { - return layout_t{sub_.scale(factor), stride_*factor, offset_*factor, nelems_*factor}; - } -}; - -inline constexpr auto -operator*(layout_t<0>::index_extension const& extensions_0d, layout_t<0>::extensions_type const& /*zero*/) --> typename layout_t<1>::extensions_type { - return typename layout_t<1>::extensions_type{tuple::index_extension>{extensions_0d}}; -} - -inline constexpr auto operator*(extensions_t<1> const& extensions_1d, extensions_t<1> const& self) { - using boost::multi::detail::get; - return extensions_t<2>({get<0>(extensions_1d.base()), get<0>(self.base())}); -} - -} // end namespace boost::multi - -namespace std { - template<> struct tuple_size> : std::integral_constant {}; - template<> struct tuple_size> : std::integral_constant {}; - template<> struct tuple_size> : std::integral_constant {}; - template<> struct tuple_size> : std::integral_constant {}; - template<> struct tuple_size> : std::integral_constant {}; -} // end namespace std - -#endif diff --git a/include/multi/detail/monotonic_allocator_.hpp b/include/multi/detail/monotonic_allocator_.hpp deleted file mode 100644 index d6e2503d1..000000000 --- a/include/multi/detail/monotonic_allocator_.hpp +++ /dev/null @@ -1,186 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2021 Alfredo A. Correa - -#ifndef BOOST_MULTI_DETAIL_MONOTONIC_ALLOCATOR_HPP -#define BOOST_MULTI_DETAIL_MONOTONIC_ALLOCATOR_HPP - -#include // for max -#include -#include // for max_align_t -#include -#include - -#include "generic_allocator.hpp" - -namespace boost { -namespace multi { - -template -struct block : std::pointer_traits{ - template block(char(&t)[N]) : start_{t}, lenght_{N} {} - typename block::pointer start_; - typename block::size_type lenght_; - bool contains(typename block::pointer p) const{ - using std::distance; - return distance(start_, p) < static_cast(lenght_); - } -}; - -template -class monotonic_buffer : block { - size_type hits_ = 0; - size_type misses_ = 0; - size_type allocated_bytes_ = 0; - size_type deallocated_bytes_ = 0; -// size_type position_ = 0; - static std::size_t max_alignment = A; - - static size_type align_up(size_type n) noexcept { - return (n + (max_alignment-1)) & ~(max_alignment-1); - } - - public: - size_type size() const {return size_;} - size_type hits() const {return hits_;} - size_type misses() const {return misses_;} - - size_type allocated_bytes() const {return allocated_bytes_;} - size_type deallocated_bytes() const {return deallocated_bytes_;} - - using block::block; - monotonic_buffer& operator=(block const& b) { - position_ = 0; - block::operator=(b); return *this; - } - - monotonic_buffer(monotonic_buffer const&) = delete; - monotonic_buffer& operator=(monotonic_buffer const&) = delete; - - ~monotonic_buffer() { -#ifndef _BOOST_MULTI_RELAX_MONOTONIC_LEAK_CONDITION - assert(allocated_bytes() == deallocated_bytes()); -#endif - } - using void_pointer = monotonic_buffer; - - template - void_pointer allocate(size_type req_bytes, size_type al = RequiredAlignment) { - static_assert(RequiredAlignment <= max_alignment, "!"); // requested alignment is too large for this MR - - assert( al <= max_alignment ); - auto bytes = this->align_up(req_bytes); - if(position_ + bytes < size_) { - auto old_position_ = position_; - position_ += bytes; - ++hits_; - allocated_bytes_ += bytes; - return buffer_ + old_position_; - } - ++misses_; - auto p = alloc_.allocate(bytes/sizeof(std::max_align_t)); - if(p) allocated_bytes_ += bytes; - return p; - } - void deallocate(void_pointer p, size_type req_bytes){ - auto bytes = align_up(req_bytes); - deallocated_bytes_ += bytes; - if(not in_buffer(static_cast(p))) { - alloc_.deallocate(static_cast(p), bytes); - } - } -}; - -template -using monotonic_allocator = multi::generic_allocator>>; - -} // end namespace multi -} // end namespace boost - -#if _TEST_BOOST_MULTI_DETAIL_MONOTONIC_ALLOCATOR - -#include "../../multi/array.hpp" - -#include -#include -#include - -namespace multi = boost::multi; -using std::cout; - -int main() { - std::cout << sizeof(std::max_align_t) <<" "<< alignof(std::max_align_t) << std::endl; - - { - multi::monotonic_buffer<> buf(250*sizeof(double)); - { - multi::array > A({10, 10}, &buf); - multi::array > B({10, 10}, &buf); - multi::array > C({10, 10}, &buf); - } - assert( buf.hits() == 2 ); - assert( buf.misses() == 1 ); - cout - <<"size: "<< buf.size() - <<"\nsaved: "<< buf.hits() - <<"\nmisses "<< buf.misses() - <<"\nallocated(bytes) "<< buf.allocated_bytes() - <<"\nreleased(bytes) "<< buf.deallocated_bytes() - << std::endl; - } - cout<<"----------"<> buf(guess*sizeof(double)); - - for(int i = 0; i != 3; ++i) { - cout<<"pass "<< i << std::endl; - { - multi::array > A({10, 10}, &buf); - multi::array > B({10, 10}, &buf); - multi::array > C({10, 10}, &buf); - } - cout - <<" size: "<< buf.size() - <<"\n save: "<< buf.hits() - <<"\n misses "<< buf.misses() - <<"\n allocated(bytes) "<< buf.allocated_bytes() - <<"\n released(bytes) "<< buf.deallocated_bytes() - << std::endl; - // guess = std::max(guess, buf.allocated_bytes()); - buf.reset(buf.allocated_bytes()); - } - } - cout<<"----------monotonic"<> buf(guess_bytes); - - { - multi::array > A({10, 10}, &buf); - - for(int i = 0; i != 3; ++i) { - multi::array > B({10, 10}, &buf); - std::vector> v(3, &buf); - v.push_back(33); v.push_back(33); - } - - multi::array > C({10, 10}, &buf); - } - - cout - <<" size: "<< buf.size() - <<"\n hits: "<< buf.hits() - <<"\n misses "<< buf.misses() - <<"\n allocated(bytes) "<< buf.allocated_bytes() - <<"\n released(bytes) "<< buf.deallocated_bytes() - << std::endl; - guess_bytes = std::max(guess_bytes, buf.allocated_bytes()); - } - } -} -#endif -#endif - diff --git a/include/multi/detail/operators.hpp b/include/multi/detail/operators.hpp deleted file mode 100644 index 1969be34f..000000000 --- a/include/multi/detail/operators.hpp +++ /dev/null @@ -1,196 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2022 Alfredo A. Correa - -#ifndef MULTI_DETAIL_OPERATORS_HPP -#define MULTI_DETAIL_OPERATORS_HPP - -#include // for enable_if -#include // for forward - -namespace boost::multi { - -struct empty_base {}; - -template struct selfable { - using self_type = Self; - constexpr auto self() const -> self_type const& {return static_cast(*this);} - constexpr auto self() -> self_type & {return static_cast(*this);} - friend constexpr auto self(selfable const& self) -> self_type const& {return self.self();} -}; - -template struct equality_comparable2; - -template -struct equality_comparable2 : selfable { - // friend constexpr auto operator==(equality_comparable2 const& self, equality_comparable2 const& other) {return self.self() == other.self() ;} - friend constexpr auto operator!=(equality_comparable2 const& self, equality_comparable2 const& other) {return not(self.self() == other.self());} -}; - -template struct equality_comparable : equality_comparable2 {}; - -template struct totally_ordered2; - -template -struct totally_ordered2 : equality_comparable2, totally_ordered2> { - using self_type = Self; - constexpr auto self() const -> self_type const& {return static_cast(*this);} - - // friend auto operator< (totally_ordered2 const& self, totally_ordered2 const& other) -> bool {return self.self() < other.self() ;} - friend auto operator==(totally_ordered2 const& self, totally_ordered2 const& other) -> bool {return not(self.self() < other.self()) and not(other.self() < self.self());} - // friend auto operator!=(totally_ordered2 const& self, totally_ordered2 const& other) {return (s.self() < o.self()) or (o.self() < s.self());} - - friend auto operator<=(totally_ordered2 const& self, totally_ordered2 const& other) -> bool {return not(other.self() < self.self());} - - friend auto operator> (totally_ordered2 const& self, totally_ordered2 const& other) -> bool {return not(self.self() < other.self()) and not(self.self() == other.self());} - friend auto operator>=(totally_ordered2 const& self, totally_ordered2 const& other) -> bool {return not(self.self() < other.self());} -}; - -template using totally_ordered = totally_ordered2; - -template -struct totally_ordered2 { - template - friend constexpr auto operator<=(const T& self, const U& other) {return (self < other) or (self == other);} - template - friend constexpr auto operator>=(const T& self, const U& other) {return (other < self) or (self == other);} - template - friend constexpr auto operator> (const T& self, const U& other) {return other < self;} -}; - -template -struct copy_constructible {}; - -template -struct weakly_incrementable { - // friend T& operator++(weakly_incrementable& t){return ++static_cast(t);} -}; - -template -struct weakly_decrementable { - // friend T& operator--(weakly_decrementable& t){return --static_cast(t);} -}; - -template struct incrementable : totally_ordered { // , self_mutable { - friend constexpr auto operator++(incrementable& self, int) -> Self {Self tmp{self.self()}; ++self.self(); assert(self.self() > tmp); return tmp;} -}; - -template -struct decrementable : weakly_decrementable { - template{}>> - friend constexpr auto operator--(U& self, int) -> T {T tmp{self}; --self; return tmp;} -}; - -template -struct steppable : totally_ordered { - using self_type = Self; - constexpr auto self() const -> self_type const& {return static_cast(*this);} - constexpr auto self() -> self_type & {return static_cast(*this);} - - friend constexpr auto operator++(steppable& self, int) -> Self {Self tmp{self.self()}; ++self.self(); return tmp;} - friend constexpr auto operator--(steppable& self, int) -> Self {Self tmp{self.self()}; --self.self(); return tmp;} -}; - -template -struct affine_with_unit : steppable { // affine_with_unit > { - using self_type = Self; - constexpr auto cself() const -> self_type const& {return static_cast(*this);} - constexpr auto self() const -> self_type const& {return static_cast(*this);} - constexpr auto self() -> self_type & {return static_cast(*this);} - - using difference_type = Difference; - friend constexpr auto operator++(affine_with_unit& self) -> Self& {return self.self() += difference_type{1};} - friend constexpr auto operator--(affine_with_unit& self) -> Self& {return self.self() -= difference_type{1};} - - friend constexpr auto operator-(affine_with_unit const& self, difference_type const& diff) -> Self { - auto ret{self.self()}; - ret += (-diff); - return ret; - } - constexpr auto operator+(difference_type const& diff) const -> Self { - auto ret{cself()}; - ret += diff; - return ret; - } - friend constexpr auto operator+(difference_type const& diff, affine_with_unit const& self) -> Self { - auto ret{self.self()}; - ret += diff; - return ret; - } - friend constexpr auto operator<(affine_with_unit const& self, affine_with_unit const& other) -> bool { - return difference_type{0} < other.self() - self.self(); - } -}; - -template -struct dereferenceable { - using self_type = Self; - constexpr auto self() const -> self_type const& {return static_cast(*this);} - constexpr auto self() -> self_type & {return static_cast(*this);} - - using reference = Reference; - - constexpr auto operator*() const -> reference {return *(self().operator->());} -}; - -template -struct random_accessable // NOLINT(fuchsia-multiple-inheritance) -: affine_with_unit -, dereferenceable { - using difference_type = Difference; - using reference = Reference; - using iterator_category = std::random_access_iterator_tag; - - using self_type = Self; - constexpr auto self() const -> self_type const& {return static_cast(*this);} - constexpr auto self() -> self_type & {return static_cast(*this);} - - constexpr auto operator[](difference_type idx) const -> reference {return *(self() + idx);} -}; - -// template -// struct dereferenceable { -// using reference = Reference; -// friend constexpr auto operator*(dereferenceable const& t) -> reference {return *static_cast(t);} -// }; - -template -struct addable2 { - using difference_type = D; - template{}> > - friend constexpr auto operator+(TT&& self, difference_type const& diff) -> T {T tmp{std::forward(self)}; tmp += diff; return tmp;} - template{}> > - friend constexpr auto operator+(difference_type const& diff, TT&& self) -> T {return std::forward(self) + diff;} -}; - -template -struct subtractable2 { - using difference_type = D; - template - friend auto operator-(TT&& self, difference_type const& diff) -> T {T tmp{std::forward(self)}; tmp -= diff; return tmp;} -}; - -template -struct affine : addable2, subtractable2 { - using difference_type = Difference; -}; - -template -struct random_iterable { - constexpr auto cfront() const& -> decltype(auto) {return static_cast(*this).front();} - constexpr auto cback () const& -> decltype(auto) {return static_cast(*this).back() ;} - friend constexpr auto cfront(T const& self) -> decltype(auto) {return self.cfront();} - friend constexpr auto cback (T const& self) -> decltype(auto) {return self.cback() ;} -}; - -template -struct random_access_iterator : equality_comparable2 { - using difference_type = Difference; - using value_type = Value; - using pointer = Pointer; - using reference = Reference; - using iterator_category = std::random_access_iterator_tag; - auto operator*() const -> Reference {return *static_cast(*this);} -}; - -} // end namespace boost::multi -#endif diff --git a/include/multi/detail/serialization.hpp b/include/multi/detail/serialization.hpp deleted file mode 100644 index 25002a27f..000000000 --- a/include/multi/detail/serialization.hpp +++ /dev/null @@ -1,148 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2021 Alfredo A. Correa - -#ifndef MULTI_DETAIL_SERIALIZATION_HPP_ -#define MULTI_DETAIL_SERIALIZATION_HPP_ - -#include // for std::for_each -#include // for std::uint32_t - -namespace boost { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat -namespace archive { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat -namespace detail { - -template class common_iarchive; -template class common_oarchive; - -} // end namespace detail -} // end namespace archive - -namespace serialization { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat - -template class nvp; // dependency "in name only" -template class array_wrapper; // dependency "in name only" - struct binary_object; // dependency "in name only", if you get an error here, it means that eventually you need to include #include - -template struct version; - -// template//, std::enable_if_t>{}, int> =0> -// auto operator>>(Archive& ar, T&& t) -> Archive& {return ar>> t;} - -} // end namespace serialization -} // end namespace boost - -namespace cereal { - -template struct OutputArchive; -template struct InputArchive; - -template class NameValuePair; // dependency "in name only", if you get an error here you many need to #include at some point - -} // end namespace cereal - -namespace boost { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat -namespace multi { - -template> and (MA::dimensionality > -1) , int> =0> -auto operator>>(Archive& arxiv, MA&& self) // this is for compatibility with Archive type -->decltype(arxiv>> self) { - return arxiv>> self; } - -template> and (MA::dimensionality > -1), int> =0> -auto operator& (Archive& arxiv, MA&& self) // this is for compatibility with Archive type -->decltype(arxiv& self) { - return arxiv& self; } - -template -struct archive_traits { - template - inline static auto make_nvp (char const* /*n*/, T&& value) noexcept {return std::forward(value);} -}; - -template -struct archive_traits, Ar> || std::is_base_of_v, Ar>>::type> { - template using nvp = boost::serialization::nvp ; - template using array_wrapper = boost::serialization::array_wrapper; - template struct binary_object_t {using type = boost::serialization::binary_object;}; - template inline static auto make_nvp (char const* name, T& value) noexcept -> const nvp {return nvp{name, value};} // NOLINT(readability-const-return-type) : original boost declaration - template inline static auto make_nvp (char const* name, T&& value) noexcept -> const nvp {return nvp{name, value};} // NOLINT(readability-const-return-type) : original boost declaration - - template inline static auto make_array ( T* first, std::size_t size) noexcept -> const array_wrapper {return array_wrapper{first, size};} // NOLINT(readability-const-return-type) original boost declaration - template inline static auto make_binary_object( const void* first, std::size_t size) noexcept -> const typename binary_object_t::type {return typename binary_object_t::type(first, size); } // if you get an error here you need to eventually `#include`// NOLINT(readability-const-return-type,clang-diagnostic-ignored-qualifiers) : original boost declaration -}; - -#if 1 -template -struct archive_traits< - Ar, - typename std::enable_if< - std::is_base_of_v, Ar> or std::is_base_of_v, Ar> - or std::is_base_of_v, Ar> or std::is_base_of_v, Ar> - >::type -> { - using self_t = archive_traits, Ar> or std::is_base_of_v, Ar> - or std::is_base_of_v, Ar> or std::is_base_of_v, Ar> - >::type>; - - template - inline static auto make_nvp (char const* name, T&& value) noexcept {return cereal::NameValuePair{name, value};} // if you get an error here you many need to #include at some point - template - inline static auto make_nvp (char const* name, T& value) noexcept {return cereal::NameValuePair{name, value};} // if you get an error here you many need to #include at some point - - template - struct array_wrapper { - T* p_; - std::size_t c_; - template - void serialize(Archive& arxiv, const unsigned int /*version*/) { - for(std::size_t i = 0; i != c_; ++i) { // NOLINT(altera-unroll-loops) TODO(correaa) consider using an algorithm - auto& item = p_[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - arxiv & make_nvp("item" , item ); // "item" is the name used by Boost.Serialization XML make_array - // arxiv & boost::multi::archive_traits::make_nvp("element", element); - // arxiv & cereal::make_nvp("element", element); - // arxiv & CEREAL_NVP( element); - // arxiv & element ; - } - } - }; - - template - inline static auto make_array(T* ptr, std::size_t count) -> array_wrapper {return array_wrapper{ptr, count};} - - template - inline static auto make_nvp (char const* name, array_wrapper&& value) noexcept {return make_nvp(name, value);} -}; - -// template auto make_nvp(char const* n, T&& v) -> decltype(auto) {return archive_traits::make_nvp(n, std::forward(v));} // NOLINT(readability-const-return-type) -#endif - -} // end namespace multi -} // end namespace boost - -namespace boost { - -template -class multi_array; - -} // end namespace boost - -namespace boost { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat -namespace serialization { - -// template -// auto serialize(Archive& ar, boost::multi_array& arr, unsigned int /*version*/) -// { -// auto x = boost::multi::extensions(arr); -// ar & multi::archive_traits::make_nvp("extensions", x); -// if( x != boost::multi::extensions(arr) ) { -// arr.resize( std::array{} ); -// arr.resize( std::array{static_cast(std::get<0>(x).size()), static_cast(std::get<1>(x).size())} ); -// } -// ar & multi::archive_traits::make_nvp("data_elements", multi::archive_traits::make_array(boost::multi::data_elements(arr), static_cast(boost::multi::num_elements(arr)))); -// } - -} // end namespace serialization -} // end namespace boost - -#endif // MULTI_DETAIL_SERIALIZATION_HPP_ diff --git a/include/multi/detail/stack_allocator.hpp b/include/multi/detail/stack_allocator.hpp deleted file mode 100644 index f7144d34c..000000000 --- a/include/multi/detail/stack_allocator.hpp +++ /dev/null @@ -1,161 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021 Alfredo A. Correa - -#ifndef BOOST_MULTI_DETAIL_STACK_ALLOCATOR_HPP -#define BOOST_MULTI_DETAIL_STACK_ALLOCATOR_HPP - -#include "monotonic_allocator.hpp" - -#include -#include -#include -#include - -namespace boost { -namespace multi { - -template< - class Alloc = std::allocator, - typename std::allocator_traits::size_type MaxAlignemnt = alignof(std::max_align_t) -> -class stack_buffer : private monotonic_buffer{ - using base_ = monotonic_buffer; - - public: - using typename base_::pointer; - using typename base_::void_pointer; - using typename base_::char_pointer; - using typename base_::allocator_type; - using typename base_::size_type; - using typename base_::difference_type; - - private: - std::stack positions_ = {}; - size_type stack_recovered_ = 0; - size_type max_needed_ = 0; - - public: - using base_::hits; - using base_::misses; - using base_::allocated_bytes; - using base_::deallocated_bytes; - size_type max_needed() const {return max_needed_;} - size_type stack_recovered() const {return stack_recovered_;} - - using base_::size; - using base_::allocator; - - stack_buffer(size_type bytes, allocator_type alloc) - : monotonic_buffer{bytes, alloc} {} - - explicit stack_buffer(size_type bytes) : stack_buffer{bytes, allocator_type{}} {} - - - void reset(size_type bytes = 0) { - assert(this->position_ == 0 and positions_.empty()); - base_::reset(bytes); - } - - template - void_pointer allocate(size_type req_bytes, size_type al = RequiredAlignment) { - if(req_bytes == 0) return this->buffer_ + this->position_; - static_assert( RequiredAlignment <= MaxAlignemnt, "!"); - assert( al <= this->max_alignment ); - auto bytes = this->align_up(req_bytes); - if(this->position_ + bytes <= this->size_) { - auto old_position_ = this->position_; - positions_.push(this->position_); - this->position_ += bytes; - ++this->hits_; - this->allocated_bytes_ += bytes; - max_needed_ = std::max(max_needed_, this->allocated_bytes_ - this->deallocated_bytes_); - return this->buffer_ + old_position_; - } - ++this->misses_; - auto p = allocator_type::allocate(bytes/sizeof(std::max_align_t)); - if(p) { - this->allocated_bytes_ += bytes; - max_needed_ = std::max(max_needed_, this->allocated_bytes_ - this->deallocated_bytes_); - } - return p; - } - - void deallocate(void_pointer p, size_type req_bytes) { - if(req_bytes == 0) return; - auto bytes = this->align_up(req_bytes); - this->deallocated_bytes_ += bytes; - if(not this->in_buffer(static_cast(p))) { - allocator_type::deallocate(static_cast(p), bytes/sizeof(std::max_align_t)); - } else { - if(std::distance(static_cast(static_cast(this->buffer_)), static_cast(p)) == static_cast(positions_.top())) { - this->position_ -= bytes; - positions_.pop(); - stack_recovered_ += bytes; - #ifndef _BOOST_MULTI_RELAX_STACK_CONDITION - } else { - assert(0 && "stack violation"); // throw std::logic_error{"stack violation!"}; // careful with throwing from deallocation! TODO should invalidate the buffer? - #endif - } - } - } -}; - -template, typename std::allocator_traits::size_type MaxAlignemnt = alignof(std::max_align_t)> -using stack_allocator = multi::generic_allocator>; - -} // end namespace multi -} // end namespace boost - -#if _TEST_BOOST_MULTI_DETAIL_STACK_ALLOCATOR - -#include "../../multi/array.hpp" -#include - -#include -#include -#include - -namespace multi = boost::multi; -using std::cout; -using boost::alignment::is_aligned; - -int main() { - { - std::size_t guess_bytes = 120; - multi::stack_buffer, 32> buf{guess_bytes}; - - for(int i = 0; i != 3; ++i) { - cout<<"pass "<< i << std::endl; - { - multi::stack_allocator, 32> sa(&buf); - multi::array, 32>> A({2, 10}, &buf); assert( is_aligned(alignof(double), &A[1][3]) ); - multi::array, 32>> B({3, 10}, &buf); assert( is_aligned(alignof(double), &B[2][3]) ); - multi::array, 32>> C({4, 10}, &buf); assert( is_aligned(alignof(double), &C[3][3]) ); - std::vector, 32>> v(3, &buf); assert( is_aligned(alignof(int), &v[1]) ); - - for(int j = 0; j != 100; ++j) { - multi::array, 32> > D({4, 10}, &buf); assert( is_aligned(alignof(double), &D[2][1]) ); - multi::array > E({4, 10}, &buf); assert( is_aligned(alignof(float), &E[3][3]) ); - } - - multi::array > F({4, 10}, &buf); assert( is_aligned(alignof(double), &F[2][1]) ); - } - cout<<" size: "<< buf.size() - <<"\n hits: "<< buf.hits() - <<"\n misses "<< buf.misses() - <<"\n allocated(bytes) "<< buf.allocated_bytes() - <<"\n deallocated(bytes) "<< buf.deallocated_bytes() - <<"\n max_needed(bytes) "<< buf.max_needed() - <<"\n stack recovered(bytes) " << buf.stack_recovered() - < sad{&buf}; - multi::stack_allocator sai{sad}; // check constructor - } -} -#endif -#endif - diff --git a/include/multi/detail/tuple_zip.hpp b/include/multi/detail/tuple_zip.hpp deleted file mode 100644 index 98732674c..000000000 --- a/include/multi/detail/tuple_zip.hpp +++ /dev/null @@ -1,410 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021-2023 Alfredo A. Correa - -#ifndef MULTI_DETAIL_TUPLE_ZIP_HPP -#define MULTI_DETAIL_TUPLE_ZIP_HPP -#pragma once - -#include -#include - -#include // for deprecated functions - -namespace boost::multi { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat -namespace detail { - -template class tuple; - -template<> class tuple<> { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - public: - constexpr tuple() = default; - constexpr tuple(tuple const&) = default; - - constexpr auto operator=(tuple const&) -> tuple& = default; - - constexpr auto operator==(tuple const& /*other*/) const -> bool {return true ;} - constexpr auto operator!=(tuple const& /*other*/) const -> bool {return false;} - - constexpr auto operator< (tuple const& /*other*/) const {return false;} - constexpr auto operator> (tuple const& /*other*/) const {return false;} -}; - -template class tuple : tuple { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - T0 head_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) can be a reference - using head_type = T0; - using tail_type = tuple; -// tuple tail_; // TODO(correaa) use [[no_unique_address]] in C++20 - - public: - constexpr auto head() const& -> T0 const& { return head_ ; } - constexpr auto head() && -> decltype(auto) { return std::move(head_); } - constexpr auto head() & -> T0 & { return head_ ; } - - constexpr auto tail() const& -> tail_type const& { return static_cast(*this); } - constexpr auto tail() && -> decltype(auto) { return static_cast(*this); } - constexpr auto tail() & -> tail_type & { return static_cast(*this); } - - constexpr tuple() = default; - constexpr tuple(tuple const&) = default; - - // template().head())> and - // std::is_constructible_v().tail())> - // >> - // constexpr tuple(tuple const& other) : head_{other.head()}, tuple{other.tail()} {} - - // template =0> - // constexpr tuple(tuple const& other) - - // cppcheck-suppress noExplicitConstructor ; allow bracket init in function argument // NOLINTNEXTLINE(runtime/explicit) - constexpr tuple(T0 head, tuple tail) : tail_type{std::move(tail) }, head_{std::move(head)} {} - constexpr explicit tuple(T0 head, Ts... tail) : tail_type{ tail...}, head_{ head } {} - - constexpr auto operator=(tuple const&) -> tuple& = default; - - template - constexpr auto operator=(tuple const& other) // NOLINT(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) signature used for SFINAE - ->decltype(std::declval() = other.head(), std::declval() = other.tail(), std::declval()) { - head_ = other.head(), tail() = other.tail(); - return *this; - } - - constexpr auto operator==(tuple const& other) const -> bool {return head_ == other.head_ and tail() == other.tail();} - constexpr auto operator!=(tuple const& other) const -> bool {return head_ != other.head_ or tail() != other.tail();} - - constexpr auto operator< (tuple const& other) const { - if(head_ < other.head_) {return true ;} - if(other.head_ < head_) {return false;} - return tail() < other.tail(); - } - constexpr auto operator> (tuple const& other) const { - if(head_ > other.head_) {return true ;} - if(other.head_ > head_) {return false;} - return tail() > other.tail(); - } - - private: - template struct priority : std::conditional_t> {}; - - template - constexpr auto at_aux(priority<0> /*prio*/, Index idx) const - ->decltype(ht_tuple(std::declval(), std::declval()[idx])) { - return ht_tuple(head() , tail()[idx] );} - - template - constexpr auto at_aux(priority<1> /*prio*/, Index idx) const - ->decltype(ht_tuple(std::declval()[idx], std::declval())) { - return ht_tuple(head() [idx], tail() ); } - - public: - template - constexpr auto operator[](Index idx) const - ->decltype(std::declval const&>().at_aux(priority<1>{}, idx)){ - return this-> at_aux(priority<1>{}, idx);} - - template - constexpr auto get() const& -> auto const& { // NOLINT(readability-identifier-length) std naming - if constexpr(N == 0) { - return head(); - } else { - return tail().template get(); - } - } - - template - constexpr auto get() & -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - if constexpr(N == 0) { - return head(); - } else { - return tail().template get(); - } - } - - template - constexpr auto get() && -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - if constexpr(N == 0) { - return std::move(*this).head(); - } else { - return std::move(*this).tail().template get(); - } - } - - -}; - -#if defined(__INTEL_COMPILER) // this instance is necessary due to a bug in intel compiler icpc -// TODO(correaa) : this class can be collapsed with the general case with [[no_unique_address]] in C++20 -template class tuple { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - T0 head_; - tuple<> tail_; - - public: - constexpr auto head() const& -> T0 const& {return head_ ;} - constexpr auto head() && -> T0 && {return std::move(head_);} - constexpr auto head() & -> T0 & {return head_ ;} - - constexpr auto tail() const& -> tuple<> const& {return tail_ ;} - constexpr auto tail() && -> tuple<> && {return std::move(tail_);} - constexpr auto tail() & -> tuple<> & {return tail_ ;} - - constexpr tuple() = default; - constexpr tuple(tuple const&) = default; - - // cppcheck-suppress noExplicitConstructor ; allow bracket init in function argument // NOLINTNEXTLINE(runtime/explicit) - constexpr tuple(T0 t0, tuple<> sub) : head_{std::move(t0)}, tail_{sub} {} - constexpr explicit tuple(T0 t0) : head_{std::move(t0)}, tail_{} {} - - constexpr auto operator=(tuple const& other) -> tuple& = default; - - constexpr auto operator==(tuple const& other) const {return head_ == other.head_;} - constexpr auto operator!=(tuple const& other) const {return head_ != other.head_;} - - constexpr auto operator< (tuple const& other) const {return head_ < other.head_;} - constexpr auto operator> (tuple const& other) const {return head_ > other.head_;} -}; -#endif - -template tuple(T0, tuple) -> tuple; - -template constexpr auto mk_tuple(T0 head, Ts... tail) { - return tuple(std::move(head), std::move(tail)...); -} - -template constexpr auto tie(T0& head, Ts&... tail) { - return tuple(head, tail...); -} - -template constexpr auto ht_tuple(T0 head, tuple tail) { - return tuple(std::move(head), std::move(tail)); -} - -template struct tuple_prepend; - -template -struct tuple_prepend> { - using type = tuple; -}; - -template -using tuple_prepend_t = typename tuple_prepend::type; - -template -constexpr auto head(tuple const& t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - return t.head(); -} - -template -constexpr auto head(tuple && t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - return std::move(t.head()); -} - -template -constexpr auto head(tuple & t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - return t.head(); -} - -template -constexpr auto tail(tuple const& t) -> decltype(t.tail()) {return t.tail();} // NOLINT(readability-identifier-length) std naming - -template -constexpr auto tail(tuple && t) -> decltype(std::move(t).tail()) {return std::move(t).tail();} // NOLINT(readability-identifier-length) std naming - -template -constexpr auto tail(tuple & t) -> decltype(t.tail()) {return t.tail();} // NOLINT(readability-identifier-length) std naming - -#if defined __NVCC__ // in place of global -Xcudafe \"--diag_suppress=implicit_return_from_non_void_function\" - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -#if not defined(_MSC_VER) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wreturn-type" -#endif -template -constexpr auto get(tuple const& t) -> auto const& { // NOLINT(readability-identifier-length) std naming - if constexpr(N == 0) { - return t.head(); - } else { - return get(t.tail()); - } -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #pragma diagnostic pop - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -template -constexpr auto get(tuple& t) -> auto& { // NOLINT(readability-identifier-length) std naming - if constexpr(N == 0) { - return t.head(); - } else { - return get(t.tail()); - } -} - -template -constexpr auto get(tuple&& t) -> auto&& { // NOLINT(readability-identifier-length) std naming - if constexpr(N == 0) { - return std::move(t).head(); - } else { - return get(std::move(t.tail())); - } -} -#if not defined(_MSC_VER) -#pragma GCC diagnostic pop -#endif - -} // end namespace detail -} // end namespace boost::multi - -// NOLINTBEGIN(cert-dcl58-cpp) define stuff in STD -namespace std { - -template -struct tuple_size> { - // cppcheck-suppress unusedStructMember - static constexpr std::size_t value = sizeof...(Ts); -}; - -template -struct tuple_element<0, boost::multi::detail::tuple> { - using type = T0; -}; - -template -struct tuple_element> { - using type = typename tuple_element>::type; -}; - -//using boost::multi::detail::get; - -template -constexpr auto get(boost::multi::detail::tuple const& t) // NOLINT(readability-identifier-length) std naming -->decltype(boost::multi::detail::get(t)) { - return boost::multi::detail::get(t); } - -template -constexpr auto get(boost::multi::detail::tuple & t) // NOLINT(readability-identifier-length) std naming -->decltype(boost::multi::detail::get(t)) { - return boost::multi::detail::get(t); } - -template -constexpr auto get(boost::multi::detail::tuple && t) // NOLINT(readability-identifier-length) std naming -->decltype(boost::multi::detail::get(std::move(t))) { - return boost::multi::detail::get(std::move(t)); } - -template -constexpr auto apply_timpl(F&& f, Tuple&& t, std::index_sequence/*012*/) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - (void)t; // fix "error #827: parameter "t" was never referenced" in NVC++ and "error #869: parameter "t" was never referenced" in oneAPI-ICPC - return std::forward(f)(boost::multi::detail::get(std::forward(t))...); -} - -template -constexpr auto apply(F&& f, boost::multi::detail::tuple const& t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - return apply_timpl( - std::forward(f), t, - std::make_index_sequence{} - ); -} - -template -constexpr auto apply(F&& f, boost::multi::detail::tuple& t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - return apply_timpl( - std::forward(f), t, - std::make_index_sequence{} - ); -} - -template -constexpr auto apply(F&& f, boost::multi::detail::tuple&& t) -> decltype(auto) { // NOLINT(readability-identifier-length) std naming - return apply_timpl( - std::forward(f), std::move(t), - std::make_index_sequence{} - ); -} - -} // end namespace std -// NOLINTEND(cert-dcl58-cpp) define stuff in STD - -namespace boost::multi { // NOLINT(modernize-concat-nested-namespaces) keep c++14 compat -namespace detail { - -template -constexpr auto tuple_zip_impl(Tuple1&& tup1, Tuple2&& tup2, std::index_sequence /*012*/) { - using boost::multi::detail::get; - return boost::multi::detail::mk_tuple( - boost::multi::detail::mk_tuple( - get(std::forward(tup1)), - get(std::forward(tup2)) - )... - ); -} - -template -constexpr auto tuple_zip_impl(Tuple1&& tup1, Tuple2&& tup2, Tuple3&& tup3, std::index_sequence /*012*/) { - using boost::multi::detail::get; - return boost::multi::detail::mk_tuple( - boost::multi::detail::mk_tuple( - get(std::forward(tup1)), - get(std::forward(tup2)), - get(std::forward(tup3)) - )... - ); -} - -template -constexpr auto tuple_zip_impl(Tuple1&& tup1, Tuple2&& tup2, Tuple3&& tup3, Tuple4&& tup4, std::index_sequence /*012*/) { - using boost::multi::detail::get; - return boost::multi::detail::mk_tuple( - boost::multi::detail::mk_tuple( - get(std::forward(tup1)), - get(std::forward(tup2)), - get(std::forward(tup3)), - get(std::forward(tup4)) - )... - ); -} - -template -constexpr auto tuple_zip(T1&& tup1, T2&& tup2) { - return detail::tuple_zip_impl( - std::forward(tup1), std::forward(tup2), - std::make_index_sequence::type>::value>() - ); -} - -template -constexpr auto tuple_zip(T1&& tup1, T2&& tup2, T3&& tup3) { - return detail::tuple_zip_impl( - std::forward(tup1), std::forward(tup2), std::forward(tup3), - std::make_index_sequence::type>::value>() - ); -} - -template -constexpr auto tuple_zip(T1&& tup1, T2&& tup2, T3&& tup3, T4&& tup4) { - return detail::tuple_zip_impl( - std::forward(tup1), std::forward(tup2), std::forward(tup3), std::forward(tup4), - std::make_index_sequence::type>::value>() - ); -} - -} // end namespace detail - -using detail::tie; - -} // end namespace boost::multi -#endif diff --git a/include/multi/detail/type_traits.hpp b/include/multi/detail/type_traits.hpp deleted file mode 100644 index 40f9a571e..000000000 --- a/include/multi/detail/type_traits.hpp +++ /dev/null @@ -1,19 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022 Alfredo A. Correa - -#ifndef MULTI_DETAIL_TYPE_TRAITS_HPP -#define MULTI_DETAIL_TYPE_TRAITS_HPP - -#include - -namespace boost { // NOLINT(modernize-concat-nested-namespaces) -namespace multi { - -template struct is_trivially_default_constructible : std::is_trivially_default_constructible {}; -template struct is_trivial : std::is_trivial {}; - - -} // end namespace multi -} // end namespace boost - -#endif diff --git a/include/multi/detail/types.hpp b/include/multi/detail/types.hpp deleted file mode 100644 index 216f456c1..000000000 --- a/include/multi/detail/types.hpp +++ /dev/null @@ -1,24 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2021 Alfredo A. Correa - -#ifndef MULTI_DETAIL_TYPES_HPP -#define MULTI_DETAIL_TYPES_HPP - -// #include "index_range.hpp" - -#include // for std::size_t -// #include // for make_tuple -#include // for make_signed_t -#include // for forward - -namespace boost::multi { - -using size_t = std::make_signed_t; -using size_type = std::make_signed_t; - -using index = std::make_signed_t; -using difference_type = std::make_signed_t; -using dimensionality_type = index; - -} // end namespace boost::multi -#endif diff --git a/include/multi/memory/adaptors/cuda_/algorithm.hpp b/include/multi/memory/adaptors/cuda_/algorithm.hpp deleted file mode 100644 index 43b436c3f..000000000 --- a/include/multi/memory/adaptors/cuda_/algorithm.hpp +++ /dev/null @@ -1,688 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -$CXXX $CXXFLAGS $0 -o $0x -lcudart -lboost_unit_test_framework -lboost_timer&&$0x&&rm $0x;exit -#endif -#ifndef BOOST_MULTI_MEMORY_ADAPTORS_CUDA_ALGORITHM_HPP -#define BOOST_MULTI_MEMORY_ADAPTORS_CUDA_ALGORITHM_HPP - -#include "../cuda/cstring.hpp" -#include "../../../array_ref.hpp" -#include "../../../config/MARK.hpp" -#include "../../../detail/adl.hpp" - -#include -#include -#include -#include - -#include "../cuda/error.hpp" - -#include //TODO remove, handle complex_fix instead? - -namespace boost {namespace multi { -namespace memory {namespace cuda { - -template{}>> -ptr copy_n(ptr first, Size count, ptr result) { - return memcpy(result, first, count*sizeof(T)), result + count; -} - -template{}>> -ptr copy_n(U* first, Size count, ptr result) {return memcpy(result, first, count*sizeof(T)), result + count;} - -template{}>> -T* copy_n(ptr first, Size count, T* result) {return memcpy(result, first, count*sizeof(T)), result + count;} - -template{}>> -ptr uninitialized_copy_n(ptr first, Size count, ptr result) {return memcpy(result, first, count*sizeof(T)), result + count;} - -template{}>> -ptr uninitialized_copy_n(U* first, Size count, ptr result) {return memcpy(result, first, count*sizeof(T)), result + count;} - -template{}>> -T* uninitialized_copy_n(ptr first, Size count, T* result) {return memcpy(result, first, count*sizeof(T)), result + count;} - -template -auto copy(PtrU first, PtrU last, ptr result){ - return copy_n(first, std::distance(first, last), result); -} - -template -auto copy(ptr first, ptr last, ptr result){ - return copy_n(first, std::distance(first, last), result); -} - -//->decltype(copy_n(first, std::distance(first, last), result)){ -// return copy_n(first, std::distance(first, last), result);} - - -template -auto fill(memory::cuda::ptr first, memory::cuda::ptr last, T const& value) -->decltype(fill_n(first, std::distance(first, last), value)){ - return fill_n(first, std::distance(first, last), value);} - -template{}>> -memory::cuda::ptr fill_n(ptr const first, Size count, U const& value){ - if(std::find_if((char const*)(&value), (char const*)(&value) + sizeof(value), [](char c){return c!=0;}) == (char const*)(&value) + sizeof(value)){ -// if(std::find(reinterpret_cast(&value), reinterpret_cast(&value) + sizeof(value), true) == reinterpret_cast(&value) + sizeof(value)){ -// if(value == 0.){ - cuda::memset(first, 0, count*sizeof(T)); - } - else if(count--) for(ptr new_first = adl_copy_n(&value, 1, first); count;){ - auto n = std::min(Size(std::distance(first, new_first)), count); - new_first = copy_n(first, n, new_first); - count -= n; - } - return first + count; -} - -template//, typename = std::enable_if_t{}>> -memory::cuda::ptr fill_n(array_iterator> const first, Size count, U const& value){ - if(count--) - for(ptr new_first = adl_copy_n(&value, 1, first); count;){ - auto n = std::min(Size(std::distance(first, new_first)), count); - new_first = copy_n(first, n, new_first); - count -= n; - } - return first + count; -} - -// TODO fix traits std::complex instead -template, T2>{},int> =0> -ptr> copy_n(ptr first, Size count, ptr> result){ - fill_n(result, count, std::complex{0}); - copy_n( - multi::array_iterator, 1, ptr>{first, 1}, count, - multi::array_iterator>{reinterpret_pointer_cast(result), 2} - ); - return result + count; -} - -template, T2>{},int> =0> -auto copy_n(iterator> first, Size count, iterator, 1, ptr>> result){ - MULTI_MARK_SCOPE("cuda copy_n 1D complex destination"); - if(stride(first) == 1 and stride(result)==1) copy_n(base(first), count, base(result)); - else assert(0); - return result + count; -} - -//template{}, int> =0> -//auto uninitialized_fill_n(cuda::ptr first, Size n, V const& v){return fill_n(first, n, v);} - -template>::value_type>{}, int> =0> -auto uninitialized_fill_n(ptr first, Size n, T const& t) -{ - return fill_n(first, n, t); -} - - -template::value_type, std::enable_if_t{}, int> = 0> -auto uninitialized_value_construct_n(It first, Size n){ - return uninitialized_fill_n(first, n, T()); -} - -template>::value_type>{}, int> = 0> -auto uninitialized_copy_n(It first, Size n, ptr d_first) -->decltype(copy_n(first, n, d_first)){ - return copy_n(first, n, d_first);} - -//template -//auto alloc_uninitialized_value_construct_n(Alloc&, It first, Size n){ -// return uninitialized_value_construct_n(first, n); -//} - -#if 1 - -#if 1 - -template void what() = delete; - -template -array_iterator> copy_n( - array_iterator> first_ , Size count, - array_iterator> result_ -){ - MULTI_MARK_SCOPE("cuda copy_n 1D"); - array_iterator> first ; std::memcpy((void*)&first , (void const*)&first_ , sizeof(first_)); - array_iterator> result; std::memcpy((void*)&result, (void const*)&result_, sizeof(first_)); - static_assert( sizeof(first ) == sizeof(first_ ) ); - static_assert( sizeof(result) == sizeof(result_) ); - ::thrust::for_each( - ::thrust::make_counting_iterator(0L), - ::thrust::make_counting_iterator(count), - [first, result, x = multi::extensions_t<1>(count)] __device__ (auto n){ // requires --extended-lambda nvcc flag - // std::tuple - auto const i = x.from_linear(n); - using boost::multi::detail::get; - result[get<0>(i)] = T2(first[get<0>(i)]); - } - ); - return result_ + count; -} - -template -array_iterator> -copy_n( - array_iterator> first_ , Size count, - array_iterator> result_ -) { - MULTI_MARK_SCOPE("cuda copy_n 2D"); - - array_iterator> first ; std::memcpy((void*)&first , (void const*)&first_ , sizeof(first_)); - array_iterator> result; std::memcpy((void*)&result, (void const*)&result_, sizeof(first_)); - - static_assert( sizeof(first ) == sizeof(first_ ) ); - static_assert( sizeof(result) == sizeof(result_) ); - - assert(first->extensions() == result->extensions()); - ::thrust::for_each( - ::thrust::make_counting_iterator(0L), - ::thrust::make_counting_iterator(count*first->num_elements()), - [first, count, result, x = first->extensions()] __device__ (auto n){ - auto const ij = (count*x).from_linear(n); - result[std::get<0>(ij)][std::get<1>(ij)] = T2(first[std::get<0>(ij)][std::get<1>(ij)]); - } - ); - - return result_ + count; -} - -template -array_iterator> -copy_n( - array_iterator> first_ , Size count, - array_iterator> result_ -) { - MULTI_MARK_SCOPE("cuda copy_n 3D"); - array_iterator> first ; std::memcpy((void*)&first , (void const*)&first_ , sizeof(first_)); - array_iterator> result; std::memcpy((void*)&result, (void const*)&result_, sizeof(first_)); - static_assert( sizeof(first ) == sizeof(first_ ) ); - static_assert( sizeof(result) == sizeof(result_) ); - assert(first->extensions() == result->extensions()); - ::thrust::for_each( - ::thrust::make_counting_iterator(0L), - ::thrust::make_counting_iterator(count*first->num_elements()), - [first, count, result, x = first->extensions()] __device__ (auto n){ - auto const ijk = (count*x).from_linear(n); - result.apply(ijk) = T2(first.apply(ijk)); - } - ); - return result_ + count; -} - -template -array_iterator> -copy_n( - array_iterator> first_ , Size count, - array_iterator> result_ -) { - MULTI_MARK_SCOPE("cuda copy_n 4D"); - array_iterator> first ; std::memcpy((void*)&first , (void const*)&first_ , sizeof(first_)); - array_iterator> result; std::memcpy((void*)&result, (void const*)&result_, sizeof(first_)); - static_assert( sizeof(first ) == sizeof(first_ ) ); - static_assert( sizeof(result) == sizeof(result_) ); - assert(first->extensions() == result->extensions()); - ::thrust::for_each( - ::thrust::make_counting_iterator(0L), - ::thrust::make_counting_iterator(count*first->num_elements()), - [first, count, result, x = first->extensions()] __device__ (auto n){ - auto const ijk = (count*x).from_linear(n); - result.apply(ijk) = T2(first.apply(ijk)); - } - ); - return result_ + count; -} - -template -array_iterator> -copy_n( - array_iterator> first_ , Size count, - array_iterator> result_ -) { - MULTI_MARK_SCOPE("cuda copy_n 5D"); - array_iterator> first ; std::memcpy((void*)&first , (void const*)&first_ , sizeof(first_)); - array_iterator> result; std::memcpy((void*)&result, (void const*)&result_, sizeof(first_)); - static_assert( sizeof(first ) == sizeof(first_ ) ); - static_assert( sizeof(result) == sizeof(result_) ); - assert(first->extensions() == result->extensions()); - ::thrust::for_each( - ::thrust::make_counting_iterator(0L), - ::thrust::make_counting_iterator(count*first->num_elements()), - [first, count, result, x = first->extensions()] __device__ (auto n){ - auto const ijk = (count*x).from_linear(n); - result.apply(ijk) = T2(first.apply(ijk)); - } - ); - return result_ + count; -} - - -template* =0> -array_iterator> -copy( - array_iterator> first_ , array_iterator> last_, - array_iterator> result_ -) {return copy_n(first_, last_ - first_, result_);} - -#endif - -template{}>> -auto copy_n(iterator first, Size count, iterator> result) -->decltype(memcpy2D(result.base(), sizeof(T2)*stride(result), first.base(), sizeof(T1)*stride(first ), sizeof(T1), count), result + count){ - MULTI_MARK_SCOPE("cuda copy_n 1D cpu source gpu destination"); - return memcpy2D(base(result), sizeof(T2)*stride(result), base(first), sizeof(T1)*stride(first ), sizeof(T1), count), result + count;} - -template{}>> -auto copy_n(iterator> first, Size count, iterator result) -->decltype(memcpy2D(result.base(), sizeof(T2)*stride(result), first.base(), sizeof(T1)*stride(first), sizeof(T1), count), result+count){ - MULTI_MARK_SCOPE("cuda copy_n 1D gpu source cpu destination"); - return memcpy2D(base(result), sizeof(T2)*stride(result), base(first), sizeof(T1)*stride(first), sizeof(T1), count), result+count;} - -//template{}>> -//auto copy_n(iterator> first, Size count, iterator> result) -//->decltype(memcpy2D(result.base(), sizeof(T2)*stride(result), first.base(), sizeof(T1)*stride(first), sizeof(T1), count), result + count){assert(0); -// return memcpy2D(base(result), sizeof(T2)*stride(result), base(first), sizeof(T1)*stride(first), sizeof(T1), count), result + count;} - -template{}>> -auto copy_n(iterator> first, Size count, iterator> result) -->decltype(memcpy2D(result.base(), sizeof(T2)*stride(result), first.base(), sizeof(T1)*stride(first), sizeof(T1), count), result + count){ - MULTI_MARK_SCOPE("cuda copy_n 1D gpu managed source gpu managed destination"); - return memcpy2D(base(result), sizeof(T2)*stride(result), base(first), sizeof(T1)*stride(first), sizeof(T1), count), result + count;} - -template{}>> -auto copy(iterator> first, iterator> last, iterator> result) -->decltype(copy_n(first, last - first, result)){ - return copy_n(first, last - first, result);} - -#endif - -#define ENABLE_IF class=std::enable_if_t - -template{}, int> =0 - ENABLE_IF{}> -// typename = std::enable_if_t{}> -> -auto copy_n(array_iterator> first, Size count, array_iterator> result) -//->decltype(memcpy2D(base(result), sizeof(T2)*stride(result), base(first), sizeof(T1)*stride(first), sizeof(T1), count), result + count) -{ return memcpy2D(base(result), sizeof(T2)*stride(result), base(first), sizeof(T1)*stride(first), sizeof(T1), count), result + count;} - -template -auto copy(array_iterator> first, array_iterator> last, array_iterator> result) -//->decltype(cuda::copy_n(first, last - first, result)) -{ return cuda::copy_n(first, last - first, result);} - -//template -//auto copy(array_iterator> first, array_iterator> last, array_iterator> result){ -// std::cout << boost::stacktrace::stacktrace() << std::endl; -// assert(0); -//} - - -template{}, int> =0> -auto uninitialized_value_construct_n(ptr first, Size n){return uninitialized_fill_n(first, n, T{});} - -namespace managed { - -//template{}>> -//auto copy_n( -// array_iterator> first, Size n, -// array_iterator> result -//) -//->decltype(memcpy2D(base(result), sizeof(T2)*stride(result), base(first), sizeof(T1)*stride(first), sizeof(T1), n), result + n){ -// return memcpy2D(base(result), sizeof(T2)*stride(result), base(first), sizeof(T1)*stride(first), sizeof(T1), n), result + n;} - -//template//, std::enable_if_t{}, int> =0> -//auto copy_n( -// managed::ptr first, Size count, -// managed::ptr result -//) -//->decltype(cuda::copy_n(cuda::ptr(first), count, cuda::ptr(result))){ -//{ return cuda::copy_n(cuda::ptr(first), count, cuda::ptr(result));} - - -template//, std::enable_if_t{}, int> =0> -auto copy_n( - managed::ptr first, Size count, - managed::ptr result -) -//->decltype(cuda::copy_n(cuda::ptr(first), count, cuda::ptr(result))){ -{ return cuda::copy_n(cuda::ptr(first), count, cuda::ptr(result));} - -template//, std::enable_if_t{}, int> =0> -auto copy( - managed::ptr first, managed::ptr last, - managed::ptr result -) -//->decltype(cuda::copy(first, last, result)){assert(0); -{ return cuda::copy(cuda::ptr(first), cuda::ptr(last), cuda::ptr(result)), result + (last - first);} - - -//template, T2>{},int> =0> -inline -managed::ptr> copy_n(managed::ptr /*first*/, std::size_t /*count*/, managed::ptr> result) { - assert(0); - return result; -} - - -template -auto copy_n( - array_iterator> first, Size count, - array_iterator> d_first -){ - return cuda::copy_n(array_iterator>(first), count, array_iterator>(d_first)), d_first + count; -} - -template -auto copy_n( - array_iterator> first, Size count, - array_iterator> d_first -){ - return cuda::copy_n(array_iterator>(first), count, array_iterator>(d_first)), d_first + count; -} - -template -auto copy_n( - array_iterator> first, Size count, - array_iterator> d_first -){ - return cuda::copy_n(array_iterator>(first), count, array_iterator>(d_first)), d_first + count; -} - -template -auto copy_n( - array_iterator> first, Size count, - array_iterator> d_first -){ - return cuda::copy_n(array_iterator>(first), count, array_iterator>(d_first)), d_first + count; -} - -template -auto copy_n( - array_iterator> first, Size count, - array_iterator> d_first -){ - return cuda::copy_n(array_iterator>(first), count, array_iterator>(d_first)), d_first + count; -} - - -template -auto copy( - array_iterator> first, - array_iterator> last, - array_iterator> d_first -){ - return managed::copy_n(first, last - first, d_first); -} - -template -auto copy( - array_iterator> first, - array_iterator> last, - array_iterator> d_first -){ - return managed::copy_n(first, last - first, d_first); -} - -template -auto copy( - array_iterator> first, - array_iterator> last, - array_iterator> d_first -){ - return managed::copy_n(first, last - first, d_first); -} - -template -auto copy( - array_iterator> first, - array_iterator> last, - array_iterator> d_first -){ - return managed::copy_n(first, last - first, d_first); -} - -template -auto copy( - array_iterator> first, - array_iterator> last, - array_iterator> d_first -){ - return managed::copy_n(first, last - first, d_first); -} - -template//, typename = std::enable_if_t{}>> -auto fill_n(managed::ptr first, Size count, U const& value){ - return fill_n(cuda::ptr(first), count, value), first + count; -} - -template{}, int> =0> -auto uninitialized_fill_n(managed::ptr first, Size n, V const& v){return fill_n(first, n, v);} - -template::value_type>{}, int> = 0> -auto alloc_uninitialized_copy(Alloc&, Ptr first, Ptr last, ForwardIt dest) -->decltype(cuda::copy(first, last, dest)){ - return cuda::copy(first, last, dest);} - -template::value_type>{}, int> = 0> -auto alloc_uninitialized_copy(Alloc&, Ptr first, Size n, ForwardIt dest) -->decltype(cuda::copy_n(first, n, dest)){ - return cuda::copy_n(first, n, dest);} - -template{}, int> =0> -auto uninitialized_default_construct_n(cuda::managed::ptr first, Size n){return first + n;} - -template{}, int> =0> -auto uninitialized_default_construct_n(cuda::managed::ptr, TP> first, Size n){ - return first + n; // TODO remove? -} - -template{}, int> =0> -auto destroy_n(cuda::managed::ptr first, Size n){return first + n;} - -} - -}} - -/* -template{}>> -array_iterator> copy_n( - T1* first, Size count, - array_iterator> result -){ - std::cout << "count " << std::endl; - return copy_n, 1, T1*>>(first, count, result); -}*/ - -#if 0 -template -auto copy( - array_iterator> f, array_iterator> l, - array_iterator> d -) -->decltype(copy_n(f, std::distance(f, l), d)){assert(stride(f)==stride(l)); - return copy_n(f, std::distance(f, l), d);} - -template -auto copy( - array_iterator f, array_iterator> l, - array_iterator> d -) -->decltype(copy_n(f, std::distance(f, l), d)){assert(stride(f)==stride(l)); - return copy_n(f, std::distance(f, l), d);} -#endif - -}} - -#define FWD(x) std::forward(x) - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//#if not __INCLUDE_LEVEL__ // _TEST_MULTI_MEMORY_ADAPTORS_CUDA_ALGORITHM - -//#define BOOST_TEST_MODULE "C++ Unit Tests for Multi initializer_list" -//#define BOOST_TEST_DYN_LINK -//#include - -//#include "../../../array.hpp" -//#include "../../../adaptors/cuda.hpp" - -//#include "../cuda/allocator.hpp" -//#include -//#include - -//namespace multi = boost::multi; -//namespace cuda = multi::memory::cuda; - -//BOOST_AUTO_TEST_CASE(copy_1d){ -// auto const A_cpu = []{ -// multi::array ret(10); -// std::generate( -// ret.data_elements(), ret.data_elements() + ret.num_elements(), std::rand -// ); -// return ret; -// }(); -// std::cout<<"memory size "<< A_cpu.num_elements()*sizeof(decltype(A_cpu)::element)/1e6 <<" MB\n"; -// { -// multi::array B_cpu(size(A_cpu)); -// boost::timer::auto_cpu_timer t{"cpu->cpu %ws wall, CPU (%p%)\n"}; -// B_cpu = A_cpu; -// } -// { -// multi::cuda::array A_gpu(size(A_cpu)); -// multi::cuda::array B_gpu(size(A_cpu)); -// boost::timer::auto_cpu_timer t{"gpu->gpu %ws wall, CPU (%p%)\n"}; -// B_gpu = A_gpu; -// cudaDeviceSynchronize(); -// } -// { -// multi::cuda::array B_gpu(size(A_cpu)); -// boost::timer::auto_cpu_timer t{"cpu->gpu %ws wall, CPU (%p%)\n"}; -// B_gpu = A_cpu; -// } -// multi::cuda::array C_cpu(size(A_cpu)); -// { -// multi::cuda::array B_gpu(size(A_cpu)); -// boost::timer::auto_cpu_timer t{"gpu->cpu %ws wall, CPU (%p%)\n"}; -// C_cpu = B_gpu; -// cudaDeviceSynchronize(); -// } -// { -// multi::cuda::managed::array const A_mng(size(A_cpu)); -// multi::cuda::managed::array B_mng(size(A_cpu)); -// { -// boost::timer::auto_cpu_timer t{"cold mng->mng %ws wall, CPU (%p%)\n"}; -// B_mng = A_mng; -// cudaDeviceSynchronize(); -// } -// { -// boost::timer::auto_cpu_timer t{"haut mng->mng %ws wall, CPU (%p%)\n"}; -// B_mng = A_mng; -// cudaDeviceSynchronize(); -// } -// } - -//// multi::array Bcpu(3); -//// Bcpu = B; -//// BOOST_REQUIRE( Bcpu[1] == 3. ); -//} - -//#if 0 -//BOOST_AUTO_TEST_CASE(multi_memory_adaptors_cuda_copy_2D){ -// multi::array A(50, 99.); -// multi::cuda::array B(50); -// BOOST_REQUIRE( size(B) == 50 ); - -//// using std::copy_n; -//// using std::copy; -// using boost::multi::adl::copy_n; -//// copy_n(&A[0], size(A), &B[0]); -// copy_n(begin(A), size(A), begin(B)); - -// multi::cuda::array D(50); -// copy_n(begin(B), size(B), begin(D)); - -// multi::array C(50, 88.); -// copy_n(begin(D), size(D), begin(C)); -//// C = B; - -//// BOOST_REQUIRE( C == A ); -//} - -//BOOST_AUTO_TEST_CASE(multi_cuda_managed_array_initialization_complex){ -// multi::cuda::managed::array B = {1. + 2.*I, 3. + 1.*I, 4. + 5.*I}; -// multi::array Bcpu(3); -// Bcpu = B; -// BOOST_REQUIRE( Bcpu[1] == 3. + 1.*I ); -//} - -//namespace utf = boost::unit_test; - -//#if 0 -//BOOST_AUTO_TEST_CASE(multi_memory_adaptors_cuda_algorithm, *utf::disabled()){ -// BOOST_REQUIRE(false); -// multi::cuda::array const A(10, 99.); -// multi::cuda::array B(10, 88.); -// B = A; - -// B() = A(); - -//// B.assign({1., 2., 3., 4.}); -// B = {1., 2., 3., 4.}; -// BOOST_REQUIRE( size(B) == 4 ); - -// B().assign({11., 22., 33., 44.});//.begin(), il.end()); -//// BOOST_REQUIRE( B[2] == 33. ); -///// B.assign - - -//// multi::cuda::array B({10, 10}, 88.); -//// B = A; -//#if 0 -// { -// cuda::allocator calloc; -// std::size_t n = 2e9/sizeof(double); -// [[maybe_unused]] cuda::ptr p = calloc.allocate(n); -// { -// boost::timer::auto_cpu_timer t; -// // using std::fill_n; fill_n(p, n, 99.); -// } -// // assert( p[0] == 99. ); -// // assert( p[n/2] == 99. ); -// // assert( p[n-1] == 99. ); -// [[maybe_unused]] cuda::ptr q = calloc.allocate(n); -// { -// boost::timer::auto_cpu_timer t; -// // multi::omp_copy_n(static_cast(p), n, static_cast(q)); -// // using std::copy_n; copy_n(p, n, q); -// using std::copy; copy(p, p + n, q); -// } -// { -// boost::timer::auto_cpu_timer t; -// // using std::copy_n; copy_n(p, n, q); -// } -// // assert( q[23] == 99. ); -// // assert( q[99] == 99. ); -// // assert( q[n-1] == 99. ); -// } -// { -// multi::array const A(100);//, double{99.}); -// multi::array> A_gpu = A; -// #pragma GCC diagnostic push // allow cuda element access -// #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -// // assert( A_gpu[1] == A_gpu[0] ); -// #pragma GCC diagnostic pop -// } -//#endif -// { -// multi::array A({32, 8}, 99.); -// multi::array> A_gpu({32, 8}, 0.);// = A;//({32, 8000});// = A; -// } -// -//} -//#endif -//#endif -//#endif -#endif diff --git a/include/multi/memory/adaptors/cuda_/allocator.hpp b/include/multi/memory/adaptors/cuda_/allocator.hpp deleted file mode 100644 index 84e1b3fdb..000000000 --- a/include/multi/memory/adaptors/cuda_/allocator.hpp +++ /dev/null @@ -1,239 +0,0 @@ -#ifdef COMPILATION// -*- indent-tabs-mode:t;c-basic-offset:4;tab-width:4; -*- -$CXXX $CXXFLAGS $0 -o $0x -lcudart -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif -// © Alfredo A. Correa 2020 - -#ifndef MULTI_MEMORY_ADAPTORS_CUDA_ALLOCATOR_HPP -#define MULTI_MEMORY_ADAPTORS_CUDA_ALLOCATOR_HPP - -#include // cudaMalloc - -#include "../../adaptors/cuda/ptr.hpp" -#include "../../adaptors/cuda/algorithm.hpp" - -#include "../../adaptors/cuda/clib.hpp" // cuda::malloc -#include "../../adaptors/cuda/cstring.hpp" // cuda::memcpy -#include "../../adaptors/cuda/malloc.hpp" - -#include // bad_alloc -#include -#include // debug - -#include - -#include - -namespace boost{namespace multi{ -namespace memory{namespace cuda{ - -struct bad_alloc : std::bad_alloc {}; - -//struct allocation_counter { -// static long n_allocations; -// static long n_deallocations; -// static long bytes_allocated; -// static long bytes_deallocated; -//}; - -//long allocation_counter::n_allocations = 0; -//long allocation_counter::n_deallocations = 0; -//long allocation_counter::bytes_allocated = 0; -//long allocation_counter::bytes_deallocated = 0; - -template -class allocator {//: protected allocation_counter { - static_assert(std::is_same>{}, - "allocated type should be a value type, not a reference or decorated type"); - - public: - using value_type = T; - using pointer = ptr; - using const_pointer = ptr; - using void_pointer = ptr; - using const_void_pointer = ptr; - using difference_type = typename pointer::difference_type; - template using rebind = allocator; - using size_type = ::size_t; // as specified by CudaMalloc - - allocator() = default; - template - allocator(allocator const& /*other*/) noexcept {} - - pointer allocate(size_type n, const_void_pointer = 0) {//const void* = 0) { - if(n == 0) return pointer{nullptr}; - auto ret = static_cast(cuda::malloc(n*sizeof(T))); - if(not ret) throw bad_alloc{}; - // ++n_allocations; bytes_allocated+=sizeof(T)*n; - return ret; - } - void deallocate(pointer p, size_type n) { - cuda::free(p); - // ++n_deallocations; bytes_deallocated+=sizeof(T)*n; - } - - std::true_type operator==(allocator const&) const {return {};} - std::false_type operator!=(allocator const&) const {return {};} - - template - [[deprecated("cuda slow")]] - void construct(P p, Args&&... args) = delete;/*{ - if(sizeof...(Args) == 0 and std::is_trivially_default_constructible{}) - cuda::memset(p, 0, sizeof(T)); - else{ - char buff[sizeof(T)]; - ::new(buff) T(std::forward(args)...); - cuda::memcpy(p, buff, sizeof(T)); - } - }*/ - template - [[deprecated("cuda slow")]] - void destroy(P p) { - if(not std::is_trivially_destructible{}) { - std::array buff; // char buff[sizeof(T)]; - cuda::memcpy(buff.data(), p, buff.size()); - ((T*)buff)->~T(); - } - } - -#if 0 - template//, typename T1 = typename std::iterator_traits::value_type> - auto alloc_uninitialized_copy_n(InputIt first, Size count, ForwardIt d_first) - DECLRETURN(adl_uninitialized_copy_n(first, count, d_first)) - - template::value_type> - auto alloc_uninitialized_move_n(InputIt first, Size count, ForwardIt d_first) - DECLRETURN(adl_uninitialized_move_n(first, count, d_first)) - - template::value_type> - auto alloc_uninitialized_copy(InputIt first, InputIt last, ForwardIt d_first) - DECLRETURN(adl_uninitialized_copy(first, last, d_first)) - -// DECLRETURN(adl_uninitialized_copy(first, count, d_first)) - template::value_type> - auto alloc_uninitialized_copy(InputIt first, Size count, ForwardIt d_first) - DECLRETURN(uninitialized_copy(first, count, d_first)) - template//, std::enable_if_t > - auto alloc_uninitialized_value_construct_n(Ptr p, Size n) - DECLRETURN(uninitialized_value_construct_n(p, n)) - - template - auto alloc_uninitialized_default_construct_n(Ptr p, Size n) - DECLRETURN(uninitialized_default_construct_n(p, n)) - - template{}, int> =0>// = typename Ptr::element_type> - Ptr alloc_uninitialized_fill_n(Ptr p, Size n, V const& v){ - return uninitialized_fill_n(p, n, v);} - template - static std::true_type is_complex_(std::complex); - static std::false_type is_complex_(...); - template struct is_complex : decltype(is_complex_(TT{})){}; - template< - class Ptr, class Size, class V = typename Ptr::element_type, - std::enable_if_t{} or is_complex{}, int> = 0 - > - Ptr alloc_uninitialized_default_construct_n(Ptr const& p, Size n) const{return p + n;} - template - Ptr alloc_destroy_n(Ptr p, Size n){ - if(std::is_trivially_destructible{}) { - } else {assert(0);} - return p + n; - } -#endif -}; - -template<> -class allocator {//: allocation_counter{ - public: - using T = std::max_align_t; - using value_type = T; - using pointer = ptr; - -// using void_pointer = ptr; - using const_void_pointer = ptr; -// using difference_type = typename pointer::difference_type; - - using size_type = ::size_t; // as specified by CudaMalloc - auto allocate(size_type n, const_void_pointer = 0) { // const void* = 0){ - if(n == 0) return pointer{nullptr}; - auto ret = static_cast(cuda::malloc(n*sizeof(T))); - if(not ret) throw bad_alloc{}; - // ++n_allocations; bytes_allocated+=sizeof(T)*n; - return ret; - } - void deallocate(pointer p, size_type n) { - cuda::free(p); - // ++n_deallocations; bytes_deallocated+=sizeof(T)*n; - } - std::true_type operator==(allocator const&) const {return {};} // template explicit for nvcc - std::false_type operator!=(allocator const&) const {return {};} - - template - void construct(/*[[maybe_unused]]*/ P p, Args&&...) {(void)p; assert(0);} // TODO investigate who is calling this - template - void destroy(P) {} // TODO(correaa) investigate who is calling this -}; - -}}}} - -namespace std { - -#if __NVCC__ // this solves this error with nvcc error: ‘template using __pointer = typename _Tp::pointer’ is protected within this context -template -class allocator_traits> { - using Alloc = boost::multi::memory::cuda::allocator; - - public: - using allocator_type = Alloc; - using value_type = typename Alloc::value_type; - using pointer = typename Alloc::pointer; - using const_pointer = typename Alloc::const_pointer; - using void_pointer = typename Alloc::void_pointer; - using const_void_pointer = typename Alloc::const_void_pointer; - using difference_type = typename Alloc::difference_type; - using size_type = typename Alloc::size_type; - using propagate_on_container_copy_assignment = std::false_type; - using propagate_on_container_move_assignment = std::false_type; - using propagate_on_container_swap = std::false_type; - template - using rebind_alloc = typename Alloc::template rebind; - - static constexpr Alloc select_on_container_copy_construction(Alloc const& a) {return a;} - - template static auto deallocate(allocator_type& a, As&&... as) {return a.deallocate(std::forward(as)...);} - template static auto allocate(allocator_type& a, As&&... as) {return a. allocate(std::forward(as)...);} -}; -#endif - -} // end namespace std - -//#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ -//#define BOOST_TEST_MODULE "C++ Unit Tests for Multi memory allocator" -//#define BOOST_TEST_DYN_LINK -//#include - -//#include -//#include -//#include - -//#include "../../../array.hpp" -//#include "../cuda/algorithm.hpp" - -//namespace multi = boost::multi; -//namespace cuda = multi::memory::cuda; - -//void add_one(double& d){d += 1.;} -//template void add_one(T&& t){std::forward(t) += 1.;} - -//template void what(T&&) = delete; -//using std::cout; - -//BOOST_AUTO_TEST_CASE(multi_memory_allocator){ -// { -// multi::static_array A(32, double{}); A[17] = 3.; -// multi::static_array> A_gpu = A; -// BOOST_REQUIRE( A_gpu[17] == 3 ); -// } -//} -//#endif -#endif - diff --git a/include/multi/memory/adaptors/cuda_/cached_/allocator.hpp b/include/multi/memory/adaptors/cuda_/cached_/allocator.hpp deleted file mode 100644 index 1b425eff9..000000000 --- a/include/multi/memory/adaptors/cuda_/cached_/allocator.hpp +++ /dev/null @@ -1,209 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2022 Alfredo A. Correa - -#ifndef MEMORY_ADAPTORS_CUDA_CACHED_ALLOCATOR_HPP_ -#define MEMORY_ADAPTORS_CUDA_CACHED_ALLOCATOR_HPP_ - -#include "../../../adaptors/cuda/allocator.hpp" -#include "../../../adaptors/cuda/cached/ptr.hpp" - -#include "../../../adaptors/cuda/cached/clib.hpp" // cuda::malloc -#include "../../../adaptors/cuda/cached/malloc.hpp" - -#include -#include -#include // for debug -#include -#include // for bad_alloc -#include -#include - -namespace boost::multi::memory::cuda { - -namespace cached { - -template -class allocator_cache { - - struct block { - size_t size; - PointerType loc; - }; - - static const size_t max_size = 4ul*1024ul*1024ul*1024ul; - static const unsigned max_entries = 200; - std::list blocks_; - std::unordered_multimap map_; - size_t mem_used; - - public: - allocator_cache() - : mem_used(0) {} - - auto put(size_t size, PointerType loc) { - - if(size >= max_size) return false; - - while(size + mem_used > max_size or map_.size() >= max_entries) { - assert(map_.size() > 0); - - cuda::cached::free(blocks_.back().loc); - mem_used -= blocks_.back().size; - auto range = map_.equal_range(blocks_.back().size); - for(auto it = range.first; it != range.second; ++it) { - if(it->second == --blocks_.end()) { - map_.erase(it); - break; - } - }; - blocks_.pop_back(); - } - - blocks_.emplace_front(block{size, loc}); - map_.emplace(size, blocks_.begin()); - assert(map_.size() == blocks_.size()); - mem_used += size; - - return true; - } - - PointerType get(size_t size) { - PointerType loc; - auto pos = map_.find(size); - if(pos != map_.end()) { - auto block_pos = pos->second; - loc = block_pos->loc; - blocks_.erase(block_pos); - map_.erase(pos); - mem_used -= size; - } else { - loc = nullptr; - } - assert(map_.size() == blocks_.size()); - return loc; - } -}; - -auto & cache() { - static allocator_cache> alloc_cache; - return alloc_cache; -} - -struct bad_alloc : std::bad_alloc {}; - -template -class allocator : cuda::allocator { - static_assert( std::is_same>{}, "!" ); - - public: - using value_type = T; - using pointer = cached::ptr; - using size_type = ::size_t; // as specified by CudaMalloc - using const_void_pointer = cached::ptr; - template using rebind = cached::allocator; - - pointer allocate(typename allocator::size_type n) { - MULTI_MARK_SCOPE("cuda::cached::allocate"); - - if(n == 0) return pointer{nullptr}; - - auto ret = static_cast(cache().get(n*sizeof(T))); - if(ret == pointer{nullptr}) { - ret = static_cast(cuda::cached::malloc(n*sizeof(T))); - if(!ret) throw bad_alloc{}; - // ++allocator::n_allocations; allocator::bytes_allocated+=sizeof(T)*n; - } - if(PrefetchDevice::value != -99) { - auto const code = cudaMemPrefetchAsync(raw_pointer_cast(ret), n*sizeof(T), PrefetchDevice::value); - if(code != cudaSuccess) { - throw std::runtime_error{"cannot prefetch for reason "+std::to_string(code)+" device is "+std::to_string(PrefetchDevice::value)}; - } - } - return ret; - } - - pointer allocate(typename allocator::size_type n, const_void_pointer hint){ - auto const ret = allocate(n); - if(not hint) { - if(cudaMemPrefetchAsync(raw_pointer_cast(ret), n*sizeof(T), /*device*/ 0) != cudaSuccess) {throw std::runtime_error{"cannot prefetch"};} - return ret; - } - - cudaPointerAttributes attr; if(cudaPointerGetAttributes(&attr, raw_pointer_cast(hint))!=cudaSuccess) {throw std::runtime_error{"cannot use attributes for hint"};} - switch(attr.type) { - case cudaMemoryTypeUnregistered: { // std::cout<< n <<" cudaMemoryTypeUnregistered"<< attr.device <<" "<< attr.device <<" cpuid:"<< cudaCpuDeviceId <>(p))) { - cuda::cached::free(static_cast>(p)); - } - } - - template - void construct(P p, Args&&... args) { - ::new(p.rp_) T(std::forward(args)...); - } - - template - void construct(P* p, Args&&... args) { - ::new(p) T(std::forward(args)...); - } - - template void destroy(P p) {p.rp_->~T();} - template void destroy(P* p) {p->~T();} - - constexpr bool operator==(allocator const&) const {return true;} - constexpr bool operator!=(allocator const&) const {return false;} - - template - constexpr ForwardIt alloc_uninitialized_copy(InputIt first, InputIt last, ForwardIt d_first) const { - return ForwardIt{adl_uninitialized_copy(first, last, d_first)}; - } - template - constexpr ForwardIt alloc_uninitialized_copy_n(InputIt first, Size count, ForwardIt d_first) const{ - return ForwardIt{adl_uninitialized_copy_n(first, count, d_first)}; - } - template - constexpr ForwardIt alloc_uninitialized_default_construct_n(ForwardIt first, Size n) const{ - return ForwardIt{adl_uninitialized_default_construct_n(first, n)}; - } - template - constexpr ForwardIt alloc_destroy_n(ForwardIt first, Size n) const{return ForwardIt{destroy_n(first, n)};} -}; - -} - -} // end namespace boost::multi::memory::cuda -#endif diff --git a/include/multi/memory/adaptors/cuda_/cached_/clib.hpp b/include/multi/memory/adaptors/cuda_/cached_/clib.hpp deleted file mode 100644 index b7182fe37..000000000 --- a/include/multi/memory/adaptors/cuda_/cached_/clib.hpp +++ /dev/null @@ -1,45 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_ADAPTOR_CUDA_CACHED_CLIB_HPP_ -#define MULTI_MEMORY_ADAPTOR_CUDA_CACHED_CLIB_HPP_ - -#include // cudaMallocCached - -#include "../../../adaptors/cuda/clib.hpp" // Cuda::free -#include "../../../adaptors/cuda/error.hpp" - -namespace Cuda { - namespace Cached { - inline error Malloc(void** p, size_t bytes){return static_cast(cudaMallocManaged(p, bytes/*, cudaMemAttachGlobal*/));} - inline void* malloc(size_t bytes){ - void* ret; - switch(auto e = Malloc(&ret, bytes)){ - case success : return ret; - case memory_allocation : return nullptr; - default : - throw std::system_error{e, "cannot allocate "+std::to_string(bytes)+" bytes in '"+__PRETTY_FUNCTION__+"'"}; - } - } - inline void free(void* p){return Cuda::free(p);} - } -} - - -//#ifdef _TEST_MULTI_MEMORY_ADAPTOR_CUDA_CACHED_MALLOC - -//#include "../../cuda/cached/ptr.hpp" - -//#include - -//namespace multi = boost::multi; -//namespace cuda = multi::memory::cuda; - -//using std::cout; - -//int main(){ -// void* p = Cuda::Cached::malloc(100); -// Cuda::Cached::free(p); -//} -//#endif -#endif // MULTI_MEMORY_ADAPTOR_CUDA_CACHED_CLIB_HPP_ diff --git a/include/multi/memory/adaptors/cuda_/cached_/malloc.hpp b/include/multi/memory/adaptors/cuda_/cached_/malloc.hpp deleted file mode 100644 index 29edce995..000000000 --- a/include/multi/memory/adaptors/cuda_/cached_/malloc.hpp +++ /dev/null @@ -1,33 +0,0 @@ -//#ifdef COMPILATION_INSTRUCTIONS -//(echo '#include"'$0'" '>$0.cpp)&& `#nvcc -ccbin=cuda-`c++ -D_TEST_MULTI_MEMORY_ADAPTORS_CUDA_CACHED_MALLOC $0.cpp -o $0x -lcudart &&$0x&&rm $0x; exit -//#endif - -#ifndef MULTI_MEMORY_ADAPTORS_CUDA_CACHED_MALLOC_HPP -#define MULTI_MEMORY_ADAPTORS_CUDA_CACHED_MALLOC_HPP - -#include "../../../adaptors/cuda/cached/clib.hpp" -#include "../../../adaptors/cuda/cached/ptr.hpp" - -namespace boost {namespace multi { -namespace memory { - -namespace cuda { - -namespace cached { - [[nodiscard]] - inline cached::ptr malloc(size_t bytes) { - MULTI_MARK_SCOPE("cuda::cached::malloc"); - return cached::ptr{Cuda::Cached::malloc(bytes)}; - } - inline void free(cached::ptr p) { - MULTI_MARK_SCOPE("cuda::cached::free"); - Cuda::Cached::free(static_cast(p)); - } -} - -} - -} -}} - -#endif diff --git a/include/multi/memory/adaptors/cuda_/cached_/ptr.hpp b/include/multi/memory/adaptors/cuda_/cached_/ptr.hpp deleted file mode 100644 index 8581cd39f..000000000 --- a/include/multi/memory/adaptors/cuda_/cached_/ptr.hpp +++ /dev/null @@ -1,364 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef BOOST_MULTI_MEMORY_ADAPTORS_CUDA_CACHED_PTR_HPP -#define BOOST_MULTI_MEMORY_ADAPTORS_CUDA_CACHED_PTR_HPP - -#include // nullptr_t -#include // random_access_iterator_tag - -#include // is_const - -#include "../../cuda/ptr.hpp" - -#include "../../../../detail/memory.hpp" - -#include // cudaDeviceSynchronize - -#ifndef _DISABLE_CUDA_SLOW -#ifdef NDEBUG -#define SLOW deprecated("because it implies a slow element access to GPU memory") -#else -#define SLOW -#endif -#else -#define SLOW -#endif - -#ifndef HD -#ifdef __CUDA_ARCH__ -#define HD __host__ __device__ -#else -#define HD -#endif -#endif - -namespace boost{ -namespace serialization{ - template class array_wrapper; - template const array_wrapper make_array(T* t, S s); -}} - -namespace boost{namespace multi{ -namespace memory{namespace cuda{ - -namespace cached{ - -template struct ptr; - -template -struct [[deprecated]] ptr : cuda::ptr { - using T = void const; - using raw_pointer = RawPtr; -// raw_pointer rp_; - template friend struct ptr; - template friend ptr const_pointer_cast(ptr const&); - explicit ptr(raw_pointer rp) : cuda::ptr{rp} {} - - public: - ptr() = default; - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialized from nullptr - ptr(std::nullptr_t n) : cuda::ptr{n} {} - - template().rp_})> - // cppcheck-suppress noExplicitConstructor ; any pointer is convertible to void pointer - ptr(Other const& o) : cuda::ptr{o.rp_} {} - - ptr& operator=(ptr const&) = default; - - using pointer = ptr; - using element_type = typename std::pointer_traits::element_type; - using difference_type = void;//typename std::pointer_traits::difference_type; -// explicit operator bool() const{return rp_;} -// explicit operator raw_pointer&()&{return rp_;} - friend constexpr bool operator==(ptr const& self, ptr const& other) {return self.rp_ == other.rp_;} - friend constexpr bool operator!=(ptr const& self, ptr const& other) {return self.rp_ != other.rp_;} - - void operator*() const = delete; - template using rebind = ptr::template rebind>; -// friend raw_pointer raw_pointer_cast(ptr const& self) {return self.rp_;} -}; - -template -struct ptr : cuda::ptr { - using pointer = ptr; - using element_type = void; - using difference_type = typename std::pointer_traits::difference_type; - - protected: - using raw_pointer = RawPtr; -// raw_pointer rp_; - - private: - ptr(ptr const& p) : cuda::ptr{const_cast(p.rp_)} {} - template friend ptr const_pointer_cast(ptr const&); - template friend struct ptr; - template friend class allocator; - - public: - template ptr(ptr const& p) : cuda::ptr{p.rp_} {} - explicit ptr(raw_pointer rp) : cuda::ptr{rp} {} - ptr() = default; - ptr(ptr const& p) = default; - - // cppcheck-suppress noExplicitConstructor ; initialized from nullptr - ptr(std::nullptr_t n) : cuda::ptr{n} {} - - template().impl_})> - // cppcheck-suppress noExplicitConstructor ; any pointer is convertible to void pointer - ptr(Other const& o) : cuda::ptr{o.rp_}{} - - ptr& operator=(ptr const&) = default; - - friend constexpr bool operator==(ptr const& self, ptr const& other){return self.rp_==other.rp_;} - friend constexpr bool operator!=(ptr const& self, ptr const& other){return self.rp_!=other.rp_;} - - template using rebind = ptr::template rebind>; - -// explicit operator bool() const {return this->rp_;} - explicit operator raw_pointer&()& {return this->rp_;} - - void operator*() = delete; - friend raw_pointer raw_pointer_cast(ptr const& self){return self.rp_;} -}; - -template > class allocator; - -template -struct ptr : cuda::ptr { - using raw_pointer = RawPtr; -// raw_pointer rp_; - - protected: - friend struct cuda::ptr; // to allow automatic conversions - template friend class allocator; - template friend struct ptr; -// template{}>::type> -// ptr(ptr const& p) : rp_{const_cast(p.impl_)}{} - template friend ptr const_pointer_cast(ptr const&); - - public: - template using rebind = ptr::template rebind>; -// explicit ptr(cuda::ptr const& other) : rp_{other.rp_}{} - - template>().rp_)>, raw_pointer>{}>> - // cppcheck-suppress noExplicitConstructor ; propagate implicit of underlying pointer - constexpr /*explicit(false)*/ ptr(ptr const& o) : cuda::ptr{static_cast(o.rp_)} {} - - template>().rp_)>, raw_pointer>{}>, typename = decltype(static_cast(std::declval>().rp_))> - constexpr explicit/*(true)*/ ptr(ptr const& o, void** = 0) : cuda::ptr{static_cast(o.rp_)} {} - - constexpr explicit ptr(void* vp) : cuda::ptr{static_cast(vp)} {} -// template>().rp_)>, raw_pointer>{}>> -// ptr(ptr const& o) HD : rp_{static_cast(o.rp_)}{} -// template>().rp_)>, raw_pointer>{}>> -// explicit ptr(ptr const& o, void** = 0) HD : rp_{static_cast(o.rp_)}{} - explicit ptr(cuda::ptr const& other) : ptr{other.rp_} { - // assert(other.rp_!=nullptr or Cuda::pointer::type(other.rp_) == cudaMemoryTypeCached); - } - constexpr explicit ptr(raw_pointer p) : cuda::ptr{p} {} - ptr() = default; - - // cppcheck-suppress noExplicitConstructor ; bug in cppcheck 2.3 - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialize from nullptr - constexpr ptr(std::nullptr_t n) : cuda::ptr{n} {} - - ptr& operator=(ptr const&) = default; - friend constexpr bool operator==(ptr const& s, ptr const& o) {return s.rp_==o.rp_;} - friend constexpr bool operator!=(ptr const& s, ptr const& o) {return s.rp_!=o.rp_;} - - using element_type = typename std::pointer_traits::element_type; - using difference_type = typename std::pointer_traits::difference_type; - using value_type = T; - using pointer = ptr; - using iterator_category = typename std::iterator_traits::iterator_category; // using iterator_concept = typename std::iterator_traits::iterator_concept; - explicit constexpr operator bool() const {return this->rp_;} -// bool operator not() const{return !rp_;} - constexpr -#ifndef MULTI_ALLOW_IMPLICIT_CPU_CONVERSION - explicit -#endif - operator raw_pointer() const& {return this->rp_;} // do not =delete - constexpr operator ptr() const {return ptr{this->rp_};} -// template -// decltype(auto) operator->*(PM pm) const{return *ptr*pm)>, decltype(&(rp_->*pm))>{&(rp_->*pm)};} - explicit constexpr operator typename std::pointer_traits::template rebind() const{return typename std::pointer_traits::template rebind{this->rp_};} - explicit operator typename std::pointer_traits::template rebind() const{return typename std::pointer_traits::template rebind{this->rp_};} - - constexpr ptr& operator++() {++(this->rp_); return *this;} // remove - constexpr ptr& operator--() {--(this->rp_); return *this;} // remove - - ptr operator++(int) {auto tmp = *this; ++(*this); return tmp;} // remove - ptr operator--(int) {auto tmp = *this; --(*this); return tmp;} // remove - - constexpr ptr& operator+=(typename ptr::difference_type n) {(this->rp_)+=n; return *this;} // remove - constexpr ptr& operator-=(typename ptr::difference_type n) HD {(this->rp_)-=n; return *this;} // remove - - constexpr ptr operator+(typename ptr::difference_type n) const {return ptr{(this->rp_) + n};} // remove - constexpr ptr operator-(typename ptr::difference_type n) const {return (*this) + (-n);} // remove - - using reference = typename std::pointer_traits::element_type&;//ref; - constexpr reference operator*() const {return *(this->rp_);} - constexpr reference operator[](difference_type n) const {return *(this->rp_ +n);} - - constexpr typename ptr::difference_type operator-(ptr const& other) const {return (this->rp_)-other.rp_;} - constexpr raw_pointer raw_pointer_cast() const& {return this->rp_;} // remove - friend raw_pointer raw_pointer_cast(ptr const& self) {return self.rp_;} - friend cuda::ptr cuda_pointer_cast(ptr const& self) {return cuda::ptr{self.rp_};} -// constexpr operator cuda::ptr() const{return cuda::ptr{this->rp_};} - friend constexpr allocator> get_allocator(ptr const&) {return {};} // do not =delete - using default_allocator_type = allocator>; - default_allocator_type default_allocator() const {return {};} - - template//, std::enable_if_t{}, int> =0> - static auto copy_n( - cached::ptr first, Size count, - cached::ptr result - ) { - return adl_copy_n(cuda::ptr(first), count, cuda::ptr(result)), result + count; - } -public: - friend allocator> default_allocator_of(ptr const&){return {};} - - template //, typename FromElement> - friend constexpr ToPointer - reinterpret_pointer_cast(ptr self) { - using to_element = typename std::pointer_traits::element_type; - return ToPointer(reinterpret_cast(self.raw_pointer_cast())); - } -}; - -template const boost::serialization::array_wrapper make_array(ptr t, S s) { - using boost::serialization::make_array; - return make_array(raw_pointer_cast(t), s); -} - -} - -}} -}} - -#undef SLOW - -#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ - -#include "../../cuda/cached/clib.hpp" // cuda::malloc -#include "../../cuda/cached/malloc.hpp" - -#include -#include -#include - -namespace multi = boost::multi; -namespace cuda = multi::memory::cuda; - -void add_one(double& d){d += 1.;} -template -void add_one(T&& t){std::forward(t) += 1.;} - -// * Functions with a __global__ qualifier, which run on the device but are called by the host, cannot use pass by reference. -//__global__ void set_5(cuda::ptr const& p){ -//__global__ void set_5(cuda::ptr p){*p = 5.;} -//__global__ void check_5(cuda::ptr p){assert(*p == 5.);} - -double const* g(){double* p{nullptr}; return p;} - -cuda::cached::ptr f(){ - return cuda::cached::ptr{nullptr}; -} - -cuda::cached::ptr ff(){ - return cuda::cached::ptr{cuda::ptr{nullptr}}; -} - -std::string full_overload(double*){return "cpu";} -std::string full_overload(cuda::ptr){return "gpu";} -std::string full_overload(cuda::cached::ptr){return "mng";} - -std::string cpugpu_overload(double*){return "cpu";} -std::string cpugpu_overload(cuda::ptr){return "gpu";} - -std::string cpuonly_overload(double*){return "cpu";} - -std::string gpuonly_overload(cuda::ptr){return "gpu";} - -template void what(T&&) = delete; - -int main(){ - - - f(); - using T = double; static_assert( sizeof(cuda::cached::ptr) == sizeof(T*) , "!"); - std::size_t const n = 100; - { - auto p = static_cast>(cuda::cached::malloc(n*sizeof(T))); - // cuda::cached::ptr vp = p; - // T* rp = p; - // void* vrp = p; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - *p = 99.; - if(*p != 99.) assert(0); - if(*p == 11.) assert(0); -#pragma GCC diagnostic pop - cuda::cached::free(p); - } - { - double d = 1.; - assert( full_overload(&d) == "cpu" ); - assert( cpugpu_overload(&d) == "cpu" ); - assert( cpugpu_overload(&d) == "cpu" ); - - cuda::ptr p = nullptr; - assert( full_overload(p) == "gpu" ); - assert( cpugpu_overload(p) == "gpu" ); - assert( gpuonly_overload(p) == "gpu" ); - - cuda::cached::ptr pm = nullptr; - assert( full_overload(pm) == "mng" ); - assert( cpugpu_overload(pm) == "gpu" ); - assert( cpuonly_overload(pm) == "cpu" ); - assert( gpuonly_overload(pm) == "gpu" ); - } - { - auto p = static_cast>(cuda::cached::malloc(n*sizeof(T))); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - double* ppp = p; *ppp = 3.14; - assert( *p == 3.14 ); -#pragma GCC diagnostic pop - // cuda::cached::ptr P = nullptr; - } - { - cuda::cached::ptr p = nullptr; - cuda::cached::ptr pc = nullptr; - assert( p == pc ); - pc = static_cast>(p); - // double* dp = cuda::cached::ptr{nullptr}; - auto f = [](double const*){}; - f(p); - // cuda::ptr pp = p; - // std::reinterpret_pointer_cast(pp); - // cuda::cached::ptr ppp{pp}; - } - { - static_assert(std::is_convertible, double*>{}); - } - { - auto p = static_cast>(cuda::cached::malloc(n*sizeof(T))); - cuda::ptr cp = p; - cuda::cached::ptr mcp{cp}; - } - { - static_assert(std::is_same>::rebind, cuda::cached::ptr>{}, "!"); - } - std::cout << "Finish" << std::endl; -} -#endif -#endif - - diff --git a/include/multi/memory/adaptors/cuda_/clib.hpp b/include/multi/memory/adaptors/cuda_/clib.hpp deleted file mode 100644 index 3eb555936..000000000 --- a/include/multi/memory/adaptors/cuda_/clib.hpp +++ /dev/null @@ -1,85 +0,0 @@ -//#ifdef COMPILATION_INSTRUCTIONS -//(echo '#include "'$0'"'>$0.cpp)&&nvcc -x cu `#-Wall -Wextra` -D_TEST_MULTI_MEMORY_ADAPTOR_CUDA_CLIB $0.cpp -o $0x -lcudart&&$0x&&rm $0x $0.cpp;exit -//#endif -// © Alfredo A. Correa 2019-2020 - -#ifndef MULTI_MEMORY_ADAPTOR_CUDA_CLIB_HPP -#define MULTI_MEMORY_ADAPTOR_CUDA_CLIB_HPP - -#include // cudaMalloc - -#include "../../adaptors/cuda/error.hpp" -#include "../../../config/NODISCARD.hpp" - -namespace Cuda { - using namespace std::string_literals; - - using size_t = ::size_t; - inline error Malloc(void** p, size_t bytes) {return static_cast(cudaMalloc(p, bytes));} - [[nodiscard]] // because it will produce a memory leak - inline void* malloc(size_t bytes) { - void* ret; - switch(auto e = Malloc(&ret, bytes)){ - case success : return ret; - case memory_allocation : return nullptr; - default : - throw std::system_error{e, "cannot allocate "+std::to_string(bytes)+" bytes in '"+__PRETTY_FUNCTION__+"'"}; - } - } - inline error Free(void* p){return static_cast(cudaFree(p));} - inline void free(void* p){ - auto e = Free(p); - // probably will terminate if called from noexcept functon - if(Cuda::success!=e) throw std::system_error{e, "cannot "s +__PRETTY_FUNCTION__}; - } - - namespace pointer { - using attributes_t = cudaPointerAttributes; - inline error GetAttributes(attributes_t* ret, void* p){return static_cast(cudaPointerGetAttributes(ret, p));} - /* attributes_t attributes(void* p){ - attributes_t ret; - auto e = GetAttributes(&ret, p); - if(e!=success) throw std::system_error{e, "cannot "s+__PRETTY_FUNCTION__}; - return ret; - }*/ - inline bool is_device(void* p) { - attributes_t ret; - auto e = GetAttributes(&ret, p); - if(e!=success) throw std::system_error{e, "cannot "s+__PRETTY_FUNCTION__}; - return ret.devicePointer or p==nullptr; - // return attributes(p).devicePointer or p==nullptr; - } - inline auto type(void* p) { - attributes_t ret; - auto e = GetAttributes(&ret, p); - if(e!=success) throw std::system_error{e, "cannot "s+__PRETTY_FUNCTION__}; - return ret.type; - } - } -} - -//#ifdef _TEST_MULTI_MEMORY_ADAPTOR_CUDA_CLIB - -//#include "../cuda/ptr.hpp" -//#include "../cuda/cstring.hpp" - -//#include - -//namespace multi = boost::multi; -//namespace cuda = multi::memory::cuda; - -//using std::cout; - -//int main(){ -// { -// void* p = Cuda::malloc(100); -// Cuda::free(p); -// } -// { -// char* p = (char*)Cuda::malloc(1ul<<40); -// assert(!p); -// Cuda::free(p); -// } -//} -//#endif -#endif diff --git a/include/multi/memory/adaptors/cuda_/cstring.hpp b/include/multi/memory/adaptors/cuda_/cstring.hpp deleted file mode 100644 index 42e97dc06..000000000 --- a/include/multi/memory/adaptors/cuda_/cstring.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_ADAPTORS_CUDA_CSTRING_HPP_ -#define MULTI_MEMORY_ADAPTORS_CUDA_CSTRING_HPP_ - -#include "../../adaptors/cuda/ptr.hpp" -#include "../../adaptors/cuda/managed/ptr.hpp" - -#include // cudaMemcpy/cudaMemset - -#include - -namespace boost{ -namespace multi{ -namespace memory{ -namespace cuda{ -#if (__cpp_nontype_template_parameter_auto >= 201606) or defined(__NVCC__) -template // requires c++17 -void call(Args&&... args){ - auto s = static_cast(CudaFunction(args...)); - if( s != Cuda::error::success ) throw std::system_error{make_error_code(s), "cannot call cuda function "}; -} -#endif - -template -auto call_static(std::string const& name){ - return [=](auto... args)->decltype(CublasFunction(args...), void()){ - std::cerr<< "Calling function " << name <(#FunctionPostfix) - -namespace memcpy_{ -// https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__TYPES.html#group__CUDART__TYPES_1g18fa99055ee694244a270e4d5101e95b - enum class kind : std::underlying_type_t{ - host_to_host=cudaMemcpyHostToHost, host_to_device=cudaMemcpyHostToDevice, - device_to_host=cudaMemcpyDeviceToHost, device_to_device=cudaMemcpyDeviceToDevice, - inferred = cudaMemcpyDefault, default_ = cudaMemcpyDefault - }; - template constexpr kind type(T1* , T2* ){return kind::host_to_host ;} - template constexpr kind type(ptr, T2* ){return kind::host_to_device ;} - template constexpr kind type(T1* , ptr){return kind::device_to_host ;} - template constexpr kind type(ptr, ptr){return kind::device_to_device;} - template constexpr kind type(managed::ptr, P2){return kind::inferred;} - template constexpr kind type(P1, managed::ptr){return kind::inferred;} - template constexpr kind type(managed::ptr, managed::ptr){return kind::inferred;} - [[deprecated]] constexpr kind type(...) {return kind::inferred; } -} // namespace memcpy_ - -template -Dest memcpy(Dest dest, Src src, std::size_t byte_count){ - cuda::call(static_cast(dest), static_cast(src), byte_count, static_cast(memcpy_::type(dest, src))); - return dest; -} - -inline ptr memset(ptr dest, int ch, std::size_t byte_count){ - cuda::call(static_cast(dest), ch, byte_count); - return dest; -} - -template -auto memcpy2D(VoidPDst dst, std::size_t dpitch, VoidPCSrc src, std::size_t spitch, std::size_t width, std::size_t height) -->decltype(cuda::call(static_cast(dst), dpitch, static_cast(src), spitch, width, height, static_cast(memcpy_::type(dst, src)))){ - return cuda::call(static_cast(dst), dpitch, static_cast(src), spitch, width, height, static_cast(memcpy_::type(dst, src)));} - -} // namespace cuda -} // namespace memory -} // namespace multi -} // namespace boost - -#endif // MULTI_MEMORY_ADAPTORS_CUDA_CSTRING_HPP_ diff --git a/include/multi/memory/adaptors/cuda_/error.hpp b/include/multi/memory/adaptors/cuda_/error.hpp deleted file mode 100644 index fc17e45ab..000000000 --- a/include/multi/memory/adaptors/cuda_/error.hpp +++ /dev/null @@ -1,96 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_ADAPTOR_CUDA_DETAIL_ERROR_HPP_ -#define MULTI_MEMORY_ADAPTOR_CUDA_DETAIL_ERROR_HPP_ - -#include // cudaError_t -#include // cudaGetErrorString - -#include -#include // underlying_type - -namespace Cuda{ - -enum /*class*/ error : std::underlying_type::type{ - success = cudaSuccess, // = 0 The API call returned with no errors. In the case of query calls, this also means that the operation being queried is complete (see cudaEventQuery() and cudaStreamQuery()). - missing_configuration = cudaErrorMissingConfiguration, - // invalid_value /*invalid_argument*/ = cudaErrorInvalidValue, // = 1, This indicates that one or more of the parameters passed to the API call is not within an acceptable range of values. - memory_allocation = cudaErrorMemoryAllocation, // = 2 // The API call failed because it was unable to allocate enough memory to perform the requested operation. - initialization_error = cudaErrorInitializationError, - lauch_failure = cudaErrorLaunchFailure, - lauch_timeout = cudaErrorLaunchTimeout, - lauch_out_of_resources = cudaErrorLaunchOutOfResources, - invalid_device_function = cudaErrorInvalidDeviceFunction, - invalid_configuration = cudaErrorInvalidConfiguration, - invalid_device = cudaErrorInvalidDevice, - invalid_value = cudaErrorInvalidValue, ///*invalid_argument*/ = cudaErrorInvalidValue, // = 1 This indicates that one or more of the parameters passed to the API call is not within an acceptable range of values. - invalid_pitch_value = cudaErrorInvalidPitchValue, - invalid_symbol = cudaErrorInvalidSymbol, - unmap_buffer_object_failed = cudaErrorUnmapBufferObjectFailed, - invalid_device_pointer = cudaErrorInvalidDevicePointer, - invalid_texture = cudaErrorInvalidTexture, - invalid_texture_binding = cudaErrorInvalidTextureBinding, - invalid_channel_descriptor = cudaErrorInvalidChannelDescriptor, - invalid_memcpy_direction = cudaErrorInvalidMemcpyDirection, - invalud_filter_setting = cudaErrorInvalidFilterSetting, - invalid_norm_setting = cudaErrorInvalidNormSetting, - unknown = cudaErrorUnknown, - invalid_resource_handle = cudaErrorInvalidResourceHandle, - insuffient_driver = cudaErrorInsufficientDriver, - no_device = cudaErrorNoDevice, - set_on_active_process = cudaErrorSetOnActiveProcess, - startup_failure = cudaErrorStartupFailure, - invalid_ptx = cudaErrorInvalidPtx, - no_kernel_image_for_device = cudaErrorNoKernelImageForDevice, - jit_compiler_not_found = cudaErrorJitCompilerNotFound -}; - -inline std::string string(enum error e){return cudaGetErrorString(static_cast(e));} - -struct error_category : std::error_category { - char const* name() const noexcept override { return "cuda wrapper"; } - std::string message(int e) const override {return string(static_cast(e));} - static error_category& instance(){ - static error_category instance; - return instance; - } -}; - -inline std::error_code make_error_code(error err) noexcept { - return {int(err), error_category::instance()}; -} - -} - -namespace std{template<> struct is_error_code_enum : true_type {};} - -#if not __INCLUDE_LEVEL__ - -#include - -using std::cout; - -int main(){ - - { - std::error_code ec = Cuda::error::memory_allocation; (void)ec; - } - try { - auto e = Cuda::error::memory_allocation; // return from a cudaFunction - throw std::system_error{e, "I cannot do allocation"}; - } catch(std::system_error const& e) { - cout - <<"catched...\n" - <<"code: " << e.code() <<'\n' - <<"message: "<< e.code().message() <<'\n' - <<"what: " << e.what() <<'\n' - ; - } - -// auto e = Cuda::error::memory_allocation; // return from a cudaFunction -// throw std::system_error{e, "because"}; - -} -#endif -#endif // MULTI_MEMORY_ADAPTOR_CUDA_DETAIL_ERROR_HPP_ diff --git a/include/multi/memory/adaptors/cuda_/malloc.hpp b/include/multi/memory/adaptors/cuda_/malloc.hpp deleted file mode 100644 index 6b10880be..000000000 --- a/include/multi/memory/adaptors/cuda_/malloc.hpp +++ /dev/null @@ -1,42 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_ADAPTORS_CUDA_MALLOC_ -#define MULTI_MEMORY_ADAPTORS_CUDA_MALLOC_ - -#include "../../adaptors/cuda/clib.hpp" -#include "../../adaptors/cuda/ptr.hpp" - -namespace boost {namespace multi { -namespace memory { - -namespace cuda{ - using size_t = Cuda::size_t; -#if __cplusplus >= 201703L -#if __has_cpp_attribute(nodiscard) >= 201603L - [[nodiscard]] -#endif -#endif - inline auto malloc(size_t bytes) -> ptr{return ptr{Cuda::malloc(bytes)};} - inline void free(ptr p){Cuda::free(p);} -} - -} // namespace memory -} // namespace multi -} // namespace boost - -#ifdef _TEST_MULTI_MEMORY_ADAPTORS_CUDA_MALLOC - -namespace multi = boost::multi; -namespace cuda = multi::memory::cuda; - -int main(){ - using cuda::ptr; - ptr p = static_cast>(cuda::malloc(100*sizeof(double))); - p[10] = 99.; - cuda::free(p); -} - -#endif -#endif // MULTI_MEMORY_ADAPTORS_CUDA_MALLOC_ - diff --git a/include/multi/memory/adaptors/cuda_/managed/allocator.hpp b/include/multi/memory/adaptors/cuda_/managed/allocator.hpp deleted file mode 100644 index e18d2ab49..000000000 --- a/include/multi/memory/adaptors/cuda_/managed/allocator.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -$CXXX $CXXFLAGS $0 -o $0x -lcudart&&$0x&&rm $0x;exit -#endif - -#ifndef MULTI_MEMORY_ADAPTORS_CUDA_MANAGED_ALLOCATOR_HPP -#define MULTI_MEMORY_ADAPTORS_CUDA_MANAGED_ALLOCATOR_HPP - -#include "../../../adaptors/cuda/allocator.hpp" -#include "../../../adaptors/cuda/managed/ptr.hpp" - -#include "../../../adaptors/cuda/managed/clib.hpp" // cuda::malloc -#include "../../../adaptors/cuda/managed/malloc.hpp" - -#include -#include -#include // debug -#include -#include // bad_alloc - -namespace boost{namespace multi{ -namespace memory{namespace cuda{ - -namespace managed{ - struct bad_alloc : std::bad_alloc{}; - - template - class allocator : cuda::allocator{ - static_assert( std::is_same>{}, "!" ); - public: - using value_type = T; - using pointer = managed::ptr; - using size_type = ::size_t; // as specified by CudaMalloc - using const_void_pointer = managed::ptr; - template using rebind = managed::allocator; - pointer allocate(typename allocator::size_type n){ - if(n == 0) return pointer{nullptr}; - auto ret = static_cast(cuda::managed::malloc(n*sizeof(T))); - if(!ret) throw bad_alloc{}; - if(PrefetchDevice::value != -99) - if(cudaMemPrefetchAsync(raw_pointer_cast(ret), n*sizeof(T), PrefetchDevice::value) != cudaSuccess) throw std::runtime_error{"cannot prefetch for some reason"}; - // ++allocator::n_allocations; allocator::bytes_allocated+=sizeof(T)*n; - return ret; - } - pointer allocate(typename allocator::size_type n, const_void_pointer hint){ - MULTI_MARK_SCOPE("cuda::managed::allocate"); - - auto const ret = allocate(n); - if(not hint){ - if(cudaMemPrefetchAsync(raw_pointer_cast(ret), n*sizeof(T), /*device*/ 0) != cudaSuccess) throw std::runtime_error{"cannot prefetch"}; - return ret; - } - cudaPointerAttributes attr; if(cudaPointerGetAttributes(&attr, raw_pointer_cast(hint))!=cudaSuccess) throw std::runtime_error{"cannot use attributes for hint"}; - switch(attr.type){ - case cudaMemoryTypeUnregistered:{//std::cout<< n <<" cudaMemoryTypeUnregistered"<< attr.device <<" "<< attr.device <<" cpuid:"<< cudaCpuDeviceId <>(p)); - } - template - void construct(P p, Args&&... args){ - ::new(p.rp_) T(std::forward(args)...); - } - template - void construct(P* p, Args&&... args){ - ::new(p) T(std::forward(args)...); - } - template void destroy(P p){p.rp_->~T();} - template void destroy(P* p){p->~T();} - constexpr bool operator==(allocator const&) const{return true;} - constexpr bool operator!=(allocator const&) const{return false;} - template - constexpr ForwardIt alloc_uninitialized_copy(InputIt first, InputIt last, ForwardIt d_first) const{ - return ForwardIt{adl_uninitialized_copy(first, last, d_first)}; - } - template - constexpr ForwardIt alloc_uninitialized_copy_n(InputIt first, Size count, ForwardIt d_first) const{ - return ForwardIt{adl_uninitialized_copy_n(first, count, d_first)}; - } - template - constexpr ForwardIt alloc_uninitialized_default_construct_n(ForwardIt first, Size n) const{ - return ForwardIt{adl_uninitialized_default_construct_n(first, n)}; - } - template - constexpr ForwardIt alloc_destroy_n(ForwardIt first, Size n) const{return ForwardIt{destroy_n(first, n)};} - }; -} - -}}}} - -#if not __INCLUDE_LEVEL__ - -#include -#include -#include "../../../../array.hpp" - -namespace multi = boost::multi; -namespace cuda = multi::memory::cuda; - -int main(){ - - multi::array > A(32); - A[17] = 3.; - assert( A[17] == 3. ); - -} -#endif -#endif - diff --git a/include/multi/memory/adaptors/cuda_/managed/clib.hpp b/include/multi/memory/adaptors/cuda_/managed/clib.hpp deleted file mode 100644 index cf8249a89..000000000 --- a/include/multi/memory/adaptors/cuda_/managed/clib.hpp +++ /dev/null @@ -1,44 +0,0 @@ -//#ifdef COMPILATION_INSTRUCTIONS -//(echo '#include "'$0'"'>$0.cpp)&&c++ -std=c++11 -Wall -Wextra -Wpedantic -Wfatal-errors -D_TEST_MULTI_MEMORY_ADAPTOR_CUDA_MANAGED_MALLOC $0.cpp -lcudart -o $0x &&$0x&& rm $0x $0.cpp; exit -//#endif -#ifndef MULTI_MEMORY_ADAPTOR_CUDA_MANAGED_CLIB_HPP -#define MULTI_MEMORY_ADAPTOR_CUDA_MANAGED_CLIB_HPP - -#include // cudaMallocManaged - -#include "../../../adaptors/cuda/clib.hpp" // Cuda::free -#include "../../../adaptors/cuda/error.hpp" - -namespace Cuda { - namespace Managed { - inline error Malloc(void** p, size_t bytes) {return static_cast(cudaMallocManaged(p, bytes/*, cudaMemAttachGlobal*/));} - inline void* malloc(size_t bytes) { - void* ret; - switch(auto e = Malloc(&ret, bytes)) { - case success : return ret; - case memory_allocation : return nullptr; - default : - throw std::system_error{e, "cannot allocate "+std::to_string(bytes)+" bytes in '"+__PRETTY_FUNCTION__+"'"}; - } - } - inline void free(void* p) {return Cuda::free(p);} - } -} - -//#ifdef _TEST_MULTI_MEMORY_ADAPTOR_CUDA_MANAGED_MALLOC - -//#include "../../cuda/managed/ptr.hpp" - -//#include - -//namespace multi = boost::multi; -//namespace cuda = multi::memory::cuda; - -//using std::cout; - -//int main(){ -// void* p = Cuda::Managed::malloc(100); -// Cuda::Managed::free(p); -//} -//#endif -#endif diff --git a/include/multi/memory/adaptors/cuda_/managed/malloc.hpp b/include/multi/memory/adaptors/cuda_/managed/malloc.hpp deleted file mode 100644 index af973005c..000000000 --- a/include/multi/memory/adaptors/cuda_/managed/malloc.hpp +++ /dev/null @@ -1,30 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_ADAPTORS_CUDA_MANAGED_MALLOC_HPP -#define MULTI_MEMORY_ADAPTORS_CUDA_MANAGED_MALLOC_HPP - -#include "../../../adaptors/cuda/managed/clib.hpp" -#include "../../../adaptors/cuda/managed/ptr.hpp" - -namespace boost::multi { -namespace memory { - -namespace cuda { - -namespace managed { - [[nodiscard]] - inline managed::ptr malloc(size_t bytes) { - MULTI_MARK_SCOPE("cuda::managed::malloc"); - return managed::ptr{Cuda::Managed::malloc(bytes)}; - } - inline void free(managed::ptr p) { - MULTI_MARK_SCOPE("cuda::managed::free"); - Cuda::Managed::free(static_cast(p)); - } -} // end namespace managed -} // end namespace cuda -} // end namespace memory -} // end namespace boost::multi - -#endif diff --git a/include/multi/memory/adaptors/cuda_/managed/ptr.hpp b/include/multi/memory/adaptors/cuda_/managed/ptr.hpp deleted file mode 100644 index 99a505d5e..000000000 --- a/include/multi/memory/adaptors/cuda_/managed/ptr.hpp +++ /dev/null @@ -1,349 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef BOOST_MULTI_MEMORY_ADAPTORS_CUDA_MANAGED_PTR_HPP -#define BOOST_MULTI_MEMORY_ADAPTORS_CUDA_MANAGED_PTR_HPP - -#include // nullptr_t -#include // random_access_iterator_tag - -#include // is_const - -#include "../../cuda/ptr.hpp" - -#include "../../../../detail/memory.hpp" - -#include // cudaDeviceSynchronize - -#ifndef _DISABLE_CUDA_SLOW -#ifdef NDEBUG -#define SLOW deprecated("because it implies a slow element access to GPU memory") -#else -#define SLOW -#endif -#else -#define SLOW -#endif - -#ifndef HD -#ifdef __CUDA_ARCH__ -#define HD __host__ __device__ -#else -#define HD -#endif -#endif - -namespace boost { -namespace serialization { - template class array_wrapper; - template const array_wrapper make_array(T* t, S s); -}} - -namespace boost {namespace multi { -namespace memory {namespace cuda { - -namespace managed{ - -template struct ptr; - -template -struct ptr : cuda::ptr { - using T = void const; - using raw_pointer = RawPtr; -// raw_pointer rp_; - template friend struct ptr; - template friend ptr const_pointer_cast(ptr const&); - explicit ptr(raw_pointer rp) : cuda::ptr{rp} {} - - public: - ptr() = default; - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialized from nullptr - ptr(std::nullptr_t n) : cuda::ptr{n} {} - - template().rp_})> - // cppcheck-suppress noExplicitConstructor ; any pointer can be converted to void pointer - ptr(Other const& o) : cuda::ptr{o} {} - ptr& operator=(ptr const&) = default; - - using pointer = ptr; - using element_type = typename std::pointer_traits::element_type; - using difference_type = void; -// explicit operator bool() const{return rp_;} -// explicit operator raw_pointer&()&{return rp_;} - friend constexpr bool operator==(ptr const& self, ptr const& o) {return self.rp_ == o.rp_;} - friend constexpr bool operator!=(ptr const& self, ptr const& o) {return self.rp_ != o.rp_;} - - void operator*() const = delete; - template using rebind = ptr::template rebind>; -}; - -template -struct ptr : cuda::ptr { - using pointer = ptr; - using element_type = void; - using difference_type = typename std::pointer_traits::difference_type; - - protected: - using raw_pointer = RawPtr; -// raw_pointer rp_; - - private: - ptr(ptr const& p) : cuda::ptr{const_cast(p.rp_)} {} - template friend ptr const_pointer_cast(ptr const&); - template friend struct ptr; - template friend class allocator; - - public: - template ptr(ptr const& p) : cuda::ptr{p.rp_} {} - explicit ptr(raw_pointer rp) : cuda::ptr{rp} {} - ptr() = default; - ptr(ptr const& p) = default; - - // cppcheck-suppress noExplicitConstructor ; initialized from nullptr - ptr(std::nullptr_t n) : cuda::ptr{n} {} - - template().impl_})> - // cppcheck-suppress noExplicitConstructor ; any pointer is convertible to void pointer - ptr(Other const& o) : cuda::ptr{o.rp_}{} - - ptr& operator=(ptr const&) = default; - - friend constexpr bool operator==(ptr const& self, ptr const& other) {return self.rp_==other.rp_;} - friend constexpr bool operator!=(ptr const& self, ptr const& other) {return self.rp_!=other.rp_;} - - template using rebind = ptr::template rebind>; - - explicit operator raw_pointer&()& {return this->rp_;} - - void operator*() = delete; - friend raw_pointer raw_pointer_cast(ptr const& self) {return self.rp_;} -}; - -template > class allocator; - -template -struct ptr : cuda::ptr { - using raw_pointer = RawPtr; -// raw_pointer rp_; - - protected: - friend struct cuda::ptr; // to allow automatic conversions - template friend class allocator; - template friend struct ptr; -// template{}>::type> -// ptr(ptr const& p) : rp_{const_cast(p.impl_)}{} - template friend ptr const_pointer_cast(ptr const&); - - public: - template using rebind = ptr::template rebind>; -// explicit ptr(cuda::ptr const& other) : rp_{other.rp_}{} - - template>().rp_)>, raw_pointer>{}>> - // cppcheck-suppress noExplicitConstructor ; propagate implicit of underlying pointer - /*explicit(false)*/ constexpr ptr(ptr const& o) : cuda::ptr{static_cast(o.rp_)} {} - - template>().rp_)>, raw_pointer>{}>, typename = decltype(static_cast(std::declval>().rp_))> - explicit/*(true)*/ constexpr ptr(ptr const& o, void** = 0) : cuda::ptr{static_cast(o.rp_)} {} - - constexpr explicit ptr(void* vp) : cuda::ptr{static_cast(vp)}{} -// template>().rp_)>, raw_pointer>{}>> -// ptr(ptr const& o) HD : rp_{static_cast(o.rp_)}{} -// template>().rp_)>, raw_pointer>{}>> -// explicit ptr(ptr const& o, void** = 0) HD : rp_{static_cast(o.rp_)}{} - constexpr explicit ptr(cuda::ptr const& other) : ptr{other.rp_}{ - // assert(other.rp_!=nullptr or Cuda::pointer::type(other.rp_) == cudaMemoryTypeManaged); - } - constexpr explicit ptr(raw_pointer p) : cuda::ptr{p} {}//Cuda::pointer::is_device(p);} - ptr() = default; - - // cppcheck-suppress noExplicitConstructor ; bug in cppcheck 2.3 - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialize from nullptr - constexpr ptr(std::nullptr_t n) : cuda::ptr{n}{} - - ptr& operator=(ptr const&) = default; - friend constexpr bool operator==(ptr const& s, ptr const& o) {return s.rp_==o.rp_;} - friend constexpr bool operator!=(ptr const& s, ptr const& o) {return s.rp_!=o.rp_;} - - using element_type = typename std::pointer_traits::element_type; - using difference_type = typename std::pointer_traits::difference_type; - using value_type = T; - using pointer = ptr; - using iterator_category = typename std::iterator_traits::iterator_category; // using iterator_concept = typename std::iterator_traits::iterator_concept; - constexpr explicit operator bool() const {return this->rp_;} -// bool operator not() const{return !rp_;} - constexpr -#ifndef MULTI_ALLOW_IMPLICIT_CPU_CONVERSION - explicit -#endif - operator raw_pointer()const&{return this->rp_;} // do not =delete - constexpr operator ptr() const{return ptr{this->rp_};} -// template -// decltype(auto) operator->*(PM pm) const{return *ptr*pm)>, decltype(&(rp_->*pm))>{&(rp_->*pm)};} - explicit constexpr operator typename std::pointer_traits::template rebind() const {return typename std::pointer_traits::template rebind{this->rp_};} - explicit operator typename std::pointer_traits::template rebind() const {return typename std::pointer_traits::template rebind{this->rp_};} - constexpr ptr& operator++() {++(this->rp_); return *this;} // remove - constexpr ptr& operator--() {--(this->rp_); return *this;} // remove - ptr operator++(int) {auto tmp = *this; ++(*this); return tmp;} // remove - ptr operator--(int) {auto tmp = *this; --(*this); return tmp;} // remove - constexpr ptr& operator+=(typename ptr::difference_type n) {(this->rp_)+=n; return *this;} // remove - constexpr ptr& operator-=(typename ptr::difference_type n) HD {(this->rp_)-=n; return *this;} // remove - constexpr ptr operator+(typename ptr::difference_type n) const {return ptr{(this->rp_) + n};} // remove - constexpr ptr operator-(typename ptr::difference_type n) const {return (*this) + (-n);} // remove - using reference = typename std::pointer_traits::element_type&;//ref; - constexpr reference operator*() const {return *(this->rp_);} - constexpr reference operator[](difference_type n) const {return *((*this)+n);} - constexpr typename ptr::difference_type operator-(ptr const& other) const {return (this->rp_)-other.rp_;} - - constexpr raw_pointer raw_pointer_cast() const& {return this->rp_;} // remove - friend constexpr raw_pointer raw_pointer_cast(ptr const& self) {return self.rp_;} - - friend cuda::ptr cuda_pointer_cast(ptr const& self) {return cuda::ptr{self.rp_};} -// constexpr operator cuda::ptr() const{return cuda::ptr{this->rp_};} - friend constexpr allocator> get_allocator(ptr const&) {return {};} // do not =delete - using default_allocator_type = allocator>; - default_allocator_type default_allocator() const {return {};} - - template//, std::enable_if_t{}, int> =0> - static auto copy_n( - managed::ptr first, Size count, - managed::ptr result - ){ - return adl_copy_n(cuda::ptr(first), count, cuda::ptr(result)), result + count; - } -public: - friend allocator> default_allocator_of(ptr const&){return {};} -}; - -template const boost::serialization::array_wrapper make_array(ptr t, S s){ - using boost::serialization::make_array; - return make_array(raw_pointer_cast(t), s); -} - -} - -}} -}} - -#undef SLOW - -#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__ - -#include "../../cuda/managed/clib.hpp" // cuda::malloc -#include "../../cuda/managed/malloc.hpp" - -#include -#include -#include - -namespace multi = boost::multi; -namespace cuda = multi::memory::cuda; - -void add_one(double& d){d += 1.;} -template -void add_one(T&& t){std::forward(t) += 1.;} - -// * Functions with a __global__ qualifier, which run on the device but are called by the host, cannot use pass by reference. -//__global__ void set_5(cuda::ptr const& p){ -//__global__ void set_5(cuda::ptr p){*p = 5.;} -//__global__ void check_5(cuda::ptr p){assert(*p == 5.);} - -double const* g(){double* p{nullptr}; return p;} - -cuda::managed::ptr f(){ - return cuda::managed::ptr{nullptr}; -} - -cuda::managed::ptr ff(){ - return cuda::managed::ptr{cuda::ptr{nullptr}}; -} - -std::string full_overload(double*){return "cpu";} -std::string full_overload(cuda::ptr){return "gpu";} -std::string full_overload(cuda::managed::ptr){return "mng";} - -std::string cpugpu_overload(double*){return "cpu";} -std::string cpugpu_overload(cuda::ptr){return "gpu";} - -std::string cpuonly_overload(double*){return "cpu";} - -std::string gpuonly_overload(cuda::ptr){return "gpu";} - -template void what(T&&) = delete; - -int main(){ - - - f(); - using T = double; static_assert( sizeof(cuda::managed::ptr) == sizeof(T*) , "!"); - std::size_t const n = 100; - { - auto p = static_cast>(cuda::managed::malloc(n*sizeof(T))); - // cuda::managed::ptr vp = p; - // T* rp = p; - // void* vrp = p; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - *p = 99.; - if(*p != 99.) assert(0); - if(*p == 11.) assert(0); -#pragma GCC diagnostic pop - cuda::managed::free(p); - } - { - double d = 1.; - assert( full_overload(&d) == "cpu" ); - assert( cpugpu_overload(&d) == "cpu" ); - assert( cpugpu_overload(&d) == "cpu" ); - - cuda::ptr p = nullptr; - assert( full_overload(p) == "gpu" ); - assert( cpugpu_overload(p) == "gpu" ); - assert( gpuonly_overload(p) == "gpu" ); - - cuda::managed::ptr pm = nullptr; - assert( full_overload(pm) == "mng" ); - assert( cpugpu_overload(pm) == "gpu" ); - assert( cpuonly_overload(pm) == "cpu" ); - assert( gpuonly_overload(pm) == "gpu" ); - } - { - auto p = static_cast>(cuda::managed::malloc(n*sizeof(T))); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - double* ppp = p; *ppp = 3.14; - assert( *p == 3.14 ); -#pragma GCC diagnostic pop - // cuda::managed::ptr P = nullptr; - } - { - cuda::managed::ptr p = nullptr; - cuda::managed::ptr pc = nullptr; - assert( p == pc ); - pc = static_cast>(p); - // double* dp = cuda::managed::ptr{nullptr}; - auto f = [](double const*){}; - f(p); - // cuda::ptr pp = p; -// std::reinterpret_pointer_cast(pp); - // cuda::managed::ptr ppp{pp}; - } - { - static_assert(std::is_convertible, double*>{}); - } - { - auto p = static_cast>(cuda::managed::malloc(n*sizeof(T))); - cuda::ptr cp = p; - cuda::managed::ptr mcp{cp}; - } - { - static_assert(std::is_same>::rebind, cuda::managed::ptr>{}, "!"); - } - std::cout << "Finish" << std::endl; -} -#endif -#endif - diff --git a/include/multi/memory/adaptors/cuda_/ptr_.hpp b/include/multi/memory/adaptors/cuda_/ptr_.hpp deleted file mode 100644 index 816f285ab..000000000 --- a/include/multi/memory/adaptors/cuda_/ptr_.hpp +++ /dev/null @@ -1,950 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_ADAPTORS_CUDA_PTR_HPP_ -#define MULTI_MEMORY_ADAPTORS_CUDA_PTR_HPP_ - -#include "../../../array_ref.hpp" -#include "../../../complex.hpp" // for adl_conj -#include "../../adaptors/cuda/clib.hpp" -#include "../../adaptors/cuda/error.hpp" - -#include "../../../config/DEPRECATED.hpp" - -#include // for debug -#include // for allocator -#include // for exchange - -#include - -#ifndef _DISABLE_CUDA_SLOW -#ifdef NDEBUG -#define SLOW DEPRECATED("WARNING: slow memory operation") -#else -#define SLOW -#endif -#else -#define SLOW -#endif - -#define CUDA_SLOW(ExpR) NO_DEPRECATED(ExpR) - -namespace boost { -namespace multi { -namespace memory { -namespace cuda { - -template struct ref; - -template struct ptr; - -namespace managed { -template struct ptr; -} - -#if 0 -template -struct [[deprecated]] ptr { - using pointer = ptr; - using element_type = void const; - // using difference_type = void;//typename std::pointer_traits::difference_type; - - operator ::thrust::cuda::pointer() const { return ::thrust::cuda::pointer{rp_}; } - - protected: - using raw_pointer = RawPtr; - template friend struct managed::ptr; - raw_pointer rp_; - template friend struct ptr; - template friend ptr const_pointer_cast(ptr const&); - explicit ptr(raw_pointer rp) : rp_{rp} {} - - public: - ptr() = default; - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialize from nullptr - ptr(std::nullptr_t n) : rp_{n} {} // NOLINT(runtime/explicit) terse syntax - - template().rp_})> - // cppcheck-suppress noExplicitConstructor ; any other pointer can be converted to void const pointer - ptr(Other const& o) : rp_{o.rp_} {} // NOLINT(runtime/explicit) terse syntax - ptr& operator=(ptr const&) = default; - - explicit operator bool() const { return rp_; } - - friend constexpr bool operator==(ptr const& s, ptr const& o) { return s.rp_ == o.rp_; } - friend constexpr bool operator!=(ptr const& s, ptr const& o) { return s.rp_ != o.rp_; } - - friend constexpr raw_pointer to_address(ptr const& self) { return self.rp_; } - friend raw_pointer raw_pointer_cast(ptr const& self) { return self.rp_; } -}; - -template class allocator; - -template -struct [[deprecated]] ptr { - operator ::thrust::cuda_cub::pointer() const { return ::thrust::cuda_cub::pointer{rp_}; } - - protected: - using T = void; - using raw_pointer = RawPtr; - using raw_pointer_traits = std::pointer_traits; - static_assert(std::is_same{}, "!"); - raw_pointer rp_; - friend ptr malloc(size_t); - friend void free(); - friend ptr memset(ptr dest, int ch, std::size_t byte_count); - template friend struct managed::ptr; - - protected: - template friend ptr const_pointer_cast(ptr const&); - template friend struct ptr; - explicit ptr(raw_pointer rp) : rp_{rp} {} - operator raw_pointer() const { return rp_; } - friend ptr malloc(std::size_t); - friend void free(ptr); - - public: - ptr() = default; - ptr(ptr const& other) : rp_{other.rp_} {} //= default; - - // cppcheck-suppress noExplicitConstructor ; initialize from nullptr - ptr(std::nullptr_t n) : rp_{n} {} // NOLINT(runtime/explicit) terse syntax - - template().rp_})> - // cppcheck-suppress noExplicitConstructor ; any pointer can be converted to void pointer - ptr(Other const& o) : rp_{o.rp_} {} // NOLINT(runtime/explicit) terse syntax - - ptr& operator=(ptr const&) = default; - friend constexpr bool operator==(ptr const& s, ptr const& o) { return s.rp_ == o.rp_; } - friend constexpr bool operator!=(ptr const& s, ptr const& o) { return s.rp_ != o.rp_; } - using pointer = ptr; - using element_type = typename std::pointer_traits::element_type; - using difference_type = typename std::pointer_traits::difference_type; - template using rebind = ptr::template rebind>; - // using default_allocator_type = typename cuda::allocator::value_type>; - explicit operator bool() const { return rp_; } - // explicit operator raw_pointer&()&{return impl_;} - - friend constexpr raw_pointer to_address(ptr const& p) { return p.rp_; } -}; - -template -struct [[deprecated]] ptr { - operator ::thrust::cuda_cub::pointer() const { return ::thrust::cuda_cub::pointer{rp_}; } - - using raw_pointer = RawPtr; - using default_allocator_type = typename cuda::allocator>; - raw_pointer rp_ = {}; - - static_assert(not std::is_same{}, "!"); - - protected: - using raw_pointer_traits = typename std::pointer_traits; - template friend class allocator; - - template friend struct ptr; - template friend struct ref; - - template friend ptr const_pointer_cast(ptr const&); - friend struct managed::ptr; - - public: - template using rebind = ptr::template rebind>; - - template>().rp_)>, raw_pointer>{} and not std::is_same{}>> - // cppcheck-suppress noExplicitConstructor ; - HD constexpr /*explicit(false)*/ ptr(ptr const& o) : rp_{static_cast(o.rp_)} {} - template< - class Other, - typename = std::enable_if_t>().rp_)>, raw_pointer>{} and not std::is_same{}>, typename = decltype(static_cast(std::declval>().rp_)) - > - HD constexpr explicit /*(true)*/ ptr(ptr const& o, void** = 0) : rp_{static_cast(o.rp_)} {} - HD constexpr explicit ptr(raw_pointer rp) : rp_{rp} {} - - template friend auto reinterpret_pointer_cast(ptr p) // clang-format off - ->decltype(ptr{reinterpret_cast(std::declval())}) { - return ptr{reinterpret_cast(p.rp_ )}; } // clang-format om - - template(std::declval().rp_))> - HD constexpr explicit ptr(Other const& o) : rp_{static_cast(o.rp_)} {} - ptr() = default; - - // cppcheck-suppress noExplicitConstructor ; bug in cppcheck 2.3 - ptr(ptr const&) = default; - - // cppcheck-suppress noExplicitConstructor ; initialize from nullptr - constexpr ptr(std::nullptr_t nu) : rp_{nu} {} // NOLINT(runtime/explicit) terse syntax - - ptr& operator=(ptr const&) = default; - - friend constexpr bool operator==(ptr const& s, ptr const& o) { return s.rp_ == o.rp_; } - friend constexpr bool operator!=(ptr const& s, ptr const& o) { return s.rp_ != o.rp_; } - - using element_type = typename raw_pointer_traits::element_type; - using difference_type = typename raw_pointer_traits::difference_type; - using size_type = difference_type; - using value_type = T; - - using pointer = ptr; - using iterator_category = typename std::iterator_traits::iterator_category; - explicit constexpr operator bool() const { return rp_; } - explicit constexpr operator void const*() const { return rp_; } - template(raw_pointer{}))> - explicit constexpr operator TT*() const { return static_cast(rp_); } - ptr& operator++() { - static_assert(not std::is_same{}, "!"); - ++rp_; - return *this; - } - ptr& operator--() {--rp_; return *this;} - - ptr operator++(int) {auto tmp = *this; ++(*this); return tmp;} - ptr operator--(int) {auto tmp = *this; --(*this); return tmp;} - - constexpr ptr& operator+=(difference_type n) {rp_+=n; return *this;} - constexpr ptr& operator-=(difference_type n) {rp_-=n; return *this;} - - constexpr ptr operator+(difference_type n) const { - // static_cast(not std::is_same{} , "!"); - return ptr{rp_ + n}; - } - constexpr ptr operator-(difference_type n) const {return ptr{rp_ - n};} - - using reference = ref; - - [[deprecated("slow")]] constexpr auto operator*() const { return reference{*this}; } - constexpr auto operator[](difference_type n) const { return reference{*((*this) + n)}; } - - constexpr difference_type operator-(ptr const& o) const { return rp_ - o.rp_; } - operator ptr() { return ptr{rp_}; } - HD auto get() const { return rp_; } - - friend constexpr raw_pointer to_address(ptr const& p) { return p.rp_; } // TODO(correaa) consider returning T* , from https://en.cppreference.com/w/cpp/memory/to_address - explicit constexpr operator raw_pointer() const { return rp_; } - constexpr raw_pointer raw_pointer_cast() const { return this->rp_; } - friend constexpr raw_pointer raw_pointer_cast(ptr const& self) { return self.rp_; } - - template - constexpr auto operator->*(PM&& pm) const - ->decltype(ref*std::forward(pm))>>{ptr*std::forward(pm))>>{&(rp_->*std::forward(pm))}}) { - return ref*std::forward(pm))>>{ptr*std::forward(pm))>>{&(rp_->*std::forward(pm))}}; } - - public: - friend allocator> get_allocator(ptr const&) { return {}; } - friend allocator> default_allocator_of(ptr const&) { return {}; } -}; -#endif - -template -DEPRECATED("experimental function, it might be removed soon https://gitlab.com/correaa/boost-multi/-/issues/91") -T* raw_pointer_cast(T* p) { return p; } - -template allocator get_allocator(ptr const&) { return {}; } - -template< - class InputIt, class Size, class... T, class ForwardIt = ptr, - typename InputV = typename std::pointer_traits::element_type, - typename ForwardV = typename std::pointer_traits::element_type, - std::enable_if_t::value, int> =0 -> -ForwardIt uninitialized_copy_n(InputIt f, Size n, ptr d) { - memcpy(d, f, n*sizeof(ForwardV)); - return d + n; -} - -template::value_type, typename = std::enable_if_t{}>> -auto uninitialized_copy_n(It first, Size count, boost::multi::iterator> result) -// ->decltype(memcpy2D(base(result), sizeof(T2)*stride(result), first, sizeof(T)*stride(first), sizeof(T), count), result + count){ -{ return memcpy2D(base(result), sizeof(T2) * stride(result), base(first), sizeof(T) * stride(first), sizeof(T), count), result + count; } - -template::value_type, typename = std::enable_if_t{}>> -auto uninitialized_move_n(It first, Size count, boost::multi::iterator> result) -// ->decltype(memcpy2D(base(result), sizeof(T2)*stride(result), first, sizeof(T)*stride(first), sizeof(T), count), result + count){ -{ return memcpy2D(base(result), sizeof(T2) * stride(result), base(first), sizeof(T) * stride(first), sizeof(T), count), result + count; } - -template>::element_type> -auto uninitialized_move_n(ptr first, Size n, ptr dest) { - assert((std::is_trivially_constructible{})); - memcpy(dest, first, n * sizeof(Element)); - return dest + n; -} - -// copy_n - -template -auto copy_n( - boost::multi::elements_iterator_t< Q1*, L1> first, Size count, - boost::multi::elements_iterator_t, L2> d_first -)-> boost::multi::elements_iterator_t, L2> { - copy_n( - first, count, - static_cast, L2>>(d_first) - ); - return d_first + count; -} - -template -auto copy_n( - boost::multi::elements_iterator_t, L1> first, Size count, - boost::multi::elements_iterator_t< Q2*, L2> d_first -)-> boost::multi::elements_iterator_t< Q2*, L2> { - copy_n( - static_cast, L1>>( first), count, - d_first - ); - return d_first + count; -} - -template -auto copy( - boost::multi::elements_iterator_t< Q1*, L1> first, - boost::multi::elements_iterator_t< Q1*, L1> last , - boost::multi::elements_iterator_t, L2> d_first -)-> boost::multi::elements_iterator_t, L2> { - return copy_n(first, last - first, d_first); -} - -template -auto copy( - boost::multi::elements_iterator_t, L1> first, - boost::multi::elements_iterator_t, L1> last , - boost::multi::elements_iterator_t< Q2*, L2> d_first -)-> boost::multi::elements_iterator_t< Q2*, L2> { - return copy_n(first, last - first, d_first); -} - -template< - multi::dimensionality_type D, - class T1, class Q1, - class T2, class Q2 -> -auto copy( - boost::multi::array_iterator first, - boost::multi::array_iterator last , - boost::multi::array_iterator> d_first -)-> boost::multi::array_iterator> { - copy_n(first, last - first, static_cast>>(d_first)); - return d_first + (last - first); -} - -template< - multi::dimensionality_type D, - class T1, class Q1, - class T2, class Q2 -> -auto copy( - boost::multi::array_iterator> first, - boost::multi::array_iterator> last , - boost::multi::array_iterator d_first -)-> boost::multi::array_iterator { - return copy_n(first, last - first, d_first); -} - -template< - multi::dimensionality_type D, - class T1, class Q1, - class T2, class Q2 -> -auto copy( - boost::multi::array_iterator> first, - boost::multi::array_iterator> last , - boost::multi::array_iterator> d_first -)-> boost::multi::array_iterator> { - return copy_n(first, last - first, d_first); -} - -// uninitialized_copy_n - -template< - multi::dimensionality_type D, - class T1, class Q1, - class Size, - class T2, class Q2 -> -auto uninitialized_copy_n( - boost::multi::array_iterator first , Size count, - boost::multi::array_iterator> d_first -)-> boost::multi::array_iterator> { - uninitialized_copy_n( - first , count, - static_cast>>(d_first) - ); - return d_first + count; -} - -template< - multi::dimensionality_type D, - class T1, class Q1, - class Size, - class T2, class Q2 -> -auto uninitialized_copy_n( - boost::multi::array_iterator> first , Size count, - boost::multi::array_iterator d_first -)-> boost::multi::array_iterator { - uninitialized_copy_n( - static_cast>>( first), count, - d_first - ); - return d_first + count; -} - -template< - multi::dimensionality_type D, - class T1, class Q1, - class Size, - class T2, class Q2 -> -auto uninitialized_copy_n( - boost::multi::array_iterator> first , Size count, - boost::multi::array_iterator> d_first -)-> boost::multi::array_iterator> { - uninitialized_copy_n( - static_cast>>( first), count, - static_cast>>(d_first) - ); - return d_first + count; -} - -// uninitalized copy - -template< - multi::dimensionality_type D, - class T1, class Q1, - class T2, class Q2 -> -auto uninitialized_copy( - boost::multi::array_iterator first, - boost::multi::array_iterator last , - boost::multi::array_iterator> d_first -)-> boost::multi::array_iterator> { - return uninitialized_copy_n(first, last - first, static_cast>>(d_first)); -} - -template< - multi::dimensionality_type D, - class T1, class Q1, - class T2, class Q2 -> -auto uninitialized_copy( - boost::multi::array_iterator> first, - boost::multi::array_iterator> last , - boost::multi::array_iterator d_first -)-> boost::multi::array_iterator { - return uninitialized_copy_n(first, last - first, d_first); -} - -template< - multi::dimensionality_type D, - class T1, class Q1, - class T2, class Q2 -> -auto uninitialized_copy( - boost::multi::array_iterator> first, - boost::multi::array_iterator> last , - boost::multi::array_iterator> d_first -)-> boost::multi::array_iterator> { - return uninitialized_copy_n(first, last - first, d_first); -} - -template< - class Alloc, class InputIt, class Size, class... T, class ForwardIt = ptr, - typename InputV = typename std::pointer_traits::element_type, - typename ForwardV = typename std::pointer_traits::element_type -// , typename = std::enable_if_t{}> -> -ForwardIt alloc_uninitialized_copy_n(Alloc&, InputIt f, Size n, ptr d) { - if(std::is_trivially_constructible{}) { - memcpy(d, f, n*sizeof(ForwardV)); // TODO(correaa) this is not correct whe InputIt is not a pointer - return d + n; - } else { - assert(0); - } - return d; -} - -template> -ForwardIt alloc_uninitialized_move_n(Alloc& a, InputIt f, Size n, ptr d) { - return alloc_uninitialized_copy_n(a, f, n, d); -} - -template -ptr const_pointer_cast(ptr const& p){return ptr{p.impl_};} - -template -static std::true_type is_ref_aux(ref const&); -std::false_type is_ref_aux(...); - -template struct is_ref : decltype(is_ref_aux(std::declval())){}; - -#if 0 -template -struct [[deprecated("use thrust::reference if possible")]] ref { - using value_type = T; - using reference = value_type&; - using pointer = ptr; - using raw_reference = value_type&; - - private: - pointer pimpl_; - constexpr explicit ref(pointer const& p) : pimpl_{p}{} - template friend struct ref; - - public: - constexpr explicit ref(T& t) : pimpl_{&t} {} - template(std::declval>().pimpl_))> - /*explicit(false)*/ constexpr ref(ref&& o) /*HD*/ : pimpl_{multi::implicit_cast(std::move(o).pimpl_)} {} - template>())>, pointer>{}>> - explicit/*(true) */ constexpr ref(ref const& o, void** = 0) /*HD*/ : pimpl_{static_cast(o)} {} - template friend struct ptr; - - pointer operator&() & __host__ __device__ {return pimpl_;} // NOLINT(runtime/operator) special type - pointer operator&() const& __host__ __device__ {return pimpl_;} // NOLINT(runtime/operator) special type - pointer operator&() && __host__ __device__ {return pimpl_;} // NOLINT(runtime/operator) special type - - struct skeleton_t { - std::array buff; - T* p_; - SLOW explicit skeleton_t(T* p) /*HD*/ : p_{p} { -#if __CUDA_ARCH__ -#else - { - cudaError_t s = cudaMemcpy(buff.data(), p_, buff.size(), cudaMemcpyDeviceToHost); - (void)s; - assert(s == cudaSuccess); - } -#endif - } - operator T&() && /*HD*/ { return reinterpret_cast(buff); } - void conditional_copyback_if_not(std::false_type) const /*HD*/ { -#if __CUDA_ARCH__ - // *p_ = reinterpret_cast( -#else - { - cudaError_t s = cudaMemcpy(p_, buff.data(), buff.size(), cudaMemcpyHostToDevice); - (void)s; - assert(s == cudaSuccess); - } -#endif - } - void conditional_copyback_if_not(std::true_type) const /*HD*/ { -#if __CUDA_ARCH__ - // *p_ = reinterpret_cast( -#else - // [[maybe_unused]] - [[maybe_unused]] cudaError_t s = cudaMemcpy(p_, buff.data(), buff.size(), cudaMemcpyHostToDevice); - assert(s == cudaSuccess); -#endif - } - ~skeleton_t() /*HD*/ { conditional_copyback_if_not(std::is_const{}); } - }; - skeleton_t skeleton() && /*HD*/ { return skeleton_t{raw_pointer_cast(pimpl_.rp_)}; } - - public: - constexpr ref(ref&& r) : pimpl_{std::move(r.pimpl_).rp_} {} - - private: - ref& move_assign(ref&& other, std::true_type) & { - [[maybe_unused]] cudaError_t s = cudaMemcpy(pimpl_.rp_, other.rp_, sizeof(T), cudaMemcpyDeviceToDevice); - assert(s == cudaSuccess); - return *this; - } - ref& move_assign(ref&& other, std::false_type) & { - [[maybe_unused]] cudaError_t s = cudaMemcpy(pimpl_.rp_, other.rp_, sizeof(T), cudaMemcpyDeviceToDevice); - assert(s == cudaSuccess); - return *this; - } - - public: - template{}, int> =0> - [[deprecated]] - ref&& operator=(ref const& other) && { - cudaError_t s = cudaMemcpy(pimpl_.rp_, other.pimpl_.rp_, sizeof(T), cudaMemcpyDeviceToDevice); assert(s==cudaSuccess); (void)s; - return std::move(*this); - } -#if __CUDA__ -#ifdef __NVCC__ -#ifndef __CUDA_ARCH__ - template - [[deprecated]] __host__ auto operator=(TT&& t) && -> decltype(*pimpl_.rp_ = std::forward(t), std::move(*this)) { - assert(0); - return std::move(*this); - } -#else - template - __device__ auto operator=(TT&& t) && -> decltype(*pimpl_.rp_ = std::forward(t), std::move(*this)) { - return *pimpl_.rp_ = std::forward(t), std::move(*this); - } -#endif -#else - template - [[deprecated]] __host__ auto operator=(TT&& t) && -> decltype(*pimpl_.rp_ = std::forward(t), std::move(*this)) { - static_assert(std::is_trivially_assignable{}, "!"); - cudaError_t s = cudaMemcpy(pimpl_.rp_, std::addressof(t), sizeof(T), cudaMemcpyHostToDevice); - assert(s == cudaSuccess); - (void)s; - return std::move(*this); - } - template - __device__ ref&& operator=(TT&& t) && { - *pimpl_.rp_ = std::forward(t); - return std::move(*this); - } -#endif -#else - template{}>> - SLOW - ref&& operator=(TT const& t) &&{ - static_assert(std::is_trivially_assignable{}); - cudaError_t s=cudaMemcpy(pimpl_.rp_, std::addressof(t), sizeof(T), cudaMemcpyHostToDevice);assert(s==cudaSuccess);(void)s; - return std::move(*this); - } -#endif - -#if defined(__clang__) -#if defined(__CUDA__) // && !defined(__CUDA_ARCH__) - operator T() && __device__ { return *(pimpl_.rp_); } - operator T() && __host__ { - static_assert(std::is_trivially_copyable>{}, "!"); - typename std::aligned_storage::type ret; - { - [[maybe_unused]] cudaError_t s = cudaMemcpy((void*)&ret, pimpl_.rp_, sizeof(T), cudaMemcpyDeviceToHost); // NOLINT(readability/casting) C code - assert(s == cudaSuccess); - } - return *reinterpret_cast(&ret); - } - operator T() const& __host__ { - static_assert(std::is_trivially_copyable>{}, "!"); - typename std::aligned_storage::type ret; - { - [[maybe_unused]] cudaError_t s = cudaMemcpy((void*)&ret, pimpl_.rp_, sizeof(T), cudaMemcpyDeviceToHost); // NOLINT(readability/casting) C code - assert(s == cudaSuccess); - } - return *reinterpret_cast(&ret); - } -#else - SLOW operator T() && { - std::array buff; - cudaError_t s = cudaMemcpy(buff.data(), pimpl_.rp_, buff.size(), cudaMemcpyDeviceToHost); - switch(s) { - case cudaSuccess: break; - case cudaErrorInvalidValue: throw std::runtime_error{"cudaErrorInvalidValue"}; - case cudaErrorInvalidMemcpyDirection: throw std::runtime_error{"cudaErrorInvalidMemcpyDirection"}; - default: throw std::runtime_error{"unknown error"}; - } - return std::move(reinterpret_cast(buff)); - } - SLOW operator T() const& { - std::array buff; // char buff[sizeof(T)]; - cudaError_t s = cudaMemcpy(buff.data(), pimpl_.rp_, buff.size(), cudaMemcpyDeviceToHost); - switch(s) { - case cudaSuccess: break; - case cudaErrorInvalidValue: throw std::runtime_error{"cudaErrorInvalidValue"}; - case cudaErrorInvalidMemcpyDirection: throw std::runtime_error{"cudaErrorInvalidMemcpyDirection"}; - default: throw std::runtime_error{"unknown error"}; - } - return reinterpret_cast(buff); - } -#endif -#else // no clang -#if __CUDA_ARCH__ - operator T() && __device__ { return *(pimpl_.rp_); } -#else - SLOW operator T() && __host__ { - std::array buff; // char buff[sizeof(T)]; - { - [[maybe_unused]] cudaError_t s = cudaMemcpy(buff.data(), pimpl_.rp_, buff.size(), cudaMemcpyDeviceToHost); - assert(s == cudaSuccess); - } - return std::move(reinterpret_cast(buff)); - } -#endif -#if defined(__clang__) - [[SLOW]] operator T() const& __host__ { - std::array buff; // char buff[sizeof(T)]; - { - // cudaError_t s = cudaMemcpy(buff, this->rp_, sizeof(T), cudaMemcpyDeviceToHost); - auto e = static_cast(cudaMemcpy(buff.data(), pimpl_.rp_, buff.size(), cudaMemcpyDeviceToHost)); - if(e != Cuda::error::success) throw std::system_error(e, " when trying to memcpy for element access"); - } - return std::move(reinterpret_cast(buff)); - } - operator T() const& __device__ { return *(pimpl_.rp_); } -#else // no clang -#if __CUDA_ARCH__ - operator T() const& __device__{return *(pimpl_.rp_);} -#else - SLOW - operator T() const& __host__{ - std::array buff; // char buff[sizeof(T)]; - { - auto e = static_cast(cudaMemcpy(buff.data(), pimpl_.rp_, buff.size(), cudaMemcpyDeviceToHost)); - if(e != Cuda::error::success) {throw std::system_error(e, " when trying to memcpy for element access");} - } - return std::move(reinterpret_cast(buff)); - } -#endif -#endif - #endif - -#ifndef _MULTI_MEMORY_CUDA_DISABLE_ELEMENT_ACCESS - bool operator!=(ref const& other) const&{return not(*this == other);} - template - bool operator!=(ref&& other)&&{ - std::array buff1; // char buff1[sizeof(T)]; - {cudaError_t s1 = cudaMemcpy(buff1.data(), this->impl_, buff1.size(), cudaMemcpyDeviceToHost); assert(s1 == cudaSuccess); (void)s1;} - std::array buff2; - {cudaError_t s2 = cudaMemcpy(buff2.data(), other.impl_, buff2.size(), cudaMemcpyDeviceToHost); assert(s2 == cudaSuccess); (void)s2;} - return reinterpret_cast(buff1) != reinterpret_cast(buff2); - } -#else -// bool operator==(ref const& other) const = delete; -#endif -#if 1 - -#if defined(__clang__) -#if defined(__CUDA__) && defined(__CUDA_ARCH__) - template>::value>> - friend auto operator==(ref&& self, Other&& other) __host__ { - // #if __CUDA_ARCH__ - // return std::forward(other)==*(this->rp_); - // return *(self->rp_) == std::forward(other); - // #else - return std::move(self).operator T() == std::forward(other); - // return static_cast(std::move(self)) == std::forward(other); -// #endif - } - template{}> > - friend auto operator==(ref&& self, Other&& other) __device__ { - return *(self->rp_) == std::forward(other); - } -#else - template{}> > - friend auto operator==(ref&& self, Other&& other) __host__ { - return std::move(self).operator T() == std::forward(other); - } -#endif -#else // no clang - template::value>> - friend auto operator==(ref&& self, Other&& other) /*HD*/{ -// #if __CUDA_ARCH__ -// return *(self.pimpl_.rp_) == std::forward(other); -// #else - return static_cast(std::move(self)) == std::forward(other); -// #endif - } -#endif - - friend __host__ __device__ decltype(auto) raw_reference_cast(ref&& r) { return *raw_pointer_cast(&r); } - friend __host__ __device__ auto raw_value_cast(ref&& r) { return std::move(r).operator T(); } - auto raw_value_cast() && { return std::move(*this).operator T(); } - - template::value>> - friend constexpr bool operator==(Other&& other, ref const& self) { -#if __CUDA_ARCH__ - // return std::forward(other)==*(this->rp_); - return std::forward(other)==*(self.pimpl_); -#else - return std::forward(other)== self.operator T(); // static_cast(std::move(self)); -#endif - } - template{}>> - SLOW bool operator==(ref&& other) && { - std::array buff1; // char buff1[sizeof(T)]; - // cuda::memcpy(buff1, ref::rp_, sizeof(T)); - {cudaError_t s1 = cudaMemcpy(buff1.data(), pimpl_.rp_, buff1.size(), cudaMemcpyDeviceToHost); assert(s1 == cudaSuccess); (void)s1;} - std::array buff2; // char buff2[sizeof(Other)]; - { - [[maybe_unused]] cudaError_t s2 = cudaMemcpy(buff2.data(), raw_pointer_cast(&other), buff2.size(), cudaMemcpyDeviceToHost); - assert(s2 == cudaSuccess); - } - return reinterpret_cast(buff1) == reinterpret_cast(buff2); - } -#if 1 - SLOW bool operator==(ref const& other) && { - std::array buff1; // char buff1[sizeof(T)]; - { - [[maybe_unused]] cudaError_t s1 = cudaMemcpy(buff1.data(), pimpl_.rp_, buff1.size(), cudaMemcpyDeviceToHost); - assert(s1 == cudaSuccess); - } - std::array buff2; // char buff2[sizeof(T)]; - { - [[maybe_unused]] cudaError_t s2 = cudaMemcpy(buff2.data(), other.pimpl_.rp_, buff2.size(), cudaMemcpyDeviceToHost); - assert(s2 == cudaSuccess); - } - return reinterpret_cast(buff1) == reinterpret_cast(buff2); - } -#endif -#endif - -#if __CUDA_ARCH__ - template() += std::declval())> __device__ ref& operator+=(O&& o) && { - *(pimpl_.rp_) += std::forward(o); - return std::move(*this); - } - template() -= std::declval())> __device__ ref& operator-=(O&& o) && { - *(pimpl_.rp_) -= std::forward(o); - return std::move(*this); - } -#else - template() += std::declval())> __host__ SLOW ref&& operator+=(O&& o) && { - std::array buff; - { - [[maybe_unused]] cudaError_t s = cudaMemcpy(buff.data(), pimpl_.rp_, buff.size(), cudaMemcpyDeviceToHost); - assert(s == cudaSuccess); - } - reinterpret_cast(buff) += std::forward(o); - { - [[maybe_unused]] cudaError_t s = cudaMemcpy(pimpl_.rp_, buff.data(), buff.size(), cudaMemcpyHostToDevice); - assert(s == cudaSuccess); - } - return std::move(*this); - } - template() -= std::declval())> __host__ SLOW ref&& operator-=(O&& o) && { - std::array buff; - { - [[maybe_unused]] cudaError_t s = cudaMemcpy(buff.data(), pimpl_.rp_, buff.size(), cudaMemcpyDeviceToHost); - assert(s == cudaSuccess); - (void)s; - } - reinterpret_cast(buff) -= std::forward(o); - { - [[maybe_unused]] cudaError_t s = cudaMemcpy(pimpl_.rp_, buff.data(), buff.size(), cudaMemcpyHostToDevice); - assert(s == cudaSuccess); - } - return std::move(*this); - } -#endif - - private: - template - void swap(Ref&& b) && { - T tmp = std::move(*this); - BEGIN_CUDA_SLOW - *this = std::forward(b); - b = std::move(tmp); - END_CUDA_SLOW - } - - public: - template -#if __NVCC__ - __attribute__((deprecated)) -#else - [[deprecated("WARNING: slow cuda memory operation")]] -#endif - friend void swap(ref&& a, Ref&& b) {a.swap(std::forward(b));} - template DEPRECATED("WARNING: slow cuda memory operation") - friend void swap(Ref&& a, ref&& b) {std::move(b).swap(a);} - DEPRECATED("WARNING: slow cuda memory operation") - friend void swap(ref&& a, ref&& b){std::move(a).swap(std::move(b));} - ref&& operator++()&& {++(std::move(*this).skeleton()); return std::move(*this);} - ref&& operator--()&& {--(std::move(*this).skeleton()); return std::move(*this);} - template{}, int> =0> - friend /*std::decay_t*/ auto conj(RRef const& self) { - return adl_conj(self.operator T()); - } - template{}, int> =0> - friend auto /*std::decay_t*/ imag(RRef const& self) { - return adl_imag(self.operator T()); - } - template{}, int> =0> - friend auto /*std::decay_t*/ real(RRef const& self) { - return adl_real(self.operator T()); - } -}; -#endif - -} // namespace cuda -} // namespace memory -} // namespace multi -} // namespace boost - -namespace thrust { -template P raw_pointer_cast(boost::multi::memory::cuda::ptr const& p) __host__ __device__ { - return p.raw_pointer_cast(); -} -} -#undef SLOW - -#if not __INCLUDE_LEVEL__ // def _TEST_MULTI_MEMORY_ADAPTORS_CUDA_PTR - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA pointers" -#define BOOST_TEST_DYN_LINK -#include - -#include "../cuda/malloc.hpp" - -#include "../../../adaptors/blas/numeric.hpp" - -namespace multi = boost::multi; -namespace cuda = multi::memory::cuda; - -template __device__ void WHAT(T&&) = delete; - -#if __CUDA_ARCH__ -__device__ void f(cuda::ptr){ -// printf("%f", *p); -// printf("%f", static_cast(*p)); -} -#endif - -BOOST_AUTO_TEST_CASE(multi_memory_cuda_ptr) { - static_assert(not std::is_convertible*, multi::memory::cuda::ptr>>::value); - static_assert(not std::is_convertible>, std::complex*>::value); - - multi::memory::cuda::ptr> xxx = nullptr; - std::complex* ppp = raw_pointer_cast(xxx); - (void)ppp; - { - auto ppp2 = static_cast>>(cuda::malloc(1*sizeof(std::complex))); - std::complex const dd{*ppp2}; - assert( dd == std::complex{0} ); - } - using T = double; - static_assert( sizeof(cuda::ptr) == sizeof(T*), "!"); - std::size_t const n = 100; - { - using cuda::ptr; - auto p = static_cast>(cuda::malloc(n*sizeof(T))); -CUDA_SLOW( - *p = 99.; -) - { - ptr pc = p; - BOOST_REQUIRE( *p == *pc ); - } - BOOST_REQUIRE( CUDA_SLOW( *p == 99. ) ); - BOOST_REQUIRE( *p != 11. ); - cuda::free(p); - - cuda::ptr P = nullptr; - BOOST_REQUIRE( P == nullptr ); - ptr pv = p; (void)pv; - } - static_assert( std::is_same>::rebind, cuda::ptr>{} , "!"); -} - -BOOST_AUTO_TEST_CASE(ptr_conversion){ - cuda::ptr p = nullptr; - cuda::ptr pc = p; (void)pc; - static_assert(not std::is_convertible, double*>{}); -} - -template struct Complex_{T real; T imag;}; - -BOOST_AUTO_TEST_CASE(multi_memory_cuda_ptr_member_pointer) { - Complex_ c{10.0, 20.0}; -// double Complex_::* - Complex_* p = &c; - auto pm = &Complex_::imag; - BOOST_REQUIRE( p->*pm == 20.0 ); - BOOST_REQUIRE( *p.*pm == 20.0 ); - -// cuda::ptr> pcu; -// pcu->*pm; -} -#endif -#endif // MULTI_MEMORY_ADAPTORS_CUDA_PTR_HPP_ diff --git a/include/multi/memory/adaptors/cuda_/test/array.cpp b/include/multi/memory/adaptors/cuda_/test/array.cpp deleted file mode 100644 index 126214b23..000000000 --- a/include/multi/memory/adaptors/cuda_/test/array.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS -${CUDACXX:-nvcc} -std=c++17 -x cu -O3 $0 -o $0x --extended-lambda --expt-relaxed-constexpr --Werror=cross-execution-space-call -lboost_unit_test_framework -lboost_timer -Xcudafe=--display_error_number -D_DISABLE_CUDA_SLOW &&$0x&&rm $0x; exit -#endif - -#define _DISABLE_CUDA_SLOW - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi CUDA array" -#define BOOST_TEST_DYN_LINK -#include - -//#include "../../cuda/allocator.hpp" -//#include "../../../../array.hpp" - -#include "../../../../adaptors/cuda.hpp" - -#include - -namespace multi = boost::multi; -namespace cuda = multi::memory::cuda; - -template T what() = delete; -template T what(T&&) = delete; - -BOOST_AUTO_TEST_CASE(cuda_allocators){ - - multi::array > A1(200, 0.); - - BOOST_REQUIRE( size(A1) == 200 ); - BEGIN_CUDA_SLOW; - A1[100] = 1.; - END_CUDA_SLOW; -// what(A1.data()); - - multi::array> const B1(200, 2.); -// what(B1.data()); - BEGIN_CUDA_SLOW; - BOOST_REQUIRE( B1[10] == 2. ); - END_CUDA_SLOW; - -// what(A1[10]); -// what(B1[10]); -BEGIN_CUDA_SLOW; - A1[10] = B1[10]; -END_CUDA_SLOW; -// BOOST_REQUIRE( A1[10] == 2. ); - -// multi::array> C1(200, 0.); - -// B1[100] = 2.; -// C1[100] = 3.; - -} - -BOOST_AUTO_TEST_CASE(cuda_copy_timing){ - multi::array::extensions_type const x = {10000, 10000}; - std::cout<<"double 2D\nsize "<< x.num_elements()*sizeof(double)/1e6 <<" MBs"< const A(x, 999.); - multi::array B(x, 888.); - boost::timer::cpu_timer timer; - ~B() = ~A(); - BOOST_REQUIRE( B[1000][1000] == 999. ); - return timer.elapsed(); - }(); - std::cout<<"cpu "<< cpu_times.wall/1e9 <<" sec"<> const A(x, 999.); - multi::array> B(x, 888.); - boost::timer::cpu_timer timer; - ~B() = ~A(); - BOOST_REQUIRE( B[1000][1000] == 999. ); - return timer.elapsed(); - }(); - std::cout<<"gpu "<< gpu_times.wall/1e9 <<" sec"<> const A(x, 999.); - multi::array> B(x, 888.); - B() = A(); - boost::timer::cpu_timer timer; - ~B() = ~A(); - BOOST_REQUIRE( B[1000][1000] == 999. ); - return timer.elapsed(); - }(); - std::cout<<"mng "<< mng_times.wall/1e9 <<" sec"<; - multi::array::extensions_type const x = {10000, 10000}; - std::cout<<"complex 2D\nsize "<< x.num_elements()*sizeof(complex)/1e6 <<" MBs"<>{} ); - - auto const cpu_times = [&]{ - multi::array const A(x, 999.); - multi::array B(x, 888.); - boost::timer::cpu_timer timer; - B() = A(); - BOOST_REQUIRE( B[1000][1000] == 999. ); - return timer.elapsed(); - }(); - std::cout<<"cpu "<< cpu_times.wall/1e9 <<" sec"<< std::endl; - - auto const gpu_times = [&]{ - multi::array> const A(x, 999.); - multi::array> B(x, 888.); - boost::timer::cpu_timer timer; - ~B() = ~A(); - BOOST_REQUIRE( B[1000][1000] == 999. ); - return timer.elapsed(); - }(); - std::cout<<"gpu "<< gpu_times.wall/1e9 <<" sec"<< std::endl; - - auto const mng_times = [&]{ - multi::array> const A(x, 999.); - multi::array> B(x, 888.); - B() = A(); - boost::timer::cpu_timer timer; - ~B() = ~A(); - BOOST_REQUIRE( B[1e3][1e3] == 999. ); - return timer.elapsed(); - }(); - std::cout<<"mng "<< mng_times.wall/1e9 <<" sec"<< std::endl; -} - -BOOST_AUTO_TEST_CASE(cuda_managed_empty){ - using complex = std::complex; - multi::array> A; - multi::array> B = A; - BOOST_REQUIRE( A.is_empty() ); - BOOST_REQUIRE( B.is_empty() ); - BOOST_REQUIRE( A == B ); -} - -BOOST_AUTO_TEST_CASE(cuda_copy_complex_timing_4d){ - using complex = std::complex; - multi::array::extensions_type const x = {100, 100, 100, 100}; - std::cout<<"complex 4D\nsize "<< x.num_elements()*sizeof(complex)/1e6 <<" MBs"<>{} ); - - auto const cpu_times = [&]{ - multi::array const A(x, 999.); - multi::array B(x, 888.); - boost::timer::cpu_timer timer; - B() = A(); - BOOST_REQUIRE( B[10][10][10][10]== 999. ); - return timer.elapsed(); - }(); - std::cout<<"cpu "<< cpu_times.wall/1e9 <<" sec"<< std::endl; - - auto const gpu_times = [&]{ - multi::array> const A(x, 999.); - multi::array> B(x, 888.); - boost::timer::cpu_timer timer; - ~B() = ~A(); - BOOST_REQUIRE( B[10][10][10][10]== 999. ); - return timer.elapsed(); - }(); - std::cout<<"gpu "<< gpu_times.wall/1e9 <<" sec"<< std::endl; - - auto const mng_times = [&]{ - multi::array> const A(x, 999.); - multi::array> B(x, 888.); - B() = A(); - boost::timer::cpu_timer timer; - ~B() = ~A(); - BOOST_REQUIRE( B[10][10][10][10]== 999. ); - return timer.elapsed(); - }(); - std::cout<<"mng "<< mng_times.wall/1e9 <<" sec"<< std::endl; -} - diff --git a/include/multi/memory/adaptors/cuda_/test/mngd_ptr.cpp b/include/multi/memory/adaptors/cuda_/test/mngd_ptr.cpp deleted file mode 100644 index 94385dd65..000000000 --- a/include/multi/memory/adaptors/cuda_/test/mngd_ptr.cpp +++ /dev/null @@ -1,133 +0,0 @@ - -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi BLAS gemm" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../../../adaptors/cuda/cublas/context.hpp" - -#include "../managed/ptr.hpp" -#include "../../../../adaptors/cuda.hpp" -#include "../../../../adaptors/blas/gemm.hpp" -#include "../../../../adaptors/blas/trsm.hpp" - -#include - -namespace multi = boost::multi; -namespace cuda = multi::memory::cuda; -namespace blas = multi::blas; - -namespace utf = boost::unit_test; - -BOOST_AUTO_TEST_CASE(multi_cuda_mngd_ptr){ - using T = double; - static_assert( sizeof(cuda::managed::ptr) == sizeof(T*) ); - static_assert( std::is_convertible, T*>{} ); - auto f = [](double* dp){return bool{dp};}; - cuda::managed::ptr p; - f(p); -} - -template void what(T&&) = delete; - -BOOST_AUTO_TEST_CASE(multi_cuda_mngd_ptr_call_gemm){ - using complex = std::complex; complex const I{0, 1}; - boost::multi::cuda::managed::array m = { - { 1. + 2.*I, 3. - 3.*I, 1.-9.*I}, - { 9. + 1.*I, 7. + 4.*I, 1.-8.*I}, - }; - boost::multi::cuda::managed::array const b = { - { 11.+1.*I, 12.+1.*I, 4.+1.*I, 8.-2.*I}, - { 7.+8.*I, 19.-2.*I, 2.+1.*I, 7.+1.*I}, - { 5.+1.*I, 3.-1.*I, 3.+8.*I, 1.+1.*I} - }; -// { -// blas::context ctxt; -// auto c =+ blas::gemm(&ctxt, 1., m, b); -// static_assert( std::is_same>{} ); -// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); -// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); -// } -// { -// multi::cuda::managed::array c({2, 4}); -// multi::cuda::cublas::context ctxt; -// blas::gemm_n(ctxt, 1., begin(m), size(m), begin(b), 0., begin(c)); -// cudaDeviceSynchronize(); -// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); -// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); -// } - { - multi::cuda::cublas::context ctxt; - auto c =+ blas::gemm(&ctxt, 1., m, b); - static_assert( std::is_same>{} ); - BOOST_REQUIRE( c[1][2] == complex(112, 12) ); - BOOST_REQUIRE( b[1][2] == 2.+1.*I ); - } -// { -// auto c =+ blas::gemm(1., m, b); -// static_assert( std::is_same>{} ); -// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); -// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); -// } -// { -// auto c =+ blas::gemm(1., m, b);//blas::default_context_of(m.base()), 1., m, b); -// static_assert( std::is_same>{} ); -// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); -// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); -// } -// { -// multi::cuda::managed::array c({2, 4}); -// multi::cuda::cublas::context ctxt; -// blas::gemm_n(ctxt, 1., begin(m), size(m), begin(b), 0., begin(c)); -// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); -// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); -// } -// { -// auto c =+ blas::gemm(1., m, b); -// static_assert( std::is_same>{} ); -// BOOST_REQUIRE( c[1][2] == complex(112, 12) ); -// BOOST_REQUIRE( b[1][2] == 2.+1.*I ); -// } -} - -//BOOST_AUTO_TEST_CASE(multi_blas_trsm_complex_nonsquare_default_diagonal_hermitized_gemm_check_managed_ptr, *utf::tolerance(0.00001)){ -// using complex = std::complex; complex const I{0, 1}; -// multi::cuda::managed::array const A = { -// { 1. + 4.*I, 3., 4.- 10.*I}, -// { 0., 7.- 3.*I, 1.}, -// { 0., 0., 8.- 2.*I} -// }; -// namespace blas = multi::blas; -// { -// { -// multi::cuda::managed::array B = { -// {1. + 1.*I, 5. + 3.*I}, -// {2. + 1.*I, 9. + 3.*I}, -// {3. + 1.*I, 1. - 1.*I}, -// }; -// blas::trsm(blas::side::left, blas::filling::lower, 1., blas::H(A), B); // S = A⁻¹†.B, S† = B†.A⁻¹ -// cudaDeviceSynchronize(); -// BOOST_TEST( real(B[2][1]) == 1.71608 ); -// } -// { -// multi::cuda::managed::array B = { -// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, -// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} -// }; -// auto const S =+ blas::trsm(blas::side::left, blas::filling::upper, 1., A, blas::H(B)); // S = A⁻¹B†, S†=B.A⁻¹†, S=(B.A⁻¹)†, B <- S†, B <- B.A⁻¹† -// cudaDeviceSynchronize(); -// // BOOST_TEST( imag(S[2][1]) == +0.147059 ); -// BOOST_TEST( imag(B[1][2]) == -0.147059 ); -// } -// { -// multi::cuda::managed::array B = { -// {1. + 1.*I, 2. + 1.*I, 3. + 1.*I}, -// {5. + 3.*I, 9. + 3.*I, 1. - 1.*I} -// }; -// auto const S =+ blas::trsm(blas::side::left, blas::filling::upper, 2., A, blas::H(B)); // S = A⁻¹B†, S†=B.A⁻¹†, S=(B.A⁻¹)†, B <- S†, B <- B.A⁻¹† -// cudaDeviceSynchronize(); -// // BOOST_TEST( imag(S[2][1]) == +0.147059*2. ); -// BOOST_TEST( imag(B[1][2]) == -0.147059*2. ); -// } -// } -//} - diff --git a/include/multi/memory/allocator.hpp b/include/multi/memory/allocator.hpp deleted file mode 100644 index 07359de58..000000000 --- a/include/multi/memory/allocator.hpp +++ /dev/null @@ -1,73 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// © Alfredo A. Correa 2019-2022 - -#ifndef MULTI_MEMORY_ALLOCATOR_HPP_ -#define MULTI_MEMORY_ALLOCATOR_HPP_ - -#include "../config/NODISCARD.hpp" -#include "../detail/memory.hpp" - -#include - -#include - -namespace boost::multi::memory { - -template -> -class allocator { - using memory_type = Memory; - memory_type* mp_ = std::pmr::get_default_resource(); - using constructor_type = typename std::allocator_traits::template rebind_alloc; - constructor_type ctor_; - - public: - using value_type = T; - using pointer = typename std::pointer_traits()->allocate(0, 0))>::template rebind; - using difference_type = typename std::pointer_traits::difference_type; - using size_type = std::make_unsigned_t; - - allocator(allocator const& o) = default; - allocator(allocator&&) noexcept = default; - - auto operator=(allocator const&) -> allocator& = default; - auto operator=(allocator&&) noexcept -> allocator& = default; - - allocator() = default; - ~allocator() = default; - - allocator(memory_type* mp, constructor_type const& ctor) : mp_{mp}, ctor_{ctor} {} - // cppcheck-suppress noExplicitConstructor ; allocator *is* a pointer to a heap - allocator(memory_type* mp) : allocator(mp, constructor_type{}) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions,runtime/explicit) to allow pointer syntax - - explicit allocator(constructor_type const& ctor) : mp_{}, ctor_{ctor} {} - - auto operator==(allocator const& o) const -> bool {return mp_ == o.mp_;} - auto operator!=(allocator const& o) const -> bool {return mp_ != o.mp_;} - - using void_pointer = typename std::pointer_traits()->allocate(0, 0))>::template rebind; - - NODISCARD("because otherwise it will generate a memory leak") - auto allocate(size_type n) -> pointer { - return static_cast(static_cast(mp_->allocate( - n*sizeof(value_type), - std::max( - alignof(std::max_align_t), - std::size_t{16} // for cuda - ) - ))); - } - void deallocate(pointer p, size_type n) { - mp_->deallocate(p, n*sizeof(value_type)); - } - template - void construct(pointer p, Args&&... args) { - allocator_traits::construct(ctor_, p, std::forward(args)...); - } - void destroy(pointer p) {allocator_traits::destroy(ctor_, p);} -}; - -} // end namespace boost::multi::memory -#endif // MULTI_MEMORY_ALLOCATOR_HPP_ diff --git a/include/multi/memory/block.hpp b/include/multi/memory/block.hpp deleted file mode 100644 index f491e2779..000000000 --- a/include/multi/memory/block.hpp +++ /dev/null @@ -1,150 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_BLOCK_HPP_ -#define MULTI_MEMORY_BLOCK_HPP_ - -#include // for assert -#include // for nullptr_t, size_t -#include // for distance -#include // for pointer_traits - -#ifdef USE_BOOST_MULTI_MEMORY_MALLOC -#include -#endif - -namespace boost { -namespace multi { -namespace memory { - -#if (__cpp_lib_byte < 201603) -enum class byte : unsigned char {}; -#else -using byte = std::byte; -#endif - -template< - typename Ptr = byte*, - typename Diff = typename std::pointer_traits::difference_type -> -struct block; - -namespace detail { -template -struct basic_block { - using pointer = typename std::pointer_traits::pointer; // basic_block? block? - using element_type = typename std::pointer_traits::element_type; - using difference_type = Difference; // typename std::pointer_traits::difference_type; - using size_type = difference_type; - - public: - typename basic_block::pointer start_; - size_type size_; - constexpr basic_block() = default; - constexpr basic_block(pointer p, size_type size) : start_{p}, size_{size} {} - constexpr basic_block(std::nullptr_t p, size_type s) : start_{p}, size_{s} { assert(!s); } -#ifdef USE_BOOST_MULTI_MEMORY_MALLOC - template - explicit constexpr basic_block(OtherPtr p) - : start_{p}, size_{static_cast(malloc_usable_size(p))} { assert(size_); } -#endif - constexpr size_type size() const { return size_; } - constexpr operator Ptr const&() const { return start_; } - constexpr bool contains(typename basic_block::pointer p) const { - using std::distance; - difference_type d = distance(start_, p); - return d >= 0 and d < size_; - } - friend constexpr size_type size(basic_block const& self) { return self.size(); } -}; -} // namespace detail - -template -struct block : detail::basic_block { - using detail::basic_block::basic_block; -}; - -template -struct block : detail::basic_block { - using detail::basic_block::basic_block; - template - constexpr explicit block(T (&t)[N]) : detail::basic_block{t, N} {} -}; - -#if (__cpp_deduction_guides >= 201703) -template block(Ptr p, Size s) -> block; -#endif - -#ifdef USE_BOOST_MULTI_MEMORY_MALLOC -#if (__cpp_deduction_guides >= 201703) -template -block(Ptr p) -> block::difference_type>; -#endif -template -constexpr size_t size(Ptr const& p) { return block(p).size(); } // size();}//static_cast const&>(p).size();} -#endif - -} // namespace memory -} // namespace multi -} // namespace boost - -#if _TEST_BOOST_MULTI_MEMORY_BLOCK - -#include - -namespace multi = boost::multi; -namespace memory = multi::memory; - -int main() { - char A[1024]; // flawfinder: ignore testing legacy type - memory::block a{A}; - - std::vector V(1000); - memory::block v(V.data(), 500); - [[maybe_unused]] memory::block v2 = v; - - memory::byte B[1024]; - memory::block<> b{B}; - - { - std::vector V(1000); - memory::block> v(V.data(), {}); - [[maybe_unused]] memory::block v2 = v; - } -#if (__cpp_deduction_guides >= 201703) - { - std::vector V(1000); - memory::block v(V.data(), std::integral_constant{}); - [[maybe_unused]] memory::block v2 = v; - assert(v == v2); - } -#endif -#ifdef USE_BOOST_MULTI_MEMORY_MALLOC - { - char* p = new char[200]; - memory::block bp = p; // non standard - assert(bp.size() >= 200); - using multi::memory::size; - assert(size(p) >= 200); - } - { - memory::block b = new char[200]; - *b = 'a'; - *(b + 1) = 'b'; - assert(*b == 'a'); - assert(b[0] == 'a'); - assert(b[1] == 'b'); - assert(size(b) >= 200); - delete b; - } -#if (__cpp_deduction_guides >= 201703) - { - memory::block b = new char[200]; - assert(size(b) >= 200); - delete b; - } -#endif -#endif -} -#endif -#endif // MULTI_MEMORY_BLOCK_HPP_ diff --git a/include/multi/memory/fallback.hpp b/include/multi/memory/fallback.hpp deleted file mode 100644 index 20961bdb8..000000000 --- a/include/multi/memory/fallback.hpp +++ /dev/null @@ -1,197 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_FALLBACK_HPP_ -#define MULTI_MEMORY_FALLBACK_HPP_ - -#if defined(__cpp_lib_memory_resource) and (__cpp_lib_memory_resource>=201603L) -#include -#endif - -#include "../memory/block.hpp" -#include "../memory/allocator.hpp" - - -#include // for max -#include // aligned_alloc, in c++17 this will be -#include // std::max_align_t - -namespace boost { -namespace multi { -namespace memory { - -template struct resource; - -template -struct resource{ - auto allocate(std::size_t size, std::size_t alignment = alignof(std::max_align_t)){ - return ::aligned_alloc(alignment, size); - } - void deallocate(void* ptr, std::size_t = alignof(std::max_align_t)){::free(ptr);} -}; - -template -inline auto get_default_resource() { - static resource instance_; - return &instance_; -} - -template=201603L) - = std::pmr::memory_resource -#else - = memory::resource<> -#endif -> -class fallback : public MemoryResource1 { - MemoryResource2* back_ = nullptr; - std::size_t fallbacks_ = 0; - - public: - std::size_t fallbacks() const {return fallbacks_;} - fallback() = default; - - fallback(MemoryResource1 const& mr, MemoryResource2* back) : MemoryResource1{mr}, back_{back} {} - - // cppcheck-suppress noExplicitConstructor ; allocators are pointers to memory resources - fallback(MemoryResource1 const& mr) // NOLINT(runtime/explicit) - : fallback{ - mr, -#if(__cpp_lib_memory_resource>=201603L) - std::pmr::get_default_resource() -#else - nullptr // memory::get_default_resource<>() -#endif - } {} - - typename fallback::void_pointer - allocate(size_type required_bytes, typename fallback::size_type align = alignof(std::max_align_t)) try { - return MemoryResource1::allocate(required_bytes, align); - } catch(...) { - ++fallbacks_; - return back_->allocate(required_bytes, align); - } - void deallocate(typename fallback::void_pointer p, typename fallback::size_type discarded_bytes) try { - MemoryResource1::deallocate(p, discarded_bytes); - } catch(...) {back_->deallocate(p, discarded_bytes);} -}; - -template=201603L) - = std::pmr::memory_resource // = std::allocator -#else - = memory::resource<> -#endif -> -using fallback_allocator = memory::allocator>; // , alignof(T)>>; - -} // end namespace memory -} // end namespace multi -} // end namespace boost - -#if not __INCLUDE_LEVEL__ // _TEST_MULTI_MEMORY_FALLBACK - -#include "../../multi/array.hpp" -#include "../memory/stack.hpp" - -#include -#include -#include - -namespace multi = boost::multi; -namespace memory = multi::memory; -using std::cout; - -int main() { -{ - alignas(double) std::array buffer; // char buffer[256*sizeof(double)]; - memory::monotonic m(buffer.data(), buffer.size()); - auto p1 = m.allocate(100*sizeof(double), alignof(double)); - try { - auto p2 = m.allocate(200*sizeof(double), alignof(double)); - m.deallocate(p2, 200*sizeof(double)); - } catch(std::bad_alloc& e) {std::cout <<"throw "<< e.what() << std::endl;} - m.deallocate(p1, 100*sizeof(double)); -} -{ - alignas(double) std::array buffer; // char buffer[256*sizeof(double)]; - memory::fallback> m(buffer.data(), buffer.size()); // , boost::multi::memory::get_default_resource()); - auto p1 = m.allocate(100*sizeof(double), alignof(double)); - auto p2 = m.allocate(200*sizeof(double), alignof(double)); - m.deallocate(p2, 200*sizeof(double)); - m.deallocate(p1, 100*sizeof(double)); - assert( m.fallbacks() == 1 ); -} -{ - alignas(double) std::array buffer; // char buffer[256*sizeof(double)]; - memory::stack s(buffer.data(), buffer.size()); - auto p1 = s.allocate(1*sizeof(double), alignof(double)); - auto p2 = s.allocate(100*sizeof(double), alignof(double)); - s.deallocate(p2, 100*sizeof(double)); - s.deallocate(p1, 1*sizeof(double)); - assert( s.max_needed() == 101*sizeof(double) ); -} -{ - alignas(double) std::array buffer; // char buffer[256*sizeof(double)]; - memory::fallback> s(buffer.data(), buffer.size()); - auto p1 = s.allocate(10000*sizeof(double), alignof(double)); - s.deallocate(p1, 10000*sizeof(double)); - assert( s.fallbacks() == 1 ); -} -{ - alignas(double) std::array buffer; // char buffer[256*sizeof(double)]; - memory::fallback> s(buffer.data(), buffer.size()); - auto p2 = s.allocate(255*sizeof(double), alignof(double)); - auto p3 = s.allocate(255*sizeof(double), alignof(double)); - s.deallocate(p3, 255*sizeof(double)); - s.deallocate(p2, 255*sizeof(double)); - assert( s.fallbacks() == 1 ); - assert( s.max_needed() == (255+255)*sizeof(double) ); -} -{ - std::size_t guess = 1000; - for(int i = 0; i != 3; ++i) { - std::vector buffer(guess); - memory::fallback> f({buffer.data(), (std::ptrdiff_t)buffer.size()}); - std::vector>> v(10, &f); - std::vector>> w(1000, &f); - cout<<"iteration: "<< i <<"-> fallbacks "<< f.fallbacks() <<" max_needed " << f.max_needed() <> f; // ({buffer.data(), (std::ptrdiff_t)buffer.size()}); // TODO error in nvcc - for(int i = 0; i != 3; ++i) { - std::vector buffer(f.max_needed()); - f = {{buffer.data(), (std::ptrdiff_t)buffer.size()}}; - std::vector>> v(10, &f); - std::vector>> w(1000, &f); - cout<<"iteration: "<< i <<"-> fallbacks "<< f.fallbacks() <<" max_needed " << f.max_needed() <> f; // ({buffer.data(), (std::ptrdiff_t)buffer.size()}); - for(int i = 0; i != 3; ++i) { - std::vector buffer(f.max_needed()); - f = {{buffer.data(), (std::ptrdiff_t)buffer.size()}}; - std::vector>> v(10, &f); - std::vector>> w(1000, &f); - cout<<"iteration: "<< i <<"-> fallbacks "<< f.fallbacks() <<" max_needed " << f.max_needed() <> f; - for(int i = 0; i != 3; ++i) { - std::vector buffer(f.max_needed()); - f = {{buffer.data(), f.max_needed()}}; - std::vector>> v(10, &f); - std::vector>> w(1000, &f); - cout<<"iteration: "<< i <<"-> fallbacks "<< f.fallbacks() <<" max_needed " << f.max_needed() < $0.cpp) && c++ -std=c++17 -Wall -Wextra -Wfatal-errors -D_TEST_BOOST_MULTI_MEMORY_INSTRUMENTED $0.cpp -o $0x && valgrind $0x && rm $0x $0.cpp; exit -#endif -#ifndef BOOST_MULTI_MEMORY_INSTRUMENTED_HPP -#define BOOST_MULTI_MEMORY_INSTRUMENTED_HPP - -#include // max_align_t -#include -#include -#include -#include -#include - -#include - -//#include "../memory/block.hpp" -//#include "../memory/allocator.hpp" - -#include // accumulate -#include -namespace boost{ -namespace multi{ -namespace memory{ - -template< - class MemoryResource = std::pmr::memory_resource, - typename SizeType = std::size_t, - typename VoidPointer = void* -> -class instrumented{ - MemoryResource* back_ = std::pmr::get_default_resource(); - using void_pointer = VoidPointer; - using size_type = SizeType; - std::map blocks_; -public: - instrumented() = default; - instrumented(instrumented const&) = delete; - std::map const& blocks() const{return blocks_;} - auto leak(){ - return std::accumulate( - blocks_.begin(), blocks_.end(), size_type{}, [](auto a, auto&& e){return a+e.second;} - ); - } - typename instrumented::void_pointer - allocate(size_type required_bytes, size_type align = alignof(std::max_align_t)){ - std::cout << "allocating " << required_bytes << std::endl; - auto ret = back_->allocate(required_bytes, align); - blocks_[ret] += required_bytes; - return ret; - } - void deallocate(typename instrumented::void_pointer p, typename instrumented::size_type discarded_bytes, size_type align = alignof(std::max_align_t)){ - std::cout << "deallocating " << discarded_bytes << std::endl; - back_->deallocate(p, discarded_bytes, align); - blocks_[p] -= discarded_bytes; - } -}; -}}} - -#include "../../multi/memory/allocator.hpp" - -namespace boost{ -namespace multi{ -namespace memory{ - -template -using instrumented_allocator = multi::memory::allocator>; - -}}} - -#if _TEST_BOOST_MULTI_MEMORY_INSTRUMENTED - -#include "../../multi/array.hpp" -#include "../memory/monotonic.hpp" - -#include -#include -#include - -namespace multi = boost::multi; -using std::cout; - -int main(){ -{ - multi::memory::instrumented<> im; - auto p1 = im.allocate(1*sizeof(double), alignof(double)); - auto p2 = im.allocate(255*sizeof(double), alignof(double)); - im.deallocate(p2, 255*sizeof(double)); - im.deallocate(p1, 1*sizeof(double)); - assert( im.blocks().size() == 2 ); - assert( not im.leak() ); - { - multi::memory::instrumented<> im; - multi::memory::allocator > A(&im); - double* p = A.allocate(1); - A.construct(p, 8.); - assert( *p == 8. ); - double* arr = A.allocate(255); - A.construct(arr, 81.); - assert( *arr == 81. ); - A.deallocate(arr, 255); - A.deallocate(p, 1); - assert( not im.leak() ); - } - { - multi::memory::instrumented<> im; - multi::memory::allocator > A(&im); - { - std::vector v(A); - v.push_back(99); - v.push_back(10); - v.push_back(12); - v.resize(1); - v.push_back(10); - } - assert( not im.leak() ); - } - { - multi::memory::instrumented<> im; - using alloc = multi::memory::instrumented_allocator; - alloc A(&im); - alloc B(A); - { - multi::static_array arr1({10}, 99., A); - multi::static_array arr2({10, 20}, 99., A); - multi::static_array arr3({10, 20, 30}, 99., B); - multi::static_array brr3(arr3); - } - assert( not im.leak() ); - } - { - multi::memory::instrumented<> im; - multi::memory::allocator > A(&im); - { - multi::array arr1({10}, 99., A); - multi::array arr2({10, 20}, 99., A); - multi::array arr3({10, 20, 30}, 99., A); - } - assert( not im.leak() ); - } - { - multi::memory::instrumented<> im; - { - using alloc = multi::memory::instrumented_allocator; - multi::array A({10, 20, 30}, 99., &im); - multi::array B({10, 20, 30}, 11., &im); - B = std::move(A); assert( empty(A) ); - } - assert( not im.leak() ); - } - std::cout << "-------------------" << std::endl; - { - multi::memory::instrumented<> im; - { - using alloc = multi::memory::instrumented_allocator; - // using alloc = std::allocator; - multi::array arr1({10}, 99., &im); - multi::array arr2({10, 20}, 99., &im); - multi::array arr3({10, 20, 30}, 99., &im); - arr1.reextent({20}); - arr2.reextent({200, 10}); - arr3.reextent({201, 10, 100}); - arr2.clear(); - multi::array brr1 = arr1; - multi::array brr2(arr2); - assert( arr3.num_elements() == 201*10*100 ); - multi::array brr3(std::move(arr3)); - assert( arr3.num_elements() == 0 ); - assert( brr3.num_elements() == 201*10*100 ); - } - assert( not im.leak() ); - } - return 0; -// return 0; -} -} -#endif -#endif - diff --git a/include/multi/memory/monotonic.hpp b/include/multi/memory/monotonic.hpp deleted file mode 100644 index 3442b3ddc..000000000 --- a/include/multi/memory/monotonic.hpp +++ /dev/null @@ -1,165 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_MONOTONIC_HPP_ -#define MULTI_MEMORY_MONOTONIC_HPP_ - -#include "../memory/block.hpp" -#include "../memory/allocator.hpp" - -#include // for max_align_t -#include -#include - -namespace boost { -namespace multi { - -// template auto raw_pointer_cast(T* p) -> T* {return p;} // with the meaning of std::to_address - -namespace memory { - -template -T* align_up(T* ptr, std::size_t bytes = alignof(std::max_align_t)) { -// return -// reinterpret_cast( -// (reinterpret_cast(ptr) + (bytes-1)) -// & ~(bytes-1) -// ) -// ; - using uintptr_t = std::uint64_t; - static_assert( sizeof(uintptr_t) == sizeof(T*), "this function works in 64 bit systems" ); - return reinterpret_cast( bytes * ((reinterpret_cast(ptr) + (bytes - 1)) / bytes) ); -} - -template // TODO(correaa) test with actual fancy ptr -constexpr -Ptr align_up(Ptr ptr, std::size_t bytes = alignof(std::max_align_t)) { - using multi::to_address; - auto p_(to_address(ptr)); -// using multi::raw_pointer_cast; -// auto p_{raw_pointer_cast(p)}; - - static_assert( sizeof(*p_)==1 , "!"); // crash -// auto q_ = reinterpret_cast( -// (reinterpret_cast(p_) + (align-1)) -// & ~(align-1) -// ); - auto q_ = align_up(p_, bytes); - return ptr + std::distance(p_, q_); -// return reinterpret_cast( bytes * ((reinterpret_cast(ptr) + (bytes - 1)) / bytes) ); // maybe using uint64_t and static_assert sizeof(void*) == uint64_t -} - -template -class null_t { - public: - using pointer = Ptr; - using size_type = typename std::pointer_traits::difference_type; - - private: - using void_pointer = typename std::pointer_traits::template rebind; - - public: - template - void_pointer allocate(size_type required_bytes, size_type = AA) { - if(required_bytes > 0) {throw std::bad_alloc{};} - return nullptr; - } - void deallocate(void_pointer p, size_type /*discarded_bytes*/) { - if(p != nullptr) {throw std::bad_alloc{};} - } -}; - -template -class monotonic : protected block { - protected: - using void_pointer = typename std::pointer_traits::template rebind; - - public: - using block::start_; - using block::size_; - using block::block; - typename monotonic::pointer position_ = start_; - void reset() {position_ = this->start_;} - template - typename monotonic::void_pointer allocate( - typename monotonic::size_type required_bytes, - typename monotonic::size_type align = AA // alignof(std::max_align_t) - ) { - auto ret = align_up(this->position_, align); - auto new_position_ = ret + required_bytes; - using std::distance; - if(not this->contains(new_position_-1)) { - throw overflow(required_bytes, this->size_ - distance(start_, position_)); - } - this->position_ = new_position_; - return ret; - } - bool owns(typename monotonic::void_pointer p) const { - return this->contains(static_cast(p)); - } - void deallocate( - typename monotonic::void_pointer p, - typename monotonic::size_type /*discarded_bytes*/ - ) { - if(not owns(p)) {throw std::bad_alloc{};} - } - struct overflow : public std::bad_alloc{ - using size_type = typename monotonic::size_type; - size_type required; - size_type available; - // constexpr auto to_string = [](auto a){return std::to_string(a);}; - std::string msg; - overflow(size_type required, size_type available) - : required{required}, available{available}, - msg{"required "+std::to_string(required)+" while only "+std::to_string(available)+" bytes available"} {} - char const* what() const throw() override {return msg.c_str();} // + std::to_string(required)).c_str();} - }; -}; - -template -using monotonic_allocator = multi::memory::allocator>; - -} // end namespace memory -} // end namespace multi -} // end namespace boost - -#if not __INCLUDE_LEVEL__ // _TEST_BOOST_MULTI_MEMORY_MONOTONIC - -#include "../../multi/array.hpp" - -#include -#include -#include - -namespace multi = boost::multi; -using std::cout; - -int main(){ -{ - multi::memory::null_t mr; - try { - mr.allocate(1*sizeof(double), alignof(double)); - } catch(...) {} -} -{ - alignas(double) std::array buffer; // char buffer[256*sizeof(double)]; - multi::memory::monotonic m(buffer.data(), buffer.size()); - auto p1 = m.allocate(1*sizeof(double), alignof(double)); - auto p2 = m.allocate(255*sizeof(double), alignof(double)); - m.deallocate(p2, 255*sizeof(double)); - m.deallocate(p1, 1*sizeof(double)); - try { - m.deallocate(reinterpret_cast(p1) + 10000, 1*sizeof(double)); - } catch(...){} -} -{ - alignas(double) std::array buffer; // char buffer[300*sizeof(double)]; - multi::memory::monotonic m(buffer.data(), buffer.size()); - multi::memory::monotonic_allocator alloc(&m); - multi::array> A({10, 10}, &m); - multi::array> B({10, 10}, &m); - multi::array> C({10, 10}, &m); -} -} -#endif -#endif // MULTI_MEMORY_MONOTONIC_HPP_ diff --git a/include/multi/memory/stack.hpp b/include/multi/memory/stack.hpp deleted file mode 100644 index dd44b95dc..000000000 --- a/include/multi/memory/stack.hpp +++ /dev/null @@ -1,94 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa - -#ifndef MULTI_MEMORY_STACK_HPP_ -#define MULTI_MEMORY_STACK_HPP_ - -#include "../memory/monotonic.hpp" -#include "../memory/allocator.hpp" - -#include // for max -#include // max_align_t -#include -#include - -namespace boost { -namespace multi { -namespace memory { - -template -class stack : protected monotonic { - private: - std::stack positions_ = {}; - typename stack::size_type total_requested_ = 0; - typename stack::size_type total_discarded_ = 0; - typename stack::size_type max_needed_ = 0; - std::size_t hits_ = 0; - - public: - std::size_t hits() const {return hits_;} - void reset() {monotonic::reset(); positions_.clear();} - typename stack::size_type max_needed() const {return max_needed_;} - - using monotonic::monotonic; - - template - typename stack::void_pointer allocate( - typename stack::size_type required_bytes, - typename stack::size_type align = AA - ) { - total_requested_ += required_bytes; - max_needed_ = std::max(max_needed_, total_requested_ - total_discarded_); - positions_.push(monotonic::template allocate(required_bytes, align)); - ++hits_; - return positions_.top(); - } - void deallocate( - typename stack::void_pointer p, - typename stack::size_type discarded_bytes - ) { - total_discarded_ += discarded_bytes; - monotonic::deallocate(p, discarded_bytes); // positions_.top(), discarded_bytes); - assert( p == positions_.top() && "stack violation" ); - this->position_ -= discarded_bytes; - positions_.pop(); - } -}; - -template -using stack_allocator = multi::memory::allocator>; - -} // end namespace memory -} // end namespace multi -} // end namespace boost - -#if not __INCLUDE_LEVEL__ -#define BOOST_TEST_MODULE "C++ Unit Tests for Multi stack memory resource" -#define BOOST_TEST_DYN_LINK -#include - -#include "../../multi/array.hpp" -#include - -namespace multi = boost::multi; -namespace memory = multi::memory; - -BOOST_AUTO_TEST_CASE(multi_memory_allocator) { - alignas(double) std::array buffer; - memory::stack stck(buffer.data(), buffer.size()); - auto p1 = stck.allocate(1*sizeof(double), alignof(double)); - BOOST_REQUIRE( stck.max_needed() == 1*sizeof(double) ); - - auto p2 = stck.allocate(255*sizeof(double), alignof(double)); - BOOST_REQUIRE( stck.max_needed() == 256*sizeof(double) ); - - stck.deallocate(p2, 255*sizeof(double)); - BOOST_REQUIRE( stck.max_needed() == 256*sizeof(double) ); - - stck.deallocate(p1, 1*sizeof(double)); - BOOST_REQUIRE( stck.max_needed() == 256*sizeof(double) ); - - auto p3 = stck.allocate(100*sizeof(double)); (void)p3; -} -#endif -#endif // MULTI_MEMORY_STACK_HPP_ diff --git a/include/multi/utility.hpp b/include/multi/utility.hpp deleted file mode 100644 index 2a9ce6b3d..000000000 --- a/include/multi/utility.hpp +++ /dev/null @@ -1,525 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa - -#ifndef MULTI_UTILITY_HPP -#define MULTI_UTILITY_HPP -#pragma once - -#include "detail/layout.hpp" - -#include // for std::invoke -#include // for allocator<> -#include // for std::invoke_result - -#if(__cplusplus >= 201703L) -#include // for std::size (in c++17) -#endif - -namespace boost::multi { - -template constexpr bool is_implicitly_convertible_v = std::is_convertible_v; -template constexpr bool is_explicitly_convertible_v = std::is_constructible_v; - -template::value, int> =0> // ::value (not _v) needed by intel's icpc 19 -constexpr auto implicit_cast(From&& r) -> To {return static_cast(r);} // NOLINT(readability-identifier-length) std naming - -template::value and not std::is_convertible::value, int> =0> // ::value (not _v) needed by intel's icpc 19 -constexpr auto explicit_cast(From&& r) -> To {return static_cast(r);} // NOLINT(readability-identifier-length) std naming - -template -struct move_ptr : private std::move_iterator { - using difference_type = typename std::iterator_traits>::difference_type; - using value_type = typename std::iterator_traits>::value_type; - using pointer = Ptr; - using reference = typename std::move_iterator::reference; - using iterator_category = typename std::iterator_traits>::iterator_category; - - template using rebind = std::conditional_t< - std::is_const_v, - typename std::pointer_traits::template rebind, - move_ptr - >; - - using std::move_iterator::move_iterator; - - HD constexpr /*implicit*/ operator Ptr() const {return std::move_iterator::base();} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) decay to lvalue should be easy - HD constexpr auto operator+=(difference_type n) -> move_ptr& {static_cast&>(*this) += n; return *this;} - HD constexpr auto operator-=(difference_type n) -> move_ptr& {static_cast&>(*this) -= n; return *this;} - - HD constexpr auto operator+(difference_type n) const -> move_ptr {move_ptr ret{*this}; ret += n; return ret;} - HD constexpr auto operator-(difference_type n) const -> move_ptr {move_ptr ret{*this}; ret -= n; return ret;} - - HD constexpr auto operator-(move_ptr const& other) const -> difference_type {return static_cast const&>(*this) - static_cast const&>(other);} - - constexpr auto operator*() const -> decltype(auto) {return *static_cast const&>(*this);} - HD constexpr auto operator[](difference_type n) const -> decltype(auto) {return *((*this) + n);} - - HD constexpr auto operator==(move_ptr const& other) const -> bool {return static_cast const&>(*this) == static_cast const&>(other);} - HD constexpr auto operator!=(move_ptr const& other) const -> bool {return static_cast const&>(*this) != static_cast const&>(other);} -}; - -template struct ref_add_const {using type = T const;}; // this is not the same as std::add_const - -template struct ref_add_const {using type = T const ;}; -template struct ref_add_const {using type = T const&;}; -template struct ref_add_const {using type = T const&;}; - -template::reference>> -struct transform_ptr { - using difference_type = typename std::iterator_traits::difference_type; - using value_type = std::decay_t; // typename std::iterator_traits>::value_type; - using pointer = Ptr; - using reference = Ref; - using iterator_category = typename std::iterator_traits::iterator_category; - - template using rebind = - transform_ptr< - std::remove_cv_t, - UF, Ptr, - typename std::conditional< - std::is_const_v, - typename ref_add_const::type, - Ref - >::type - > - ; - - template - constexpr transform_ptr(pointer ptr, UF fun) : p_{ptr}, f_(std::move(fun)) {} - - template - // cppcheck-suppress noExplicitConstructor - constexpr transform_ptr(Other const& other) : p_{other.p_}, f_{other.f_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) TODO(correaa) use conditional explicit idiom here - - constexpr auto functor() const -> UF {return f_;} - constexpr auto base() const -> Ptr const& {return p_;} - constexpr auto operator*() const -> reference { // NOLINT(readability-const-return-type) in case synthesis reference is a `T const` - return std::invoke(f_, *p_); // NOLINT(readability-const-return-type) in case synthesis reference is a `T const` - // return f_(*p_); // NOLINT(readability-const-return-type) in case synthesis reference is a `T const` - } - - constexpr auto operator+=(difference_type n) -> transform_ptr& {p_ += n; return *this;} - constexpr auto operator-=(difference_type n) -> transform_ptr& {p_ -= n; return *this;} - - constexpr auto operator+(difference_type n) const -> transform_ptr {transform_ptr ret{*this}; ret += n; return ret;} - constexpr auto operator-(difference_type n) const -> transform_ptr {transform_ptr ret{*this}; ret -= n; return ret;} - - constexpr auto operator-(transform_ptr const& other) const -> difference_type {return p_ - other.p_;} - - constexpr auto operator[](difference_type n) const -> reference {return *((*this) + n);} - - constexpr auto operator==(transform_ptr const& other) const -> bool {return p_ == other.p_;} - constexpr auto operator!=(transform_ptr const& other) const -> bool {return p_ != other.p_;} - - private: - Ptr p_; - UF f_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) technically this type can be const - - template friend struct transform_ptr; -}; - -template -struct array_traits; - -template -struct array_traits { - using reference = typename Array::reference; - using element = typename Array::element; - using element_ptr = typename Array::element_ptr; - using decay_type = typename Array::decay_type; - using default_allocator_type = typename Array::default_allocator_type; -}; - -template - auto has_rank_aux(T const&) -> std::true_type; -inline auto has_rank_aux(... ) -> std::false_type; - -template struct has_rank : decltype(has_rank_aux(std::declval())){}; - -template struct rank; - -template{}> > -constexpr auto rank_aux(T const&) -> typename T::rank; - -template::value> > -constexpr auto rank_aux(T const&) -> std::integral_constant>; - -template struct rank : decltype(rank_aux(std::declval())) {}; - -#if not defined(__cpp_lib_nonmember_container_access) or __cpp_lib_nonmember_container_access < 201411 -template -constexpr auto size(Container const& con) --> std::make_signed_t { - return static_cast>(con.size());} -#else -#endif - -template{}, int> = 0> // special sfinae trick -constexpr auto stride(Pointer /*ptr*/) -> std::ptrdiff_t {return 1;} - -template{}, int> =0> // special sfinae trick -constexpr auto base(Pointer ptr) -> Pointer {return ptr;} - -template -constexpr auto reinterpret_pointer_cast(U* other) // name taken from thrust::reinterpret_pointer_cast, which is difference from std::reinterpret_pointer_cast(std::shared_ptr) --> decltype(reinterpret_cast(other)) {return reinterpret_cast(other);} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) : unavoidalbe implementation? - -template -constexpr auto size(const T(&/*array*/)[N]) noexcept {return static_cast(N);} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - -template - auto has_get_allocator_aux(T const&) -> std::true_type; -inline auto has_get_allocator_aux(... ) -> std::false_type; - -template -constexpr auto get_allocator(T(&/*array*/)[N]) noexcept -> std::allocator::type>> {return {};} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - -template -constexpr auto get_allocator(T* const& /*t*/) -->decltype(std::allocator::value_type>{}) { - return std::allocator::value_type>{}; } - -template -constexpr auto default_allocator_of(T* /*unused*/) { - return std::allocator::value_type>{}; -} - -template -constexpr -auto to_address(T* const& ptr) -->decltype(ptr) { - return ptr; } - -template -auto has_get_allocator_aux(T const& cont) -> decltype(cont.get_allocator(), std::true_type {}); - -template struct has_get_allocator : decltype(has_get_allocator_aux(std::declval())) {}; - -template // std::common_type_t> -auto common(T1 const& val1, T2 const& val2) -> Ret { - return val1 == val2? - val1: - Ret{} - ; -} - -template - auto has_num_elements_aux(T const& /*array*/)->decltype(std::declval().num_elements() + 1, std::true_type {}); -inline auto has_num_elements_aux(... )->decltype( std::false_type{}); -template struct has_num_elements : decltype(has_num_elements_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -template{}> > -constexpr auto num_elements(A const& arr) -->std::make_signed_t { - return static_cast>(arr.num_elements()); -} - -template - auto has_size_aux(T const& cont) -> decltype(cont.size(), std::true_type {}); -inline auto has_size_aux(... ) -> decltype( std::false_type{}); -template struct has_size : decltype(has_size_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -template - auto has_data_elements_aux(T&& array)->decltype(array.data_elements() + 1, std::true_type {}); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) why +1? -inline auto has_data_elements_aux(... )->decltype( std::false_type{}); -template struct has_data_elements : decltype(has_data_elements_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -template - auto has_base_aux(T&& array)->decltype(array.base() + 1, std::true_type {}); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) why +1? -inline auto has_base_aux(... )->decltype( std::false_type{}); -template struct has_base : decltype(has_base_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -namespace detail { -template - auto has_data_aux(T&& cont) -> decltype(cont.data_elements() + 1, std::true_type {}); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) TODO(correaa) why +1? -inline auto has_data_aux(... ) -> decltype( std::false_type{}); -} // end namespace detail -template struct has_data : decltype(detail::has_data_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -template>::value && !has_data_elements>::value, int> =0> -auto data_elements(Array& arr) {return arr.data();} - -template>::value && !has_data_elements>::value, int> =0> -auto data_elements(Array const& arr) {return arr.data();} - -template::value && has_size::value && has_data::value, int> =0> -constexpr auto num_elements(A const& arr) -> std::make_signed_t { - - return static_cast>(arr.size()); -} - -template{}, int> =0> -constexpr auto data_elements(A const& arr) -->decltype(arr.data_elements()) { - return arr.data_elements(); } - -template> and not has_data_elements>::value && !has_data>::value, int> =0> -constexpr auto data_elements(T& value) -> decltype(&value) {return &value;} - -template struct num_elements_t: std::integral_constant {}; - -template struct num_elements_t : std::integral_constant{})> {}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - -template struct num_elements_t : num_elements_t {}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - -template -constexpr auto num_elements(const T(&/*array*/)[N]) noexcept {return num_elements_t{};} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - -template().data())>>> -auto data_elements(Vector const& vec) -->decltype(vec.data()) { - return vec.data(); } - -template -constexpr auto stride(const T(&/*array*/)[N]) noexcept -> std::ptrdiff_t {return num_elements_t{};} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility - -template -constexpr auto is_compact(const T(&/*t*/)[N]) noexcept -> bool {return true;} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility - -template -constexpr auto offset(const T(&/*t*/)[N]) noexcept -> std::ptrdiff_t {return 0;} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility - -template -[[deprecated("use data_elements instead")]] // this name is bad because when the element belongs to std:: then std::data is picked up by ADL and the -constexpr auto data(T(&array)[N]) noexcept {return data(array[0]);} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - -template -constexpr auto data_elements(T(&array)[N]) noexcept {return data_elements(array[0]);} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - -template - auto has_dimensionality_aux(T const& /*array*/)->decltype(T::rank_v, std::true_type {}); -inline auto has_dimensionality_aux(... )->decltype( std::false_type{}); -template struct has_dimensionality : decltype(has_dimensionality_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -template{}, int> =0> -constexpr auto dimensionality(Container const& /*container*/) -->std::decay_t { - return Container::rank_v;} - -template - auto has_dimensionaliy_member_aux(T const& /*array*/) -> decltype(static_cast(static_cast(T::rank_v)), std::true_type {}); -inline auto has_dimensionaliy_member_aux(... ) -> decltype( std::false_type{}); -template struct has_dimensionality_member : decltype(has_dimensionaliy_member_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -template{}>> -constexpr auto dimensionality(T const&/*, void* = nullptr*/) {return 0;} - -template -constexpr auto dimensionality(T const(&array)[N]) {return 1 + dimensionality(array[0]);} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - -template -inline constexpr auto sizes(T const& /*unused*/) noexcept -> tuple<> {return {};} - -template -constexpr auto sizes(const T(&array)[N]) noexcept { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility -// using std::size; // this line needs c++17 - using multi::size; - return tuple(multi::size(array), multi::sizes(array[0])); -// return tuple_cat(make_tuple(boost::multi::size(t)), sizes(t[0])); -} - -template -constexpr auto base(T(&array)[N]) noexcept { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility - return data_elements(array); -} - -template -constexpr auto base(T(*&array)[N]) noexcept {return base(*array);} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility - -template>> -constexpr auto base(T const* ptr) noexcept {return ptr;} - -template>> -constexpr auto base(T* ptr) noexcept {return ptr;} - -template and std::is_trivial_v, int> =0> -auto base(T& array) {return &array;} - -template -constexpr auto corigin(const T& value) {return &value;} - -template -constexpr auto corigin(const T(&array)[N]) noexcept {return corigin(array[0]);} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for backwards compatibility - -template().extension())> - auto has_extension_aux(T const&) -> std::true_type; -inline auto has_extension_aux(... ) -> std::false_type; -template struct has_extension : decltype(has_extension_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -template::value>> -auto extension(Container const& cont) // TODO(correaa) consider "extent" -->decltype(multi::extension_t>(0, static_cast>(size(cont)))) { - return multi::extension_t>(0, static_cast>(size(cont))); } - -template().shape())> - auto has_shape_aux(T const&) -> std::true_type; -inline auto has_shape_aux(... ) -> std::false_type; - -template struct has_shape : decltype(has_shape_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) trick - -template().extensions())> - auto has_extensions_aux(T const&) -> std::true_type; -inline auto has_extensions_aux(... ) -> std::false_type; - -template struct has_extensions : decltype(has_extensions_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) trick - -template::value, int> =0> -[[nodiscard]] auto extensions(T const& array) -> std::decay_\ -t { - return array.extensions(); } - -template -constexpr auto extensions_aux2(BoostMultiArray const& arr, std::index_sequence /*012*/) { - return boost::multi::extensions_t( - boost::multi::iextension{static_cast(arr.index_bases()[I]), static_cast(arr.index_bases()[I]) + static_cast(arr.shape()[I])} ... // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - ); -} - -template::value && !has_extensions::value, int> =0> -constexpr auto extensions(BoostMultiArray const& array) { - return extensions_aux2(array, std::make_index_sequence{}); -} - -template::value && !has_shape::value, int> =0> -constexpr auto extensions(T const& /*unused*/) -> multi::layout_t<0>::extensions_type {return {};} - -template -constexpr auto extensions(T(&array)[N]) { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : for backwards compatibility - return index_extension{N}*extensions(array[0]); -} - -template -struct extensions_aux { - template - static auto call(T const& array) { - return tuple_cat(std::make_tuple(array.extension()), extensions(array)); - } -}; - -template<> struct extensions_aux<0> { - template static auto call(T const& /*unused*/){return std::make_tuple();} -}; - -template -auto extensions(T const& array) { - return extensions_aux::call(array); -} - -template struct extensions_t_aux; - -template auto extensions_me(T2 const& array) { - return extensions_t_aux::call(array); -} - -template struct extension_t_aux { - static auto call(T1 const& /*unused*/) {return std::make_tuple();} - template - static auto call(T2 const& array) {return tuple_cat(std::make_tuple(array.extension()), extensions_me(*begin(array)));} -}; - -template().layout())> - auto has_layout_member_aux(T const&) -> std::true_type; -inline auto has_layout_member_aux(... ) -> std::false_type; - -template -struct has_layout_member : decltype(has_layout_member_aux(std::declval())) {}; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg,cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) - -template{}> > -auto layout(T const& array) -->decltype(array.layout()) { - return array.layout(); } - -template{}> > -auto layout(T const& /*unused*/) -> layout_t<0> {return {};} - -template -constexpr auto layout(T(&array)[N]) {return multi::layout_t> {multi::extensions(array)};} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): for backward compatibility - -template -constexpr auto strides(T(&array)[N]) {return layout(array).strides();} // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): for backward compatibility - -template -struct array_traits> { - static constexpr auto dimensionality() -> dimensionality_type {return 1;} - - using reference = T&; - using value_type = std::decay_t; - using pointer = T*; - using element = value_type; - using element_ptr = pointer; - using decay_type = std::array; -}; - -template -struct array_traits, N>> { - static constexpr auto dimensionality() -> dimensionality_type {return 1 + array_traits>::dimensionality();} - - using reference = std::array&; - using value_type = std::array, M>; - using pointer = std::array*; - using element = typename array_traits>::element; - using element_ptr = typename array_traits>::element; - using decay_type = std::array; -}; - -template constexpr auto data_elements(std::array & arr) noexcept {return arr.data();} -template constexpr auto data_elements(std::array, N>& arr) noexcept {return data_elements(arr[0]);} - -template constexpr auto data_elements(std::array const& arr) noexcept {return arr.data();} -template constexpr auto data_elements(std::array, N> const& arr) noexcept {return data_elements(arr[0]);} - -template constexpr auto data_elements(std::array && arr) noexcept {return arr.data();} - -template -constexpr auto data_elements(std::array, N>&& arr) noexcept {return data_elements(arr[0]);} - -template constexpr auto num_elements(std::array const& /*unused*/) noexcept --> std::ptrdiff_t{return N;} - -template -constexpr auto num_elements(std::array, N> const& arr) --> std::ptrdiff_t {return static_cast(N)*num_elements(arr[0]);} - -template -constexpr auto dimensionality(std::array const& /*unused*/) -> boost::multi::dimensionality_type {return 1;} - -template -constexpr auto dimensionality(std::array, N> const& arr) -> boost::multi::dimensionality_type { - return 1 + dimensionality(arr[0]); -} - -#if (__cplusplus < 201703L) -// this conflicts with std::size in nvcc 11 and c++17 -template -constexpr auto size(std::array const& /*arr*/) { - return multi::size_type{N}; -} -#endif - -template -constexpr auto extensions(std::array const& /*arr*/) { - return multi::extensions_t<1>{multi::index_extension(0, N)}; -} - -template -auto extensions(std::array, M> const& arr) { - return multi::iextension{M}*extensions(arr[0]); -} - -template -constexpr auto stride(std::array const& /*arr*/) { - return static_cast(1); // multi::stride_type? -} - -template -constexpr auto stride(std::array, M> const& arr) { - return num_elements(arr[0]); -} - -template -constexpr auto layout(std::array const& arr) { - return multi::layout_t>::dimensionality()>{multi::extensions(arr)}; -} - -} // end namespace boost::multi -#endif diff --git a/include/multi/utility/const_iterator.hpp b/include/multi/utility/const_iterator.hpp deleted file mode 100644 index 56e63dde5..000000000 --- a/include/multi/utility/const_iterator.hpp +++ /dev/null @@ -1,131 +0,0 @@ -#ifdef COMPILATION_INSTRUCTIONS// -*-indent-tabs-mode:t;tab-width:4;c-basic-offset:4;truncate-lines:1-*- -$CXX $0 -o $0x -DBOOST_TEST_DYN_LINK -lboost_unit_test_framework&&$0x&&rm $0x;exit -#endif - -#include // iterator_traits - -namespace boost{ -namespace multi{ - -template -struct reference_traits{ - using reference = Reference; - using rebind_const = std::add_const_t; -}; - -template -struct reference_traits{ - using reference = T&; - using rebind_const = std::add_const_t; -}; - -template -class const_iterator : Iter{ -// see notes in https://en.cppreference.com/w/cpp/iterator/move_iterator - public: - using base_type = Iter; - using iterator_type = Iter; - using iterator_category = typename std::iterator_traits::iterator_category; - using iterator_concept = std::input_iterator_tag; - using value_type = typename std::iterator_traits::value_type; - using difference_type = typename std::iterator_traits::difference_type; - using pointer = Iter; - using reference = typename multi::reference_traits::reference>::rebind_const; - - public: - constexpr const_iterator() = default; // (1) - constexpr explicit const_iterator(iterator_type x) : Iter{x} {} // (2) - template // (3) - constexpr explicit const_iterator(const_iterator const& o) : Iter{o.base()} {} - template - constexpr const_iterator& operator=(const_iterator const& o) { - static_cast(*this)=o.base(); - return *this; - } - constexpr base_type base() const {return static_cast(*this);} -// https://en.cppreference.com/w/cpp/iterator/move_iterator/operator* - reference operator*() const {return *static_cast(*this);} - constexpr pointer operator->() const {return &*static_cast(*this);} -// https://en.cppreference.com/w/cpp/iterator/move_iterator/operator_at - constexpr reference operator[](difference_type n) const {return static_cast(*this)[n];} -// https://en.cppreference.com/w/cpp/iterator/move_iterator/operator_arith - constexpr const_iterator& operator++(){return ++static_cast(*this), *this;} //(1) - constexpr const_iterator& operator--(){return --static_cast(*this), *this;} //(2) - constexpr const_iterator operator++(int){return const_iterator{static_cast(*this)++};}//(3) - constexpr const_iterator operator--(int){return const_iterator{static_cast(*this)--};}//(4) - constexpr const_iterator operator+(difference_type n) const{ //(5) - return const_iterator{static_cast(*this)+n}; - } - constexpr const_iterator operator-(difference_type n) const{ //(6) - return const_iterator{static_cast(*this)-n}; - } - constexpr const_iterator& operator+=(difference_type n){ //(7) - return static_cast(*this)+=n, *this; - } - constexpr const_iterator& operator-=(difference_type n){ //(8) - return static_cast(*this)-=n, *this; - } - template::value_type, typename std::iterator_traits::value_type>{} and - std::is_assignable{} and - not std::is_assignable::reference, typename std::iterator_traits::value_type>{}, int - > =0> - operator Other() const{return base();} - using rebind_const = const_iterator; -}; - -template constexpr bool operator==(const_iterator const& lhs, const_iterator const& rhs){return lhs.base()==rhs.base();} //(1) -template constexpr bool operator!=(const_iterator const& lhs, const_iterator const& rhs){return lhs.base()!=rhs.base();} //(2) -template constexpr bool operator< (const_iterator const& lhs, const_iterator const& rhs){return lhs.base()< rhs.base();} //(3) -template constexpr bool operator<=(const_iterator const& lhs, const_iterator const& rhs){return lhs.base()<=rhs.base();} //(4) -template constexpr bool operator> (const_iterator const& lhs, const_iterator const& rhs){return lhs.base()> rhs.base();} //(5) -template constexpr bool operator>=(const_iterator const& lhs, const_iterator const& rhs){return lhs.base()>=rhs.base();} //(6) -// TODO three way comparison for C++20 - -template -const_iterator make_const_iterator(It it){return const_iterator{it};} - -template ,typename Enable = void> -struct iterator_traits : Base{ - using rebind_const = multi::const_iterator; -}; - -template -struct iterator_traits : Base{ - using rebind_const = typename Iterator::rebind_const; -}; - -template -struct iterator_traits : std::iterator_traits{ - using rebind_const = T const*; -}; - -}} - -#if not __INCLUDE_LEVEL__ // TEST BELOW - -#define BOOST_TEST_MODULE test const_iterator -#ifdef BOOST_TEST_DYN_LINK -#include -#else -#include -#endif - -#include - -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(sematics) { - std::vector v(5, 9); - std::vector::iterator it = v.begin(); - *it += 1; - BOOST_REQUIRE(v[0] == 10 ); - - static_assert( std::is_same::rebind_const, double const*>{}, "" ); - static_assert( std::is_same::iterator>::rebind_const, multi::const_iterator::iterator> >{}, "" ); - - std::vector::const_iterator cit = multi::make_const_iterator(v.begin()); (void)cit; -} - -#endif - diff --git a/meta/libraries.json b/meta/libraries.json new file mode 100644 index 000000000..42e5c258e --- /dev/null +++ b/meta/libraries.json @@ -0,0 +1,16 @@ +{ + "key": "multi", + "name": "Multi", + "authors": [ + "Alfredo Correa" + ], + "maintainers": [ + "Alfredo Correa " + ], + "description": "Multi is a modern C++ library that provides access and manipulation of data in multidimensional arrays, for both CPU and GPU memory.", + "category": [ + "Containers", + "Math" + ], + "cxxstd": "17" +} diff --git a/mull.yml b/mull.yml new file mode 100644 index 000000000..0186b8e3b --- /dev/null +++ b/mull.yml @@ -0,0 +1,56 @@ +mutators: + - cxx_add_assign_to_sub_assign # Replaces += with -= + - cxx_add_to_sub # Replaces + with - + - cxx_and_assign_to_or_assign # Replaces &= with |= + - cxx_and_to_or # Replaces & with | + - cxx_assign_const # Replaces `a = b` with `a = 42` + - cxx_bitwise_not_to_noop # Replaces ~x with x + - cxx_div_assign_to_mul_assign # Replaces /= with *= + - cxx_div_to_mul # Replaces / with * + - cxx_eq_to_ne # Replaces == with != + - cxx_ge_to_gt # Replaces >= with > + - cxx_ge_to_lt # Replaces >= with < + - cxx_gt_to_ge # Replaces > with >= + - cxx_gt_to_le # Replaces > with <= + - cxx_init_const # Replaces ‘T a = b’ with ‘T a = 42’ + - cxx_le_to_gt # Replaces <= with > + - cxx_le_to_lt # Replaces <= with < + - cxx_logical_and_to_or # Replaces && with || + - cxx_logical_or_to_and # Replaces || with && # The module was corrupted by 'cxx_logical_or_to_and' mutator. + - cxx_lshift_assign_to_rshift_assign # Replaces <<= with >>= + - cxx_lshift_to_rshift # Replaces << with >> + - cxx_lt_to_ge # Replaces < with >= + - cxx_lt_to_le # Replaces < with <= # TODO(correaa) investigate why this mutant survives with the range.contains function + - cxx_minus_to_noop # Replaces -x with x + - cxx_mul_assign_to_div_assign # Replaces *= with /= + - cxx_mul_to_div # Replaces * with / + - cxx_ne_to_eq # Replaces != with == + - cxx_or_assign_to_and_assign # Replaces |= with &= + - cxx_or_to_and # Replaces | with & + - cxx_post_dec_to_post_inc # Replaces x– with x++ + - cxx_post_inc_to_post_dec # Replaces x++ with x– + - cxx_pre_dec_to_pre_inc # Replaces –x with ++x + - cxx_pre_inc_to_pre_dec # Replaces ++x with –x + - cxx_rem_assign_to_div_assign # Replaces %= with /= + - cxx_rem_to_div # Replaces % with / + - cxx_remove_negation # Replaces !a with a + - cxx_remove_void_call # Removes calls to a function returning void + - cxx_replace_scalar_call # Replaces call to a function with 42 + - cxx_rshift_assign_to_lshift_assign # Replaces >>= with <<= + - cxx_rshift_to_lshift # Replaces << with >> + - cxx_sub_assign_to_add_assign # Replaces -= with += + - cxx_sub_to_add # Replaces - with + + - cxx_xor_assign_to_or_assign # Replaces ^= with |= + - cxx_xor_to_or # Replaces ^ with | + - negate_mutator + - scalar_value_mutator +ignoreMutators: + - cxx_logical_or_to_and # bug in mull 16 + - cxx_logical_and_to_or # bug in mull 16 + - scalar_value_mutator # bug in mull 17 +timeout: 20000 # in milliseconds +# debug: +# slowIRVerification: true +quiet: true +excludePaths: + - .*test.* diff --git a/pre-push b/pre-push index 664429800..494d53c2b 100755 --- a/pre-push +++ b/pre-push @@ -1,23 +1,89 @@ #!/bin/bash -# -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -# sudo apt install ccache clang clang-tidy cmake cppcheck g++ git lcov libblas-dev pkg-config libfftw3-dev libboost-test-dev libboost-timer-dev make ninja-build valgrind +# sudo dpkg --add-architecture i386 +# sudo apt install ccache clang clang-tidy cmake cppcheck g++ git iwyu lcov libblas-dev pkg-config libfftw3-dev libboost-test-dev libboost-timer-dev libopenblas-dev libtbb-dev make ninja-build valgrind libboost-test-dev:i386 libboost-timer-dev:i386 # sudo dnf install boost-devel blas-devel ccache clang clang-tools-extra cmake cppcheck fftw-devel git lcov libasan liblas-devel libubsan ninja-build valgrind -# install circle # mkdir -p $HOME/bin && wget https://www.circle-lang.org/linux/build_latest.tgz -P $HOME/tmp/ && tar -zxvf $HOME/tmp/build_latest.tgz --directory $HOME/bin/ && $HOME/bin/circle --version -# install nvc++ # ($ echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | sudo tee /etc/apt/sources.list.d/nvhpc.list) && sudo apt-get updatesudo apt-get update && sudo apt-get install nvhpc-22-7 -# # sudo yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && sudo yum install -y nvhpc-cuda-multi-23.1 - - (mkdir -p .build.clang++ && cd .build.clang++ && CXX=clang++ cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-Wfatal-errors" -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ctest -j 12 --output-on-failure) || exit 666 - (mkdir -p .build.nvcc && echo ".build.nvcc" && cd .build.nvcc && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc -DCMAKE_CUDA_ARCHITECTURES=61 -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" -DCMAKE_CUDA_COMPILER_LAUNCHER="ccache" && cmake --build . && ctest -j 12 --output-on-failure) || exit 666 -#(mkdir -p .build.hip && echo ".build.hip" && cd .build.hip && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DENABLE_HIP=1 -DCMAKE_HIP_ARCHITECTURES=gfx90a && cmake --build . ) || exit 666 - (mkdir -p .build.g++-.sani-check-cov && cd .build.g++-.sani-check-cov && CXX=g++ cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;--suppress=missingIncludeSystem;--inline-suppr;--std=c++17;--check-config;--error-exitcode=1" -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1 -Wfatal-errors -fsanitize=address,undefined,pointer-compare,pointer-subtract,float-divide-by-zero -fno-sanitize-recover=all --coverage -lgcov -fno-inline -fno-inline-small-functions -fno-default-inline" -DCMAKE_EXE_LINKER_FLAGS="-lgcov --coverage" -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ASAN_OPTIONS="new_delete_type_mismatch=0" ctest -j 12 --output-on-failure -T Test && lcov --directory . --capture --output-file coverage.info && lcov --remove coverage.info '/usr/*' --output-file coverage.info && lcov --list coverage.info && genhtml coverage.info) || exit 666 -#(mkdir -p .build.circle && cd .build.circle && CXX="$HOME/bin/circle" cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_CIRCLE=1 -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ctest -j 12 --output-on-failure) || exit 666 - (mkdir -p .build.clang++.sani-tidy && cd .build.clang++.sani-tidy && CXX=clang++ cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CLANG_TIDY="clang-tidy" -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1 -D_LIBCPP_DEBUG=1 -Wfatal-errors -fsanitize=address,undefined,pointer-compare,pointer-subtract,float-divide-by-zero -fno-sanitize-recover=all" -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ASAN_OPTIONS="new_delete_type_mismatch=0" ctest -j 12 --output-on-failure) || exit 666 -#(mkdir -p .build.icpc && cd .build.icpc && CXX=/home/correaa/intel/oneapi/compiler/latest/linux/bin/intel64/icpc cmake .. --GNinja DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ctest -j 12 --output-on-failure) || exit 666 -#(mkdir -p .build.icpx && cd .build.icpx && CXX=/opt/intel/oneapi/compiler/latest/linux/bin/icpx cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ctest -j 12 --output-on-failure) || exit 666 -#(mkdir -p .build.nvc++ && cd .build.nvc++ && CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2023/compilers/bin/nvc++ cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ctest -j 12 --output-on-failure) || exit 666 - (mkdir -p .build.g++.anlys-std20-memchk && cd .build.g++.anlys-std20-memchk && CXX=g++ cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 `#-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="iwyu"` -DCMAKE_CXX_FLAGS="-fanalyzer -Wno-analyzer-null-dereference -Wno-analyzer-possible-null-dereference -Wno-analyzer-malloc-leak -Wno-analyzer-use-of-uninitialized-value -Wno-analyzer-use-after-free" -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ctest -j 12 --output-on-failure --overwrite MemoryCheckCommandOptions="-q --tool=memcheck --leak-check=yes --num-callers=51 --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all" -T memcheck) || exit 666 - (mkdir -p .build.g++-release && cd .build.g++-release && CXX=g++ cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER="ccache" && cmake --build . && ctest -j 1 --output-on-failure -T Test) || exit 666 +# install circle # mkdir -p $HOME/bin && wget https://www.circle-lang.org/linux/build_latest.tgz -P $HOME/tmp/ && tar zxvf $HOME/tmp/build_latest.tgz --directory $HOME/bin/ && $HOME/bin/circle --version +# install nvc++ +# on Ubuntu: +# curl https://developer.download.nvidia.com/hpc-sdk/ubuntu/DEB-GPG-KEY-NVIDIA-HPC-SDK | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-hpcsdk-archive-keyring.gpg +# echo 'deb [signed-by=/usr/share/keyrings/nvidia-hpcsdk-archive-keyring.gpg] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | sudo tee /etc/apt/sources.list.d/nvhpc.list +# sudo apt-get update -y +# sudo apt-get install -y nvhpc +# on Fedora: +# sudo yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && sudo yum install -y nvhpc-cuda-multi-24.5 +# install oneAPI +# wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null +# echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list +# sudo apt update +# sudo apt install intel-hpckit +# brew install llvm@16 + +set -x # echo on + +export CMAKE_GENERATOR=Ninja + +export CMAKE_CUDA_COMPILER_LAUNCHER="ccache" +export CMAKE_CXX_COMPILER_LAUNCHER="ccache" +export CMAKE_COMPILE_WARNING_AS_ERROR="ON" +export CMAKE_BUILD_PARALLEL_LEVEL=8 +export CTEST_PARALLEL_LEVEL=4 + +if [ -z "$1" ]; then +echo "no arg" +else +CMT="--target $1" +CTR="--verbose -R $1" +fi + +if [[ $(uname -m) != 'arm64' ]]; then + +export PMIX_MCA_gds=hash +export UBSAN_OPTIONS=print_stacktrack=1 + + (CXX=clang++ cmake -S . -B .build.clang++-rel -DCMAKE_BUILD_TYPE=Release && cmake --build .build.clang++-rel $CMT && (ctest --test-dir .build.clang++-rel $CTR || ctest --test-dir .build.clang++-rel --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=g++ cmake -S . -B .build.g++ -DCMAKE_BUILD_TYPE=Debug && cmake --build .build.g++ $CMT && (ctest --test-dir .build.g++ $CTR || ctest --test-dir .build.g++ --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=clang++-16 cmake -S . -B .build.clang++-16 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=20 && cmake --build .build.clang++-16 $CMT && (ctest --test-dir .build.clang++-16 $CTR || ctest --test-dir .build.clang++-16 --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=clang++ CXXFLAGS=-Weverything cmake -S . -B .build.clang++ -DCMAKE_BUILD_TYPE=Debug && cmake --build .build.clang++ $CMT && (ctest --test-dir .build.clang++ $CTR || ctest --test-dir .build.clang++ --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=clang++ cmake -S . -B .build.clang++.c++23 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=23 && cmake --build .build.clang++.c++23 $CMT && (ctest --test-dir .build.clang++.c++23 $CTR || ctest --test-dir .build.clang++.c++23 --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=g++ cmake -S . -B .build.g++.c++23 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=23 && cmake --build .build.g++.c++23 $CMT && (ctest --test-dir .build.g++.c++23 $CTR || ctest --test-dir .build.g++.c++23 --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=clang++ cmake -S . -B .build.clang++ -DCMAKE_BUILD_TYPE=Debug && cmake --build .build.clang++ $CMT && (ctest --test-dir .build.clang++ $CTR || ctest --test-dir .build.clang++ --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=clang++ cmake -S . -B .build.clang++.libcpp -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=23 -DCMAKE_CXX_FLAGS="-stdlib=libc++" && cmake --build .build.clang++.libcpp $CMT && (ctest --test-dir .build.clang++.libcpp $CTR || ctest --test-dir .build.clang++.libcpp --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=g++ cmake -S . -B .build.g++-rel -DCMAKE_BUILD_TYPE=Release -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=TRUE && cmake --build .build.g++-rel $CMT && (ctest --test-dir .build.g++-rel $CTR || ctest --test-dir .build.g++-rel --rerun-failed --output-on-failure $CTR) ) || exit 666 + +if [[ $(uname -m) != 'aarch64' ]]; then + clang-format-19 --dry-run -Werror include/**/*.hpp test/*.cpp + +#(CXX="$HOME/bin/circle" cmake -S . -B .build.circle -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=20 -DDISABLE_MPI=1 -DCMAKE_CXX_EXTENSIONS=OFF -DENABLE_CIRCLE=1 -DCMAKE_THREAD_LIBS_INIT="-lpthread" && cmake --build .build.circle $CMT && (ctest --test-dir .build.circle $CTR || ctest --test-dir .build.circle --rerun-failed --output-on-failure $CTR)) || exit 666 +#( cmake -S . -B .build.nvcc.relax -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=20 -DENABLE_CUDA=1 -DCMAKE_CUDA_COMPILER=nvcc -DCMAKE_CUDA_FLAGS="--expt-relaxed-constexpr --threads 2" -DCMAKE_CUDA_HOST_COMPILER=g++ -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CUDA_ARCHITECTURE=native && cmake --build .build.nvcc.relax $CMT && (ctest --test-dir .build.nvcc.relax $CTR || ctest --test-dir .build.nvcc.relax --rerun-failed --output-on-failure $CTR)) || exit 666 + ( cmake -S . -B .build.nvcc -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 -DENABLE_CUDA=1 -DCMAKE_CUDA_COMPILER=nvcc -DCMAKE_CUDA_FLAGS="--threads 2" -DCMAKE_CUDA_HOST_COMPILER=g++ -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CUDA_ARCHITECTURES=native && cmake --build .build.nvcc $CMT && (ctest --test-dir .build.nvcc $CTR || ctest --test-dir .build.nvcc --rerun-failed --output-on-failure $CTR)) || exit 666 +#(source /opt/intel/oneapi/setvars.sh && CXX=/opt/intel/oneapi/compiler/latest/bin/icpx cmake -S . -B .build.icpx -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 && cmake --build .build.icpx $CMT && (ctest --test-dir .build.icpx $CTR || ctest --test-dir .build.icpx --rerun-failed --output-on-failure $CTR)) || exit 666 + (CXX=/opt/nvidia/hpc_sdk/Linux_x86_64/2025/compilers/bin/nvc++ cmake -S . -B .build.nvc++ -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17 -DBLA_PREFER_PKGCONFIG=ON -DCMAKE_CXX_COMPILER=/opt/nvidia/hpc_sdk/Linux_x86_64/2025/compilers/bin/nvc++ && cmake --build .build.nvc++ $CMT && (ctest --test-dir .build.nvc++ $CTR || ctest --test-dir .build.nvc++ --rerun-failed --output-on-failure $CTR)) || exit 666 + ( cmake -S . -B .build.culang -DCMAKE_BUILD_TYPE=Release -DENABLE_CUDA=1 -DCMAKE_CUDA_COMPILER=clang++-19 -DCMAKE_CUDA_HOST_COMPILER=clang++-19 -DCMAKE_CXX_COMPILER=clang++-19 && cmake --build .build.culang $CMT && (ctest --test-dir .build.culang $CTR || ctest --test-dir .build.culang --rerun-failed --output-on-failure $CTR)) || exit 666 +fi + + (CXX=g++ cmake -S . -B .build.g++.plus -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=23 -DDISABLE_MPI=1 -DBLA_VENDOR=OpenBLAS `#-DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;--suppress=missingIncludeSystem;--inline-suppr;--std=c++17;--checkers-report=cppcheck.report;--error-exitcode=1"` -DCMAKE_CXX_FLAGS="-D_GLIBCXX_DEBUG=1" -DCMAKE_CXX_FLAGS="-DRUNNING_ON_VALGRIND -fconcepts-diagnostics-depth=4 -fimplicit-constexpr -Wlto-type-mismatch -Wodr -fanalyzer -Wno-analyzer-null-dereference -Wno-analyzer-possible-null-dereference -Wno-analyzer-malloc-leak -Wno-analyzer-use-of-uninitialized-value -Wno-analyzer-use-after-free" && cmake --build .build.g++.plus $CMT && (PMIX_MCA_gds=hash ctest --test-dir .build.g++.plus --output-on-failure --rerun-failed --overwrite MemoryCheckCommandOptions=" --trace-children=yes --leak-check=full --track-origins=yes --show-leak-kinds=all --gen-suppressions=all --error-exitcode=1" --overwrite MemoryCheckSuppressionFile=$PWD/.valgrind-suppressions -T memcheck $CTR) ) || exit 666 + (CXX=clang++ cmake -S . -B .build.clang++.plus -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=20 -DBLA_VENDOR=Generic -DCMAKE_CXX_CLANG_TIDY="clang-tidy" -DCMAKE_CXX_FLAGS="-g -fsanitize=undefined -fno-sanitize-recover=undefined -DUBSAN=1" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=undefined" `#-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="iwyu;-Xiwyu;--mapping_file=/home/correaa/boost-multi/.iwyu-test.imp;-Xiwyu;--no_fwd_decls"` && cmake --build .build.clang++.plus $CMT && ( ctest --test-dir .build.clang++.plus $CTR || ctest --parallel 1 -rerun-failed --output-on-failure $CTR)) || exit 666 + +## sudo setfacl --modify user:correaa:rw /var/run/docker.sock +# gitlab-runner exec docker rocm + +else +# brew install boost ccache clang-tidy cmake cppcheck cpplint ninja llvm@19 llvm@16 +# ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy" +# git clone thrust; cd thrust; mkdir build; cd build; cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME -DTHRUST_ENABLE_HEADER_TESTING=OFF -DTHRUST_ENABLE_TESTING=OFF -DTHRUST_ENABLE_EXAMPLES=OFF +export CMAKE_PREFIX_PATH=$HOME/lib/cmake/thrust +#export Thurst_DIR=/Users/correatedesco1/include/thrust/cmake + (CXX=c++ CXXFLAGS="-fexperimental-library" cmake -S . -B .build.c++.std23 -DCMAKE_BUILD_TYPE=Debug `#-DOpenMP_ROOT=/opt/homebrew/opt/libomp` -DCMAKE_CXX_STANDARD=23 && cmake --build .build.c++.std23 $CMT && (ctest --test-dir .build.c++.std23 $CTR || ctest --verbose --test-dir .build.c++.std23 --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=c++ cmake -S . -B .build.c++.asan -DCMAKE_BUILD_TYPE=Release -DOpenMP_ROOT=/opt/homebrew/opt/libomp -DCMAKE_CXX_STANDARD=20 -DCMAKE_CXX_FLAGS="-D_LIBCPP_DEBUG=1 -fsanitize=undefined -fsanitize=pointer-overflow -fsanitize-trap=pointer-overflow -fsanitize=address -fno-omit-frame-pointer -fsanitize=signed-integer-overflow" && cmake --build .build.c++.asan $CMT && ASAN_OPTIONS="new_delete_type_mismatch=1" ctest --test-dir .build.c++.asan $CTR --output-on-failure ) || exit 666 + (CXX=c++ cmake -S . -B .build.c++.m32 -DCMAKE_BUILD_TYPE=Release -DOpenMP_ROOT=/opt/homebrew/opt/libomp -DCMAKE_CXX_FLAGS="-m32" && cmake --build .build.c++.m32 $CMT && ctest --test-dir .build.c++.m32 $CTR --output-on-failure ) || exit 666 +#(CXX=c++ cmake -S . -B .build.c++.tidy -DCMAKE_BUILD_TYPE=Release -DOpenMP_ROOT=/opt/homebrew/opt/libomp -DCMAKE_CXX_CLANG_TIDY=clang-tidy && cmake --build .build.c++.tidy $CMT && ctest --test-dir .build.c++.tidy $CTR --output-on-failure ) || exit 666 + (CXX=g++-15 cmake -S . -B .build.g++-15 -DCMAKE_BUILD_TYPE=Release -DOpenMP_ROOT=/opt/homebrew/opt/libomp -DCMAKE_CXX_STANDARD=23 -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=TRUE -DCMAKE_CXX_FLAGS="-D_GLIBCXX_ASSERTIONS -D_GLIBCXX_DEBUG=1 -D_FORTIFY_SOURCE=3" && cmake --build .build.g++-15 $CMT && (ctest --test-dir .build.g++-15 $CTR || ctest --rerun-failed --output-on-failure $CTR) ) || exit 666 + (CXX=c++ cmake -S . -B .build.c++.tidy -DCMAKE_BUILD_TYPE=Release -DOpenMP_ROOT=/opt/homebrew/opt/libomp -DCMAKE_CXX_CLANG_TIDY=clang-tidy -DCMAKE_CXX_CPPLINT="cpplint;--quiet" -DCMAKE_CXX_FLAGS="-DCPPCHECK=1" -DCMAKE_CXX_CPPCHECK="cppcheck;-DCPPCHECK=1;--enable=all;--suppress=preprocessorErrorDirective;--suppress=missingIncludeSystem;--checkers-report=cppcheck.report;--suppress=checkersReport;--suppress=syntaxError;--suppress=unmatchedSuppression;--suppress=unusedFunction;--suppress=constParameterReference;--suppress=normalCheckLevelMaxBranches;--inline-suppr;--std=c++17;--error-exitcode=1" -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="include-what-you-use;-Xiwyu;--mapping_file=/Users/correatedesco1/boost-multi/.iwyu-test.imp;-Xiwyu;--no_fwd_decls;-Xiwyu;-Werror" && cmake --build .build.c++.tidy $CMT --verbose --parallel && (ctest --test-dir .build.c++.tidy -j 12 $CTR || ctest --test-dir .build.c++.tidy --rerun-failed --output-on-failure $CTR)) || exit 666 + /opt/homebrew/Cellar/llvm@19/19.1.7/bin/clang-format --dry-run -Werror include/**/*.hpp test/*.cpp #(mkdir -p .build.clang++.iwyu && cd .build.clang++.iwyu && CXX=clang++ cmake .. -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="iwyu" && make -j 10 && ctest -j 12 --output-on-failure) || exit # TODO(correaa) make cppcheck work for all the code #(find . -name '*.hpp' -exec cppcheck --enable=all --inline-suppr --suppress=unmatchedSuppression:{} --suppress=syntaxError --suppress=missingInclude --suppress=missingIncludeSystem --suppress=preprocessorErrorDirective --suppress=syntaxError --suppress=unusedFunction --suppress=arithOperationsOnVoidPointer --suppress=sizeofDereferencedVoidPointer -D__align__ -DCUDARTAPI --language=c++ --std=c++17 --error-exitcode=666 --suppress=unmatchedSuppression {} \;) || exit +fi + +#fuser --kill /dev/nvidia-uvm; while fuser --silent /dev/nvidia-uvm; do sleep 1; done; modprobe -r nvidia_uvm && modprobe nvidia_uvm diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..8f39eeb97 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,20 @@ +sonar.projectKey=correaa_boost-multi +sonar.organization=correaa + +# This is the name and version displayed in the SonarCloud UI. +#sonar.projectName=boost-multi +#sonar.projectVersion=1.0 + + +# Path is relativep to the sonar-project.properties file. Replace "\" by "/" on Windows. +#sonar.sources=. + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8 + +# sonar.issue.ignore.all=true + +# Cognitive Complexity of functions should not be too high cpp:S3776 +sonar.issue.ignore.specific.rule=cpp:S3776 +# "explicit" should be used on single-parameter constructors and conversion operators cpp:S1709 +sonar.issue.ignore.specific.rule=cpp:S1709 diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index 10f3c3d80..000000000 --- a/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build* - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b281d87fc..1fcab3213 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,883 +1,424 @@ -#[=[Multi Test suite can be run like this: -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- - mkdir -p build && cd build - cmake .. [-DENABLE_CUDA=1] - make -j - ctest -j --output-on-error [-T memcheck] - exit -#]=] -cmake_minimum_required(VERSION 3.11) - -enable_testing() - -if (NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) -endif() -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +# Copyright 2018-2025 Alfredo A. Correa +# Copyright 2024 Matt Borland +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt -find_package(Boost COMPONENTS unit_test_framework) # tests require Boost.Test `sudo apt install libboost-test-dev` +cmake_minimum_required(VERSION 3.16) # 3.16 for precompiled_headers -if((ENABLE_CUDA OR DEFINED CXXCUDA) AND NOT DEFINED FORCED_ARCH) - enable_language(CUDA) -endif() +if (DEFINED BOOST_SUPERPROJECT_VERSION AND NOT DEFINED BOOST_MULTI_STANDALONE AND BUILD_TESTING) + + include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST) + + if(HAVE_BOOST_TEST) + + boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::multi Boost::multi_array Boost::iterator) -enable_testing() -include(CTest) - -set(WARNS -$<$: --Xcompiler=-Werror,-Wall,-Wextra,-Wcast-align,-Wcast-qual,-Wdouble-promotion,-Wduplicated-branches,-Wduplicated-cond,-Wformat-truncation,-Wformat=2,-Wlogical-op,-Wmisleading-indentation,-Wno-missing-include-dirs,-Wnon-virtual-dtor,-Wno-missing-declarations,-Wnon-virtual-dtor,-Wnull-dereference,-Woverloaded-virtual,-Wpointer-arith,-Wno-redundant-decls,-Wno-shadow,-Wno-switch-enum,-Wno-unknown-pragmas,-Wtrampolines,-Wuninitialized,-Wunused,-Wunused-but-set-variable,-Wunused-result,-Wno-zero-as-null-pointer-constant ---expt-relaxed-constexpr --extended-lambda --Werror=cross-execution-space-call -Xcudafe=--display_error_number -Xcudafe=--diag_error=incompatible_assignment_operands -Xcudafe=--diag_error=returning_ptr_to_local_variable -Xcudafe=--diag_error=subscript_out_of_range -Xcudafe=--diag_error=used_before_set -Xcudafe=--diag_error=undefined_preproc_id -Xcudafe=--diag_error=implicit_func_decl -Xcudafe=--diag_error=implicit_return_from_non_void_function -Xcudafe=--diag_error=missing_type_specifier -> -$<$,$>,$>>: -Werror # -Wno-unknown-warning (activates -Wunknown-pragmas) -# -WNSObject-attribute # (gcc 12, not in 11) -# -Wabi=13 -Wabi-tag # (maybe important when linking with very old libraries) --Waddress -#-Waddress-of-packed-member # (gcc 11, not in gcc 8) --Wno-aggregate-return # (disallow return classes or structs, seems a C-compatibility warning) --Waggressive-loop-optimizations --Waligned-new=all # (gcc 12, not in 11) -# -Walloc-size-larger-than= # (gcc 12, not in 11) --Walloc-zero # -Walloc-size-larger-than= --Walloca # -Walloca-larger-than= -# -Warith-conversion # (gcc 11, not in gcc 9) --Warray-bounds # -Warray-bounds=<0,2> -# -Warray-compare # (gcc 12, not in gcc 9) -# -Warray-parameter #=<0,2> # (gcc 11, not in gcc 10) -#-Wattribute-alias #=<0,2> # (gcc 12, not in 11, not in 7) -#-Wattribute-warning # (gcc 9, not in 8) --Wattributes --Wbool-compare -Wbool-operation -# -Wbidi-chars -Wbidi-chars=any # (gcc 12, not in 11) --Wbuiltin-declaration-mismatch -Wbuiltin-macro-redefined -#-Wc++-compat --Wc++0x-compat -Wc++11-compat -Wc++14-compat -Wc++17-compat -# -Wc++17-extensions # (gcc 12, not in 9) --Wc++1z-compat -# -Wc++20-compat -Wc++20-extensions -Wc++23-extensions -Wc++2a-compat # (gcc 11, not in gcc 9) -#-Wcannot-profile # (gcc 9, not in gcc 8) -#-Wcast-align=strict -Wcast-function-type # (gcc 8, not in gcc 7) --Wcast-qual -#-Wcatch-value #=<0, 3> # (gcc 8, not in gcc 7) --Wchar-subscripts -#-Wclass-conversion # (gcc 11, not in 8) -#-Wclass-memaccess # (gcc 12, not in 11, not in 7) --Wclobbered -# -Wcomma-subscript # (gcc 12, not in 11) --Wcomment # (same as -Wcomments) --Wconditionally-supported --Wconversion -Wconversion-null --Wcoverage-mismatch -Wcpp -# -Wctad-maybe-unsupported # (gcc 12, not in 9) --Wctor-dtor-privacy --Wdangling-else -# -Wdangling-pointer # (gcc 12, not in 11) --Wdate-time --Wdelete-incomplete -Wdelete-non-virtual-dtor --Wdeprecated -#-Wdeprecated-copy -Wdeprecated-copy-dtor # (gcc 11, not in gcc 8) --Wdeprecated-declarations -# -Wdeprecated-enum-enum-conversion -Wdeprecated-enum-float-conversion # (gcc 11, not in gcc 10) --Wdisabled-optimization --Wdiv-by-zero -Wdouble-promotion --Wduplicated-branches -Wduplicated-cond -# -Weffc++ # (doesn't allow some advanced techniques, such as CRTP) --Wempty-body -Wendif-labels --Wenum-compare -# -Wenum-conversion # (gcc 11, not in 10) --Wexpansion-to-defined -# -Wexceptions # (gcc 11, not in 10) -# -Wextra-semi # (gcc 8, not in 7) --Wfloat-conversion # -Wfloat-equal # (disallows float equality) --Wformat=2 --Wformat-contains-nul # (gcc 12, not in 11) -# -Wformat-diag # (gcc 10, not in 9) --Wformat-extra-args -Wformat-nonliteral --Wformat-overflow=1 --Wformat-security -Wformat-signedness -Wformat-truncation -Wformat-y2k -Wformat-zero-length --Wframe-address # -Wframe-larger-than= --Wfree-nonheap-object -Whsa -# -Wif-not-aligned # (gcc 8, not in 7) --Wignored-attributes # -Wignored-qualifiers --Wno-ignored-qualifiers --Wimplicit-fallthrough #=3 # -Wimplicit-fallthrough=<0,5> -# -Winaccessible-base # (gcc 12, not in 11) -# -Winfinite-recursion # (gcc 12, not in 9) --Winherited-variadic-ctor -#-Winit-list-lifetime # (gcc 12, not in 11) --Winit-self --Wno-inline # not all inline attempts can be successful --Wint-in-bool-context -Wint-to-pointer-cast -# -Winterference-size # (gcc 12, not in 11) -# -Winvalid-imported-macros # (gcc 11, not in gcc 10) --Winvalid-memory-model -Winvalid-offsetof -Winvalid-pch -# -Wlarger-than= # (disallow large objects types? in executable) --Wliteral-suffix --Wlogical-not-parentheses -Wlogical-op -# -Wlong-long # (C++98 warning) --Wlto-type-mismatch -Wmain -Wmaybe-uninitialized --Wmemset-elt-size -Wmemset-transposed-args --Wmisleading-indentation -# -Wmismatched-dealloc -Wmismatched-new-delete # (gcc 11, not in gcc 10) -# -Wmismatched-tags # (gcc 11, not in 9) -# -Wmissing-attributes # (gcc 8, not in 8) --Wmissing-braces -Wmissing-declarations -Wmissing-field-initializers -Wmissing-format-attribute -Wmissing-include-dirs -Wmissing-noreturn -#-Wmissing-profile # (gcc 11, not in gcc 8) -# -Wmissing-requires -Wmissing-template-keyword # (gcc 12, not in 11) --Wmultichar --Wno-multiple-inheritance # (disallows composition by inheritance) -# -Wmultistatement-macros # (gcc 8, not in 7) --Wno-namespaces # (disallows use of namespaces, seems a C-tool) --Wnarrowing -# -Wno-alloc-size-larger-than= -Wframe-larger-than= -Wno-larger-than -Wstack-usage= # (gcc 112, not in 11) --Wnoexcept -Wnoexcept-type --Wnon-template-friend -Wnon-virtual-dtor --Wnonnull -Wnonnull-compare --Wnormalized #=nfc -Wnormalized=[none|id|nfc|nfkc] --Wnull-dereference --Wodr -Wold-style-cast -# -Wopenacc-parallelism # (gcc 12, not in 11) --Wopenmp-simd -Woverflow --Woverlength-strings -Woverloaded-virtual --Wpacked -Wpacked-bitfield-compat -# -Wpacked-not-aligned # (gcc 8, not 7) -# -Wpadded # (disallows structs that need padding for alignment) --Wparentheses -#-Wpessimizing-move # (gcc 11, not in gcc 8) --Wplacement-new #=1 -Wplacement-new=<0,2> --Wpmf-conversions --Wpointer-arith -Wpointer-compare --Wpragmas -#-Wprio-ctor-dtor # (gcc 11, not in gcc 8) --Wpsabi -# -Wrange-loop-construct # (gcc 11, not in gcc 10) --Wredundant-decls -#-Wredundant-move # (gcc 11, not in gcc 8) -# -Wredundant-tags # (gcc 11, not in gcc 9) --Wregister --Wreorder # (gcc 12, not in 11) --Wreturn-local-addr -Wreturn-type --Wrestrict -Wreorder --Wscalar-storage-order -Wsequence-point --Wshadow -Wshadow-compatible-local -Wshadow-local -Wshadow=compatible-local -Wshadow=local --Wshift-count-negative -Wshift-count-overflow -Wshift-negative-value -Wshift-overflow #=1 -Wshift-overflow=<0,2> --Wsign-compare -Wsign-conversion -Wsign-promo --Wsized-deallocation --Wsizeof-array-argument -# -Wsizeof-array-div # (gcc 11, not in 10) --Wsizeof-pointer-memaccess --Wstack-protector # -Wstack-usage= --Wstrict-aliasing #=3 -Wstrict-aliasing=<0,3> --Wstrict-null-sentinel #=1 -Wstrict-overflow=<0,5> --Wstrict-overflow #=1 -Wstrict-overflow=<0,5> -# -Wstring-compare # (gcc 11, not in 9) --Wstringop-overflow #=2 -Wstringop-overflow=<0,4> -# -Wstringop-overread # (gcc 11, not in 10) -# -Wstringop-truncation # (gcc 8, not in 7) --Wsubobject-linkage -# -Wsuggest-attribute=cold # (gcc 12, not in 11, not in 7) --Wsuggest-attribute=const -Wsuggest-attribute=format -# -Wsuggest-attribute=malloc # (gcc 8, not in 7) --Wsuggest-attribute=noreturn # -Wsuggest-attribute=pure # (false positive in gcc 8.5.0 RedHat) --Wsuggest-final-methods -Wsuggest-final-types --Wsuggest-override # (gcc 12, not in gcc 11) --Wswitch -Wswitch-bool -Wswitch-default -Wswitch-enum -# -Wswitch-outside-range # (gcc 11, not in gcc 9) --Wswitch-unreachable --Wsync-nand -Wsynth --Wno-system-headers # (expects system headers to be warning-compliant which they are not) --Wtautological-compare --Wno-templates # (disallows templates, C-tool) --Wterminate # (gcc 12, not in 11) --Wtrampolines -Wtrigraphs -# -Wtrivial-auto-var-init # (gcc 12, not in 11) -# -Wtsan # (gcc 11, not in 10) --Wtype-limits -Wundef -Wuninitialized --Wno-unknown-pragmas # (see above) -Wunknown-pragmas (other compilers need their own pragmas for their warnings) --Wunreachable-code -Wunsafe-loop-optimizations --Wunused -Wunused-but-set-parameter -Wunused-but-set-variable --Wunused-const-variable #=2 TODO(correaa) add [[maybe_unused]] to niebloids --Wunused-function -Wunused-label -Wunused-local-typedefs -Wunused-macros -Wunused-parameter -Wunused-result -Wunused-value -Wunused-variable -# -Wuse-after-free # =<0,3> # (gcc 12, not in 11) --Wuseless-cast --Wvarargs -Wvariadic-macros -Wvector-operation-performance -# -Wvexing-parse # (gcc 11, not in gcc 10) --Wvirtual-inheritance -Wvirtual-move-assign --Wvla # -Wvla-larger-than= # (gcc 12, not in 11) -# -Wvla-parameter # (gcc 11, not in gcc 10) -# -Wvolatile # (gcc 11, not in gcc 9) --Wvolatile-register-var --Wwrite-strings --Wzero-as-null-pointer-constant -# -Wzero-length-bounds # (gcc 12, not in 11) -> -$<$,$,$>:-Werror -Wall -Wpedantic -Wextra --Wno-error=\#warnings # -Wno-\#warnings disable this working for a bug in rocm ~5.6 --WCFString-literal --WCL4 --WIndependentClass-attribute --WNSObject-attribute --Wabi --Wabsolute-value --Wabstract-final-class -Wabstract-vbase-init --Waddress -Waddress-of-packed-member -Waddress-of-temporary --Waggregate-return -# -Waix-compat (clang 13, not in clang 11) -# -Walign-mismatch (clang 13, not in clang 11) -# -Walloca (clang 11, not in clang 9) --Walloca-with-align-alignof -# -Walways-inline-coroutine (clang 15, not in clang 13) --Wambiguous-delete -Wambiguous-ellipsis -Wambiguous-macro -Wambiguous-member-template -# -Wambiguous-reversed-operator (clang 11, not in clang 9) --Wanalyzer-incompatible-plugin -# -Wanon-enum-enum-conversion (clang 11, not in clang 9) --Wanonymous-pack-parens --Warc -Warc-bridge-casts-disallowed-in-nonarc -Warc-maybe-repeated-use-of-weak -Warc-non-pod-memaccess -Warc-performSelector-leaks -Warc-repeated-use-of-weak -Warc-retain-cycles -Warc-unsafe-retained-assign -# -Wargument-outside-range (clang 15, not in clang 13) -# -Wargument-undefined-behaviour (clang 15, not in clang 13) --Warray-bounds -Warray-bounds-pointer-arithmetic --Wasm -Wasm-operand-widths --Wassign-enum --Wassume --Wat-protocol --Watimport-in-framework-header -# -Watomic-access (clang 15, not in clang 13) --Watomic-alignment -# -Watomic-implicit-seq-cst (not in clang 7) --Watomic-memory-ordering -Watomic-properties -Watomic-property-with-user-defined-accessor --Wattribute-packed-for-bitfield -# -Wattribute-warning (clang 15, not in clang 13) --Wattributes --Wauto-disable-vptr-sanitizer -Wauto-import -Wauto-storage-class -Wauto-var-id --Wavailability -# -Wavr-rtlib-linking-quirks (not in clang 7) --Wbackend-plugin --Wbackslash-newline-escape --Wbad-function-cast --Wbinary-literal --Wbind-to-temporary-copy --Wbinding-in-condition -# -Wbit-int-extension (clang 15, not in clang 13) --Wbitfield-constant-conversion -Wbitfield-enum-conversion -Wbitfield-width -# -Wbitwise-conditional-parentheses (clang 11, not in clang 9) -# -Wbitwise-instead-of-logical (clang 15, not in clang 13) --Wbitwise-op-parentheses --Wblock-capture-autoreleasing --Wbool-conversion -Wbool-conversions -# -Wbool-operation (clang 11, not in clang 9) --Wbraced-scalar-init -# -Wbranch-protection (clang 15, not in clang 13) --Wbridge-cast -# -Wbuiltin-assume-aligned-alignment (clang 11, not in clang 9) --Wbuiltin-macro-redefined -Wbuiltin-memcpy-chk-size -Wbuiltin-requires-header -# -Wc++-compat -# -Wc++0x-compat -Wc++0x-extensions -Wc++0x-narrowing -# -Wc++11-compat -Wc++11-compat-deprecated-writable-strings -Wc++11-compat-pedantic -Wc++11-compat-reserved-user-defined-literal -Wc++11-extensions -Wc++11-extra-semi -Wc++11-inline-namespace -Wc++11-long-long -Wc++11-narrowing -# -Wc++14-attribute-extensions (clang 15, not in clang 13) --Wc++14-binary-literal -# -Wc++14-compat -Wc++14-compat-pedantic -Wc++14-extensions -# -Wc++17-attribute-extensions (clang 15, not in clang 13) -# -Wc++17-compat -Wc++17-compat-mangling -Wc++17-compat-pedantic -Wc++17-extensions -# -Wc++1y-extensions -# -Wc++1z-compat -Wc++1z-compat-mangling -Wc++1z-extensions -# -Wc++20-attribute-extensions (clang 15, not in clang 13) -# -Wc++20-compat -Wc++20-compat-pedantic -# -Wc++20-designator -Wc++20-extensions -# -Wc++2a-compat -Wc++2a-compat-pedantic -# -Wc++2a-extensions -# -Wc++2b-extensions -# -Wc++98-c++11-c++14-c++17-compat -Wc++98-c++11-c++14-c++17-compat-pedantic -Wc++98-c++11-c++14-compat -Wc++98-c++11-c++14-compat-pedantic -# -Wc++98-c++11-compat -Wc++98-c++11-compat-binary-literal -Wc++98-c++11-compat-pedantic -# -Wc++98-compat --Wno-c++98-compat-bind-to-temporary-copy -#-Wc++98-compat-extra-semi -Wc++98-compat-local-type-template-args -Wc++98-compat-pedantic -Wc++98-compat-unnamed-type-template-args --Wno-c++98-compat-unnamed-type-template-args -# -Wc11-extensions -# -Wc2x-extensions -# -Wc99-compat -# -Wc99-designator -Wc99-extensions --Wcast-align -Wcast-qual -# -Wcall-to-pure-virtual-from-ctor-dtor (not in clang 7) -# -Wcalled-once-parameter (clang 13, not in clang 11) --Wcast-align -Wcast-calling-convention -# -Wcast-function-type (clang 13, not in clang 11) --Wcast-of-sel-type -Wcast-qual -Wcast-qual-unrelated --Wchar-align -Wchar-subscripts --Wclang-cl-pch -# -Wclass-conversion (clang 11, not in clang 9) --Wclass-varargs -# -Wcmse-union-leak (clang 11, not in clang 9) --Wcomma --Wcomment -Wcomments --Wcompare-distinct-pointer-types -# -Wcompletion-handler (clang 13, not in clang 11) --Wcomplex-component-init -# -Wcompound-token-split -Wcompound-token-split-by-macro -Wcompound-token-split-by-space (clang 13, not in clang 11) --Wconversion -#-Wconcepts-ts-compat (not working in clang++ 16) --Wconditional-type-mismatch -Wconditional-uninitialized --Wconfig-macros --Wconstant-conversion -# -Wconstant-evaluated (clang 11, not in clang 9) --Wconstant-logical-operand --Wconstexpr-not-const --Wconsumed --Wconversion -Wconversion-null --Wcoroutine -Wcoroutine-missing-unhandled-exception --Wcovered-switch-default --Wcpp --Wcstring-format-directive --Wctor-dtor-privacy -# -Wctu # (not in clang 7) --Wcuda-compat --Wcustom-atomic-properties -# -Wcxx-attribute-extension (clang 13, not in clang 11) --Wdangling -Wdangling-else -Wdangling-field -# -Wdangling-gsl (clang 13, not in clang 9) --Wdangling-initializer-list -# -Wdarwin-sdk-settings (not in clang 7) --Wdate-time --Wdealloc-in-category --Wdebug-compression-unavailable --Wdeclaration-after-statement -# -Wdefaulted-function-deleted (not in clang 7) --Wdelegating-ctor-cycles -# -Wdelete-abstract-non-virtual-dtor (not in clang 7) --Wdelete-incomplete -# -Wdelete-non-abstract-non-virtual-dtor (not in clang 7) -# -Wdelete-non-virtual-dtor (not in clang 7) -# -Wdelimited-escape-sequence-extension (clang 15, not in clang 13) --Wdeprecated -# -Wdeprecated-altivec-src-compat (clang 13, not in clang 11) -# -Wdeprecated-anon-enum-enum-conversion -Wdeprecated-array-compare (clang 11, not in clang 9) --Wdeprecated-attributes -# -Wdeprecated-comma-subscript (clang 11, not in clang 9) -# -Wdeprecated-copy -Wdeprecated-copy-dtor (clang 11, not in clang 9) -# -Wdeprecated-copy-with-dtor (clang 13, not in clang 11) -# -Wdeprecated-copy-with-user-provided-copy (clang 13, not in clang 11) -# -Wdeprecated-copy-with-user-provided-dtor (clang 13, not in clang 11) -# -Wdeprecated-coroutine (clang 15, not in clang 13) --Wdeprecated-declarations -Wdeprecated-dynamic-exception-spec -# -Wdeprecated-enum-compare -Wdeprecated-enum-compare-conditional -Wdeprecated-enum-enum-conversion (clang 11, not in clang 9) -# -Wdeprecated-enum-float-conversion (clang 11, not in clang 9) -# -Wdeprecated-experimental-coroutine (clang 15, not in clang 13) --Wdeprecated-implementations -Wdeprecated-increment-bool -# -Wdeprecated-non-prototype (clang 15, not in clang 13) --Wdeprecated-objc-isa-usage -Wdeprecated-objc-pointer-introspection -Wdeprecated-objc-pointer-introspection-performSelector -# -Wdeprecated-pragma (clang 15, not in clang 13) --Wdeprecated-register -Wdeprecated-this-capture -# -Wdeprecated-type (clang 15, not in clang 13) -# -Wdeprecated-volatile (clang 11, not in clang 9) --Wdeprecated-writable-strings --Wdirect-ivar-access -# -Wdisabled-macro-expansion (Boost.Test) --Wdisabled-optimization --Wdiscard-qual --Wdistributed-object-modifiers --Wdiv-by-zero --Wdivision-by-zero --Wdll-attribute-on-redeclaration -Wdllexport-explicit-instantiation-decl -Wdllimport-static-field-def --Wdocumentation -Wdocumentation-deprecated-sync -Wdocumentation-html -Wdocumentation-pedantic -Wdocumentation-unknown-command --Wdollar-in-identifier-extension --Wdouble-promotion -# -Wdtor-name -Wdtor-typedef (clang 11, not in clang 9) --Wduplicate-decl-specifier -Wduplicate-enum -Wduplicate-method-arg -Wduplicate-method-match -Wduplicate-protocol --Wdynamic-class-memaccess -Wdynamic-exception-spec -# -Weffc++ -# -Welaborated-enum-base -Welaborated-enum-class (clang 11, not in clang 9) --Wembedded-directive --Wempty-body -Wempty-decomposition -# -Wempty-init-stmt (not in clang 7) --Wempty-translation-unit --Wencode-type --Wendif-labels --Wenum-compare -# -Wenum-compare-conditional (clang 11, not in clang 9) --Wenum-compare-switch -Wenum-conversion -# -Wenum-enum-conversion -Wenum-float-conversion (clang 11, not in clang 9) --Wenum-too-large --Wexceptions -# -Wexcess-initializers (clang 11, not in clang 9) --Wexit-time-destructors --Wexpansion-to-defined --Wexplicit-initialize-call -Wexplicit-ownership-type -# -Wexport-unnamed (not in clang 7) -# -Wexport-using-directive (not in clang 7) --Wextern-c-compat -Wextern-initializer -#-Wextra --Wextra-qualification -# -Wextra-semi (clang 8, not in 7) -# -Wextra-semi-stmt (not in clang 7) --Wextra-tokens -# -Wfinal-dtor-non-final-class (clang 11, not in clang 9) -# -Wfinal-macro (clang 15, not in clang 13) -# -Wfixed-enum-extension (not in clang 7) -# -Wfixed-point-overflow (clang 11, not in clang 9) --Wflag-enum --Wflexible-array-extensions --Wfloat-conversion # -Wfloat-equal --Wfloat-overflow-conversion -Wfloat-zero-conversion --Wfor-loop-analysis --Wformat -Wformat-extra-args -# -Wformat-insufficient-args (clang 13, not in clang 11) --Wformat-invalid-specifier -Wformat-non-iso -Wformat-nonliteral -Wformat-pedantic -Wformat-security -# -Wformat-type-confusion (clang 11, not in clang 9) --Wformat-y2k -Wformat-zero-length -Wformat=2 -# -Wfortify-source (not in clang 7) --Wfour-char-constants -# -Wframe-address (clang 11, not in clang 9) -# -Wframe-larger-than # -Wframe-larger-than= --Wframework-include-private-from-public -# -Wfree-nonheap-object (clang 13, not in clang 11) --Wfunction-def-in-objc-container -Wfunction-multiversion -# -Wfuse-ld-path (clang 13, not in clang 11) -# -Wfuture-attribute-extensions (clang 15, not in clang 13) -# -Wfuture-compat -# -Wgcc-compat -# -Wglobal-constructors (Boost.Test) -# -Wglobal-isel (clang 11, not in clang 9) --Wgnu -Wgnu-alignof-expression -Wgnu-anonymous-struct -Wgnu-array-member-paren-init -Wgnu-auto-type -Wgnu-binary-literal -Wgnu-case-range -Wgnu-complex-integer -Wgnu-compound-literal-initializer -Wgnu-conditional-omitted-operand -Wgnu-designator -Wgnu-empty-initializer -Wgnu-empty-struct -Wgnu-flexible-array-initializer -Wgnu-flexible-array-union-member -Wgnu-folding-constant -Wgnu-imaginary-constant -Wgnu-include-next -# -Wgnu-inline-cpp-without-extern (clang 11, not in clang 9) --Wgnu-label-as-value -# -Wgnu-null-pointer-arithmetic -# -Wgnu-pointer-arith (clang 15, not in clang 13) --Wgnu-redeclared-enum -Wgnu-statement-expression -Wgnu-static-float-init -Wgnu-string-literal-operator-template -Wgnu-union-cast -Wgnu-variable-sized-type-not-at-end -Wgnu-zero-line-directive -Wgnu-zero-variadic-macro-arguments -# -Wgpu-maybe-wrong-side (clang 15, not in clang 13) --Wheader-guard -Wheader-hygiene -# -Whip-only (clang 11, not in clang 9) --Widiomatic-parentheses --Wignored-attributes -# -Wignored-availability-without-sdk-settings (clang 13, not in clang 11) --Wignored-optimization-argument -#-Wignored-pragma-intrinsic -#-Wignored-pragma-optimize -#-Wignored-pragmas (other compilers need pragmas) --Wno-ignored-qualifiers -# -Wignored-reference-qualifiers (clang 15, not in clang 13) --Wimplicit -Wimplicit-atomic-properties -# -Wimplicit-const-int-float-conversion (clang 11, not in clang 9) --Wimplicit-conversion-floating-point-to-bool -Wimplicit-exception-spec-mismatch -Wimplicit-fallthrough -Wimplicit-fallthrough-per-function -# -Wimplicit-fixed-point-conversion -Wimplicit-float-conversion (not in clang 7) --Wimplicit-function-declaration -Wimplicit-int -# -Wimplicit-int-conversion (not in clang 7) -# -Wimplicit-int-float-conversion (clang 11, not in clang 9) --Wimplicit-retain-self -Wimplicitly-unsigned-literal --Wimport -Wimport-preprocessor-directive-pedantic --Winaccessible-base --Winclude-next-absolute-path -Winclude-next-outside-header --Wincompatible-exception-spec -Wincompatible-function-pointer-types -Wincompatible-library-redeclaration -Wincompatible-ms-struct -Wincompatible-pointer-types -Wincompatible-pointer-types-discards-qualifiers -Wincompatible-property-type -Wincompatible-sysroot -Wincomplete-framework-module-declaration -Wincomplete-implementation -Wincomplete-module -# -Wincomplete-setjmp-declaration (not in clang 7) --Wincomplete-umbrella --Winconsistent-dllimport -Winconsistent-missing-destructor-override -Winconsistent-missing-override --Wincrement-bool --Winfinite-recursion --Winit-self --Winitializer-overrides --Winjected-class-name --Winline -Winline-asm -# -Winline-namespace-reopened-noninline (clang 11, not in clang 9) --Winline-new-delete --Winstantiation-after-specialization --Wint-conversion -Wint-conversions -# -Wint-in-bool-context (clang 11, not in clang 9) --Wint-to-pointer-cast -Wint-to-void-pointer-cast --Winteger-overflow -# -Winterrupt-service-routine (clang 13, not in clang 11) --Winvalid-command-line-argument -Winvalid-constexpr -Winvalid-iboutlet -Winvalid-initializer-from-system-header -Winvalid-ios-deployment-target -# -Winvalid-no-builtin-names (clang 11, not in clang 9) --Winvalid-noreturn -Winvalid-offsetof -Winvalid-or-nonexistent-directory -Winvalid-partial-specialization -Winvalid-pch -Winvalid-pp-token -Winvalid-source-encoding -Winvalid-token-paste --Wjump-seh-finally --Wkeyword-compat -Wkeyword-macro --Wknr-promoted-parameter --Wlanguage-extension-token --Wlarge-by-value-copy --Wliblto -# -Wlinker-warnings (clang 15, not in clang 13) --Wliteral-conversion -Wliteral-range -# -Wlocal-type-template-args --Wlogical-not-parentheses -Wlogical-op-parentheses -#-Wlong-long (C++98 warning) --Wloop-analysis --Wmacro-redefined --Wmain -Wmain-return-type --Wmalformed-warning-check --Wmany-braces-around-scalar-init -# -Wmax-tokens (clang 11, not in clang 9) --Wmax-unsigned-zero --Wmemset-transposed-args -Wmemsize-comparison --Wmethod-signatures --Wmicrosoft -# -Wmicrosoft-abstract (clang 13, not in clang 11) --Wmicrosoft-anon-tag -Wmicrosoft-cast -Wmicrosoft-charize -Wmicrosoft-comment-paste -Wmicrosoft-const-init -Wmicrosoft-cpp-macro -Wmicrosoft-default-arg-redefinition -# -Wmicrosoft-drectve-section (not in clang 7) --Wmicrosoft-end-of-file -Wmicrosoft-enum-forward-reference -Wmicrosoft-enum-value -Wmicrosoft-exception-spec -Wmicrosoft-exists -Wmicrosoft-explicit-constructor-call -Wmicrosoft-extra-qualification -Wmicrosoft-fixed-enum -Wmicrosoft-flexible-array -Wmicrosoft-goto -Wmicrosoft-inaccessible-base -Wmicrosoft-include -Wmicrosoft-mutable-reference -Wmicrosoft-pure-definition -Wmicrosoft-redeclare-static -Wmicrosoft-sealed -# -Wmicrosoft-static-assert (clang 13, not in clang 11) --Wmicrosoft-template -# -Wmicrosoft-template-shadow (clang 11, not in clang 9) --Wmicrosoft-union-member-reference -Wmicrosoft-unqualified-friend -Wmicrosoft-using-decl -Wmicrosoft-void-pseudo-dtor -# -Wmisexpect (clang 15, not in clang 13) -# -Wmisleading-indentation (clang 11, not in clang 9) --Wmismatched-new-delete -Wmismatched-parameter-types -Wmismatched-return-types -Wmismatched-tags --Wmissing-braces -# -Wmissing-constinit (clang 11, not in clang 9) --Wmissing-declarations -Wmissing-exception-spec -Wmissing-field-initializers -Wmissing-format-attribute -Wmissing-include-dirs -Wmissing-method-return-type -Wmissing-noescape -Wmissing-noreturn -Wmissing-prototype-for-cc -Wmissing-prototypes -Wmissing-selector-name -Wmissing-sysroot -Wmissing-variable-declarations -# -Wmisspelled-assumption (clang 13, not in clang 11) -# -Rmodule-build --Wmodule-conflict -Wmodule-file-config-mismatch -Wmodule-file-extension -# -Rmodule-import (not in clang 7) --Wmodule-import-in-extern-c -# -Rmodule-lock --Wmodules-ambiguous-internal-linkage -Wmodules-import-nested-redundant --Wmost --Wmove --Wmsvc-include -Wmsvc-not-found --Wmultichar --Wmultiple-move-vbase --Wnon-virtual-dtor --Wnarrowing --Wnested-anon-types -Wnested-externs --Wnew-returns-null --Wnewline-eof -# -Wnoderef (not in clang 7) --Wnoexcept-type -# -Wnon-c-typedef-for-linkage (clang 11, not in clang 9) --Wnon-gcc --Wnon-literal-null-conversion --Wnon-modular-include-in-framework-module -Wnon-modular-include-in-module --Wnon-pod-varargs -# -Wnon-power-of-two-alignment (clang 11, not in clang 9) --Wnon-virtual-dtor --Wnonnull -#-Wnonportable-cfstrings --Wnonportable-include-path --Wnonportable-system-include-path --Wnonportable-vector-initialization --Wnontrivial-memaccess --Wnsconsumed-mismatch --Wnsreturns-mismatch --Wnull-arithmetic -Wnull-character -Wnull-conversion -Wnull-dereference -Wnull-pointer-arithmetic -# -Wnull-pointer-subtraction (clang 13, not in clang 11) --Wnullability -Wnullability-completeness -Wnullability-completeness-on-arrays -Wnullability-declspec -Wnullability-extension -Wnullability-inferred-on-nested-type --Wnullable-to-nonnull-conversion --Wodr --Wold-style-cast -Wold-style-definition --Wopencl-unsupported-rgba -# -Wopenmp (clang 11, not in clang 9) -# -Wopenmp-51-extensions (clang 13, not in clang 11) --Wopenmp-clauses -Wopenmp-loop-form -# -Wopenmp-mapping (clang 11, not in clang 9) --Wopenmp-target --Woption-ignored --Wordered-compare-function-pointers --Wout-of-line-declaration -Wout-of-scope-function --Wover-aligned --Woverflow --Woverlength-strings --Woverloaded-shift-op-parentheses -Woverloaded-virtual -# -Woverride-init (not in clang 7) --Woverride-module -Woverriding-method-mismatch -Woverriding-t-option --Wpacked -# -Wpadded (ask for aligment) --Wparentheses -Wparentheses-equality --Wpartial-availability -# -Rpass -Rpass-analysis --Wpass-failed -# -Rpass-missed --Wpch-date-time --Wpedantic -Wpedantic-core-features -# -Wpedantic-macros (clang 15, not in clang 13) --Wpessimizing-move --Wpointer-arith -Wpointer-bool-conversion -# -Wpointer-compare (clang 11, not in clang 9) -# -Wpointer-integer-compare (not in clang 7) --Wpointer-sign -# -Wpointer-to-enum-cast (clang 11, not in clang 9) --Wpointer-to-int-cast -Wpointer-type-mismatch -# -Wpoison-system-directories (clang 11, not in clang 9) -# -Wpotentially-direct-selector (clang 11, not in clang 9) --Wpotentially-evaluated-expression -# -Wpragma-clang-attribute -Wpragma-once-outside-header -Wpragma-pack -Wpragma-pack-suspicious-include -Wpragma-system-header-outside-header -Wpragmas -# -Wpre-c++14-compat -Wpre-c++14-compat-pedantic -# -Wpre-c++17-compat -Wpre-c++17-compat-pedantic -# -Wpre-c++20-compat -Wpre-c++20-compat-pedantic -Wpre-c++2b-compat -Wpre-c++2b-compat-pedantic (clang 13, not in clang 11) -# -Wpre-c2x-compat -Wpre-c2x-compat-pedantic (clang 13, not in clang 11) -# -Wpre-openmp-51-compat (clang 13, not in clang 11) --Wpredefined-identifier-outside-function --Wprivate-extern -Wprivate-header -Wprivate-module --Wprofile-instr-missing -Wprofile-instr-out-of-date -Wprofile-instr-unprofiled --Wproperty-access-dot-syntax -Wproperty-attribute-mismatch --Wprotocol -Wprotocol-property-synthesis-ambiguity -# -Wpsabi (clang 11, not in clang 9) --Wqualified-void-return-type --Wquoted-include-in-framework-header -# -Wrange-loop-analysis (this check is overzealous in clang 9) --Wno-range-loop-analysis -# -Wrange-loop-bind-reference -Wrange-loop-construct (clang 11, not in clang 9) --Wreadonly-iboutlet-property --Wreceiver-expr -Wreceiver-forward-class --Wredeclared-class-member -# -Wredundant-consteval-if (clang 15, not in clang 13) --Wredundant-decls -Wredundant-move -Wredundant-parens --Wregister --Wreinterpret-base-class --Rremark-backend-plugin --Wreorder -# -Wreorder-ctor -Wreorder-init-list (clang 11, not in clang 9) --Wrequires-super-attribute --Wreserved-id-macro -# -Wreserved-identifier -Wreserved-macro-identifier (clang 13, not in clang 11) --Wreserved-user-defined-literal -# -Wrestrict-expansion (clang 15, not in clang 13) --Wretained-language-linkage --Wreturn-stack-address -Wreturn-std-move -Wreturn-type -Wreturn-type-c-linkage -# -Wrewrite-not-bool (clang 11, not in clang 9) -# -Rround-trip-cc1-args (clang 13, not in clang 11) -# -Wrtti (clang 13, not in clang 11) --Wshadow --Wold-style-cast --Woverloaded-virtual --Rsanitize-address -# -Rsearch-path-usage --Wsection --Wselector -Wselector-type-mismatch --Wself-assign -Wself-assign-field -Wself-assign-overloaded --Wself-move --Wsemicolon-before-method-body --Wsentinel --Wsequence-point --Wserialized-diagnostics --Wshadow -Wshadow-all -Wshadow-field -Wshadow-field-in-constructor -Wshadow-field-in-constructor-modified -Wshadow-ivar -Wshadow-uncaptured-local --Wshift-count-negative -Wshift-count-overflow -Wshift-negative-value -Wshift-op-parentheses -Wshift-overflow -Wshift-sign-overflow --Wshorten-64-to-32 --Wsign-compare -Wsign-conversion -Wsign-promo --Wsigned-enum-bitfield -# -Wsigned-unsigned-wchar (clang 11, not in clang 9) --Wsizeof-array-argument -Wsizeof-array-decay -# -Wsizeof-array-div (clang 11, not in clang 9) -# -Wsizeof-pointer-div (not in clang 7) --Wsizeof-pointer-memaccess --Wslash-u-filename -# -Wslh-asm-goto (clang 11, not in clang 9) --Wsometimes-uninitialized -# -Wsource-mgr (clang 13, not in clang 11) --Wsource-uses-openmp --Wspir-compat -# -Wspirv-compat (clang 15, not in clang 13) -# -Wstack-exhausted (clang 11, not in clang 9) --Wstack-protector --Wstatic-float-init -Wstatic-in-inline -Wstatic-inline-explicit-instantiation -Wstatic-local-in-inline -Wstatic-self-init --Wstdlibcxx-not-found --Wstrict-aliasing -Wstrict-aliasing=0 -Wstrict-aliasing=1 -Wstrict-aliasing=2 --Wstrict-overflow -Wstrict-overflow=0 -Wstrict-overflow=1 -Wstrict-overflow=2 -Wstrict-overflow=3 -Wstrict-overflow=4 -Wstrict-overflow=5 -# -Wstrict-potentially-direct-selector (clang 11, not in clang 9) --Wstrict-prototypes -Wstrict-selector-match --Wstring-compare -# -Wstring-concatenation (clang 13, not in clang 11) --Wstring-conversion -Wstring-plus-char -Wstring-plus-int --Wstrlcpy-strlcat-size --Wstrncat-size -# -Wsuggest-destructor-override (clang 11, not in clang 9) -# -Wsuggest-override (clang 11, not in clang 9) --Wsuper-class-method-mismatch --Wsuspicious-bzero -Wsuspicious-memaccess -# -Wswift-name-attribute (clang 13, not in clang 11) --Wswitch -Wswitch-bool -Wswitch-default -Wswitch-enum --Wsync-fetch-and-nand-semantics-changed --Wsynth -# -Wtarget-clones-mixed-specifiers (clang 15, not in clang 13) -# -Wtautological-bitwise-compare (clang 11, not in clang 9) --Wtautological-compare -Wtautological-constant-compare -Wtautological-constant-in-range-compare -Wtautological-constant-out-of-range-compare -# -Wtautological-objc-bool-compare (not in clang 7) --Wtautological-overlap-compare -Wtautological-pointer-compare -Wtautological-type-limit-compare -Wtautological-undefined-compare -# -Wtautological-unsigned-char-zero-compare -Wtautological-unsigned-enum-zero-compare -# -Wtautological-unsigned-zero-compare -Wtautological-value-range-compare (clang 13, not in clang 11) -# -Wtcb-enforcement (clang 13, not in clang 11) --Wtentative-definition-incomplete-type --Wthread-safety -Wthread-safety-analysis -Wthread-safety-attributes -Wthread-safety-beta -Wthread-safety-negative -Wthread-safety-precise -Wthread-safety-reference -Wthread-safety-verbose --Wtrigraphs --Wtype-limits -Wtype-safety --Wtypedef-redefinition --Wtypename-missing --Wunable-to-open-stats-file -# -Wunaligned-access (clang 15, not in clang 13) -# -Wunaligned-qualifier-implicit-cast (clang 15, not in clang 13) --Wunavailable-declarations --Wundeclared-selector --Wundef -# -Wundef-prefix (clang 11, not in clang 9) --Wundefined-bool-conversion -Wundefined-func-template -Wundefined-inline -Wundefined-internal -Wundefined-internal-type --Wno-undefined-reinterpret-cast --Wundefined-var-template -# -Wunderaligned-exception-object (not in clang 7) --Wunevaluated-expression --Wunguarded-availability -Wunguarded-availability-new --Wunicode -Wunicode-homoglyph -Wunicode-whitespace -# -Wunicode-zero-width (not in clang 7) --Wuninitialized -# -Wuninitialized-const-reference (clang 11, not in clang 9) --Wunknown-argument -# -Wunknown-assumption (clang 13, not in clang 11) --Wunknown-attributes -# -Wunknown-cuda-version (clang 11, not in clang 9) --Wunknown-escape-sequence -# -Wunknown-pragmas # (other compilers need their own pragmas) --Wno-unknown-pragmas # (other compilers need their own pragmas) -# -Wunknown-sanitizers -# -Wunknown-warning-option --Wunnamed-type-template-args --Wunneeded-internal-declaration -Wunneeded-member-function --Wno-unknown-pragmas -# -Wunqualified-std-cast-call (clang 15, not in clang 13) --Wunreachable-code -Wunreachable-code-aggressive -Wunreachable-code-break -# -Wunreachable-code-fallthrough (clang 15, not in clang 13) --Wunreachable-code-loop-increment -Wunreachable-code-return --Wunsequenced -# -Wunsupported-abi (clang 15, not in clang 13) --Wunsupported-abs -Wunsupported-availability-guard -Wunsupported-cb -Wunsupported-dll-base-class-template -# -Wunsupported-floating-point-opt (clang 11, not in clang 9) --Wunsupported-friend -Wunsupported-gpopt -Wunsupported-nan -Wunsupported-target-opt -Wunsupported-visibility --Wunusable-partial-specialization --Wunused -Wunused-argument -# -Wunused-but-set-parameter -Wunused-but-set-variable (clang 13, not in clang 11) --Wunused-command-line-argument -Wunused-comparison -Wunused-const-variable -Wunused-exception-parameter -Wunused-function -Wunused-getter-return-value -Wunused-label -Wunused-lambda-capture -Wunused-local-typedef -Wunused-local-typedefs -Wunused-macros -Wunused-member-function -Wunused-parameter -Wunused-private-field -Wunused-property-ivar -Wunused-result -Wunused-template -Wunused-value -Wunused-variable -Wunused-volatile-lvalue -# -Wused-but-marked-unused (Boost.Test) --Wuser-defined-literals -Wuser-defined-warnings --Wvarargs --Wvariadic-macros --Wvec-elem-size --Wvector-conversion -Wvector-conversions --Wvexing-parse --Wvisibility --Wvla -Wvla-extension -# -Wvoid-pointer-to-enum-cast -Wvoid-pointer-to-int-cast (clang 11, not in clang 9) --Wvoid-ptr-dereference --Wvolatile-register-var -# -Wwasm-exception-spec (clang 11, not in clang 9) --Wweak-template-vtables -Wweak-vtables --Wwritable-strings --Wwrite-strings -# -Wxor-used-as-pow (clang 11, not in clang 9) --Wzero-as-null-pointer-constant -Wzero-length-array -> -$<$: -Werror -Wall -Wextra # also IntelLLVM, XL (ibm), XLClang (ibm) --diag-disable=remark --diag-error:3846 --diag-disable=1011 # disables warning missing return at the end of non-void function --diag-disable=2196 # disables "error #2196: routine is both "inline" and "noinline"" in icpc 2021.5.0 --wd161 --Wabi --Warray-bounds --Wcast-qual --Wchar-subscripts --Wcomment --Wdeprecated --Wenum-compare --Wextra-tokens --Wformat -Wformat=2 -Wformat-security --Wic-pointer --Wnarrowing --Wno-return-type --Wnon-virtual-dtor --Wnonnull --Wmaybe-uninitialized --Wmain --Wmissing-declarations -Wmissing-prototypes --Wmultichar --Woverloaded-virtual --Woverflow --Wp64 --Wparentheses --Wpointer-arith --Wpointer-sign --Wreorder --Wreturn-type --Wsequence-point --Wshadow --Wsign-compare --Wshorten-64-to-32 --Wmissing-prototypes --Wno-strict-aliasing --Wno-unknown-pragmas --Wstrict-prototypes --Wtrigraphs --Wtype-limits --Wuninitialized --Wunused -Wunused-but-set-variable -Wunused-function -Wunused-parameter -Wunused-variable --Wwrite-strings -> -# $<$,$>: -# -Werror -Wall -# -Wcast-qual -# -Wformat=2 -# -Wshadow -# -Wsign-compare -# -Wuninitialized -# -Wunused -Wunused-parameter -# > -$<$: --Werror -Wall --Wcast-qual --Wformat=2 --Wshadow --Wuninitialized -> -$<$: -/WX /W4 /permissive- /volatile:iso # /EHsc /Zc:wchar_t /Zc:forScope /Zc:inline -> -) - -file( - GLOB TEST_SRCS - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - *.cpp -) - -if(NOT Boost_FOUND) - message(WARNING "Cannot find Boost, Multi library will have a very minimal test. If you want to test the library install Boost.Test, for example please run:\n sudo apt install libboost-test-dev\n sudo dnf install boost-devel") - - add_executable(main main.cpp) - # target_link_libraries(main PRIVATE multi) - # add_test(NAME main COMMAND ./main) - - target_include_directories(main PRIVATE ${PROJECT_SOURCE_DIR}/include) - target_compile_definitions(main PUBLIC BOOST_TEST_MODULE="C++ Unit Tests for Multi main") - - target_compile_definitions(main PRIVATE "$<$:BOOST_PP_VARIADICS>") - - if(NOT DEFINED ENABLE_CIRCLE) - target_compile_options(main PRIVATE ${WARNS}) - else() - target_compile_definitions(main PRIVATE "BOOST_TEST_DYN_LINK=1") - target_compile_options (main PRIVATE -Werror -Wall) endif() - add_test(NAME main COMMAND ./${TEST_EXE}) else() - foreach(TEST_FILE ${TEST_SRCS}) - set(TEST_EXE "${TEST_FILE}.x") - add_executable(${TEST_EXE} ${TEST_FILE}) + enable_testing() + + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + option(CMAKE_COMPILE_WARNING_AS_ERROR "Compile warnings as errors" ON) - if(ENABLE_CUDA OR DEFINED CXXCUDA) - set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) + find_package(Boost CONFIG) +# find_package(Boost COMPONENTS headers) # should be last + + +#libomp is keg-only, which means it was not symlinked into /opt/homebrew, +#because it can override GCC headers and result in broken builds. +# +#For compilers to find libomp you may need to set: +# export LDFLAGS="-L/opt/homebrew/opt/libomp/lib" +# export CPPFLAGS="-I/opt/homebrew/opt/libomp/include" + + if(NOT Boost_FOUND) + message(WARNING "preliminary: Cannot find Boost, Multi library will have a very minimal test. If you want to test the library install Boost.Test, for example please run:\n sudo apt install libboost-test-dev\n sudo dnf install boost-devel") + endif() + + if(ENABLE_CUDA) + enable_language(CUDA) + set(CMAKE_CUDA_EXTENSIONS OFF) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES native) endif() + endif() + +# find_package(TBB) + + enable_testing() + include(CTest) - target_link_libraries(${TEST_EXE} PRIVATE multi) + if (CMAKE_CUDA_HOST_COMPILER_ID STREQUAL "MSVC") + message(STATUS "NVCC's host compiler is MSVC.") + # Add specific actions or configurations for MSVC host compiler + else() + message(STATUS "NVCC's host compiler is not MSVC (ID: ${CMAKE_CUDA_HOST_COMPILER_ID}).") + # Add specific actions or configurations for other host compilers + endif() + + set(WARNS + $<$: + #remove Xcompile for Windows? + #-Xcompiler=-Werror,-Wall,-Wextra,-Wcast-align,-Wcast-qual,-Wdouble-promotion,-Wduplicated-branches,-Wduplicated-cond,-Wformat-truncation,-Wformat=2,-Wlogical-op,-Wmisleading-indentation,-Wno-missing-include-dirs,-Wnon-virtual-dtor,-Wno-missing-declarations,-Wnon-virtual-dtor,-Wnull-dereference,-Woverloaded-virtual,-Wpointer-arith,-Wno-redundant-decls,-Wno-shadow,-Wno-switch-enum,-Wno-unknown-pragmas,-Wtrampolines,-Wuninitialized,-Wunused,-Wunused-but-set-variable,-Wunused-result,-Wno-zero-as-null-pointer-constant + #--Werror=cross-execution-space-call + #--Werror ext-lambda-captures-this + #--use_fast_math --extra-device-vectorization --restrict + -Xcudafe=--display_error_number + #-Xcudafe=--diag_error=incompatible_assignment_operands -Xcudafe=--diag_error=returning_ptr_to_local_variable -Xcudafe=--diag_error=subscript_out_of_range -Xcudafe=--diag_error=used_before_set -Xcudafe=--diag_error=undefined_preproc_id -Xcudafe=--diag_error=implicit_func_decl -Xcudafe=--diag_error=implicit_return_from_non_void_function -Xcudafe=--diag_error=missing_type_specifier + $<$,12>: + -Xcudafe=--diag_suppress=20011 # for std::string in test/allocator.cpp, more? + -Xcudafe=--diag_suppress=20014 + > + #-Xcudafe=--diag_suppress=1427 # for member_array_cast.cpp in msvc + > + $<$,$>,$>>: -Werror + $<$,12>:-ftrivial-auto-var-init=pattern> + # -WNSObject-attribute # (gcc 12, not in 11) + # -Wabi=13 -Wabi-tag # (maybe important when linking with very old libraries) + -Waddress + $<$,11>:-Waddress-of-packed-member> + -Wno-aggregate-return # (disallow return classes or structs, seems a C-compatibility warning) + -Waggressive-loop-optimizations + $<$,10>:-Wanalyzer-out-of-bounds> + -Waligned-new=all # (gcc 12, not in 11) + # -Walloc-size-larger-than= # (gcc 12, not in 11) + -Walloc-zero + -Walloca # -Walloca-larger-than= + $<$,11>:-Warith-conversion> + -Warray-bounds # -Warray-bounds=<0,2> + $<$,12>:-Warray-compare> + $<$,11>:-Warray-parameter> #=<0,2> # (gcc 11, not in gcc 10) + $<$,12>:-Wattribute-alias> #=<0,2> # (gcc 12, not in 11, not in 7) + $<$,9>:-Wattribute-warning> + -Wattributes + -Wbool-compare -Wbool-operation + $<$,12>:-Wbidi-chars -Wbidi-chars=any> + -Wbuiltin-declaration-mismatch -Wbuiltin-macro-redefined + #-Wc++-compat + -Wc++0x-compat -Wc++11-compat -Wc++14-compat -Wc++17-compat + $<$,12>:-Wc++17-extensions> + -Wc++1z-compat + $<$,11>:-Wc++20-compat> + $<$,12>:-Wc++20-extensions -Wc++23-extensions> + $<$,11>:-Wc++2a-compat> + $<$,9>:-Wcannot-profile> + $<$,8>:-Wcast-align=strict -Wcast-function-type> # (gcc 8, not in gcc 7) + -Wcast-qual + $<$,8>:-Wcatch-value> #=<0, 3> + -Wchar-subscripts + $<$,11>:-Wclass-conversion> + $<$,12>:-Wclass-memaccess> + -Wclobbered + $<$,12>:-Wcomma-subscript> + -Wcomment # (same as -Wcomments) + -Wconditionally-supported + -Wconversion -Wconversion-null + -Wcoverage-mismatch -Wcpp + $<$,12>:-Wctad-maybe-unsupported> + -Wctor-dtor-privacy + -Wdangling-else + $<$,12>:-Wdangling-pointer> + $<$,13>:-Wdangling-reference> + -Wdate-time + -Wdelete-incomplete -Wdelete-non-virtual-dtor + -Wdeprecated + $<$,11>:-Wdeprecated-copy -Wdeprecated-copy-dtor> # (gcc 11, not in gcc 8) + -Wdeprecated-declarations + $<$,11>:-Wdeprecated-enum-enum-conversion -Wdeprecated-enum-float-conversion> + -Wdisabled-optimization + -Wdiv-by-zero -Wdouble-promotion + -Wduplicated-branches -Wduplicated-cond + # -Weffc++ # (doesn't allow some advanced techniques, such as CRTP) + -Wempty-body -Wendif-labels + -Wenum-compare + $<$,11>:-Wenum-conversion> # (gcc 11, not in 10) + -Wexpansion-to-defined + $<$,11>:-Wexceptions> + $<$,8>:-Wextra-semi> + -Wfloat-conversion -Wfloat-equal + -Wformat=2 + -Wformat-contains-nul # (gcc 12, not in 11) + $<$,10>:-Wformat-diag> + -Wformat-extra-args -Wformat-nonliteral + -Wformat-overflow=1 + -Wformat-security -Wformat-signedness -Wformat-truncation -Wformat-y2k -Wformat-zero-length + -Wframe-address # -Wframe-larger-than= + -Wfree-nonheap-object -Whsa + # -Wif-not-aligned # (gcc 8, not in 7) + -Wignored-attributes + -Wignored-qualifiers + -Wimplicit-fallthrough #=3 # -Wimplicit-fallthrough=<0,5> + # -Winaccessible-base # (gcc 12, not in 11) + # -Winfinite-recursion # (gcc 12, not in 9) + -Winherited-variadic-ctor + #-Winit-list-lifetime # (gcc 12, not in 11) + -Winit-self + -Wno-inline # not all inline attempts can be successful + -Wint-in-bool-context -Wint-to-pointer-cast + # -Winterference-size # (gcc 12, not in 11) + # -Winvalid-imported-macros # (gcc 11, not in gcc 10) + -Winvalid-memory-model -Winvalid-offsetof -Winvalid-pch + # -Wlarger-than= # (disallow large objects types? in executable) + -Wliteral-suffix + -Wlogical-not-parentheses -Wlogical-op + # -Wlong-long # (C++98 warning) + -Wlto-type-mismatch -Wmain -Wmaybe-uninitialized + -Wmemset-elt-size -Wmemset-transposed-args + -Wmisleading-indentation + # -Wmismatched-dealloc -Wmismatched-new-delete # (gcc 11, not in gcc 10) + # -Wmismatched-tags # (gcc 11, not in 9) + # -Wmissing-attributes # (gcc 8, not in 8) + -Wmissing-braces -Wmissing-declarations -Wmissing-field-initializers -Wmissing-format-attribute -Wmissing-include-dirs -Wmissing-noreturn + #-Wmissing-profile # (gcc 11, not in gcc 8) + # -Wmissing-requires -Wmissing-template-keyword # (gcc 12, not in 11) + -Wmultichar + -Wno-multiple-inheritance # (disallows composition by inheritance) + # -Wmultistatement-macros # (gcc 8, not in 7) + -Wno-namespaces # (disallows use of namespaces, seems a C-tool) + -Wnarrowing + # -Wno-alloc-size-larger-than= -Wframe-larger-than= -Wno-larger-than -Wstack-usage= # (gcc 112, not in 11) + -Wnoexcept -Wnoexcept-type + -Wnon-template-friend -Wnon-virtual-dtor + -Wnonnull -Wnonnull-compare + -Wnormalized #=nfc -Wnormalized=[none|id|nfc|nfkc] + $<$,14>:-Wnrvo> # (not in gcc 13) + -Wnull-dereference + -Wodr + -Wold-style-cast # TODO(correaa) fix this + # -Wopenacc-parallelism # (gcc 12, not in 11) + -Wopenmp-simd -Woverflow + -Woverlength-strings -Woverloaded-virtual + -Wpacked -Wpacked-bitfield-compat + # -Wpacked-not-aligned # (gcc 8, not 7) + # -Wpadded # (disallows structs that need padding for alignment) + -Wparentheses + #-Wpessimizing-move # (gcc 11, not in gcc 8) + -Wplacement-new #=1 -Wplacement-new=<0,2> + -Wpmf-conversions + -Wpointer-arith -Wpointer-compare + -Wpragmas + #-Wprio-ctor-dtor # (gcc 11, not in gcc 8) + -Wpsabi + # -Wrange-loop-construct # (gcc 11, not in gcc 10) + -Wredundant-decls + #-Wredundant-move # (gcc 11, not in gcc 8) + # -Wredundant-tags # (gcc 11, not in gcc 9) + -Wregister + -Wreorder # (gcc 12, not in 11) + -Wreturn-local-addr -Wreturn-type + -Wrestrict -Wreorder + -Wscalar-storage-order -Wsequence-point + -Wshadow -Wshadow-compatible-local -Wshadow-local -Wshadow=compatible-local -Wshadow=local + -Wshift-count-negative -Wshift-count-overflow -Wshift-negative-value -Wshift-overflow #=1 -Wshift-overflow=<0,2> + -Wsign-compare -Wsign-conversion -Wsign-promo + -Wsized-deallocation + -Wsizeof-array-argument + # -Wsizeof-array-div # (gcc 11, not in 10) + -Wsizeof-pointer-memaccess + -Wstack-protector # -Wstack-usage= + -Wstrict-aliasing #=3 # -Wstrict-aliasing=<0,3> + -Wstrict-null-sentinel + # -Wstrict-overflow=<0,5> + -Wstrict-overflow=1 + # -Wstrict-overflow=2 # generates false positives when comparing pointers + # -Wstrict-overflow=3 # incompatible with index_range operator- + # -Wstrict-overflow=4 # not compatible with stl heap + # -Wstrict-overflow=5 # not compatible with Boost.Test + $<$,11>:-Wstring-compare> # (not in gcc 9) + -Wstringop-overflow #=2 -Wstringop-overflow=<0,4> + # -Wstringop-overread # (gcc 11, not in 10) + # -Wstringop-truncation # (gcc 8, not in 7) + -Wsubobject-linkage + # -Wsuggest-attribute=cold # (gcc 12, not in 11, not in 7) + -Wsuggest-attribute=const -Wsuggest-attribute=format + # -Wsuggest-attribute=malloc # (gcc 8, not in 7) + -Wsuggest-attribute=noreturn # -Wsuggest-attribute=pure # (false positive in gcc 8.5.0 RedHat) + -Wsuggest-final-methods -Wsuggest-final-types + -Wsuggest-override # (gcc 12, not in gcc 11) + -Wswitch -Wswitch-bool + -Wno-switch-default + -Wswitch-enum + $<$,10>:-Wswitch-outside-range> # (gcc 11, not in gcc 9) + -Wswitch-unreachable + -Wsync-nand -Wsynth + -Wno-system-headers # (expects system headers to be warning-compliant which they are not) + -Wtautological-compare + -Wno-templates # (disallows templates, C-tool) + $<$,12>:-Wterminate> # (gcc 12, not in 11) + -Wtrampolines -Wtrigraphs + $<$,12>:-Wtrivial-auto-var-init> # (not in gcc 11) + $<$,11>:-Wtsan> # (gcc 11, not in 10) + -Wtype-limits -Wundef -Wuninitialized + -Wno-unknown-pragmas # (see above) -Wunknown-pragmas (other compilers need their own pragmas for their warnings) + -Wunreachable-code -Wunsafe-loop-optimizations + -Wunused -Wunused-but-set-parameter -Wunused-but-set-variable + -Wunused-const-variable #=2 TODO(correaa) add [[maybe_unused]] to niebloids + -Wunused-function -Wunused-label -Wunused-local-typedefs -Wunused-macros -Wunused-parameter -Wunused-result -Wunused-value -Wunused-variable + # -Wuse-after-free # =<0,3> # (gcc 12, not in 11) + -Wuseless-cast + -Wvarargs -Wvariadic-macros -Wvector-operation-performance + # -Wvexing-parse # (gcc 11, not in gcc 10) + -Wvirtual-inheritance -Wvirtual-move-assign + -Wvla # -Wvla-larger-than= # (gcc 12, not in 11) + # -Wvla-parameter # (gcc 11, not in gcc 10) + # -Wvolatile # (gcc 11, not in gcc 9) + -Wvolatile-register-var + -Wwrite-strings + -Wzero-as-null-pointer-constant + # -Wzero-length-bounds # (gcc 12, not in 11) + > + $<$,$,$>: + $<$,12>:-ftrivial-auto-var-init=pattern> + -Wall -Wextra + -Wno-error=\#warnings # -Wno-\#warnings disable this working for a bug in rocm ~5.6 + -Wno-c++98-compat -Wno-c++98-compat-bind-to-temporary-copy -Wno-c++98-compat-pedantic + -Wno-poison-system-directories + #$<$,12>:-Wno-pre-c++17-compat> + -Wno-large-by-value-copy # subarray references can be large + #-Wno-padded # most classes, e.g. layouts, need padding + -Wstrict-aliasing -Wstrict-aliasing=0 -Wstrict-aliasing=1 -Wstrict-aliasing=2 + -Wstrict-overflow -Wstrict-overflow=0 -Wstrict-overflow=1 -Wstrict-overflow=2 -Wstrict-overflow=3 -Wstrict-overflow=4 -Wstrict-overflow=5 + -Wno-undefined-reinterpret-cast # needed to cast to c-array + -Wunknown-pragmas # other compilers need their own pragmas + # -Rpass -Rpass-analysis -Rpass-missed + # -Rsanitize-address # -Rsearch-path-usage + > + $<$: -Werror -Wall -Wextra # also IntelLLVM, XL (ibm), XLClang (ibm) + -diag-disable=remark + -diag-error:3846 + -diag-disable=1011 # disables warning missing return at the end of non-void function + -diag-disable=2196 # disables "error #2196: routine is both "inline" and "noinline"" in icpc 2021.5.0 + -wd161 + -Wabi + -Warray-bounds + -Wcast-qual + -Wchar-subscripts + -Wcomment + -Wdeprecated + -Wenum-compare + -Wextra-tokens + -Wformat -Wformat=2 -Wformat-security + -Wic-pointer + -Wnarrowing + -Wreturn-type + -Wnon-virtual-dtor + -Wnonnull + -Wmaybe-uninitialized + -Wmain + -Wmissing-declarations -Wmissing-prototypes + -Wmultichar + -Woverloaded-virtual + -Woverflow + -Wp64 + -Wparentheses + -Wpointer-arith + -Wpointer-sign + -Wreorder + -Wreturn-type + -Wsequence-point + -Wshadow + -Wsign-compare + -Wshorten-64-to-32 + -Wmissing-prototypes + -Wstrict-aliasing + -Wno-unknown-pragmas + -Wstrict-prototypes + -Wtrigraphs + -Wtype-limits + -Wuninitialized + -Wunused -Wunused-but-set-variable -Wunused-function -Wunused-parameter -Wunused-variable + -Wwrite-strings + > + $<$: + $<$,23>: + #-Wfatal-errors + -fmax-errors=2 + > + -Wall -Wextra # -Wpedantic + #-Wcast-qual -Wformat=2 -Wshadow -Wsign-compare -Wundef -Wuninitialized -Wunused + > + $<$: + $<$>: + /W4 # /volatile:iso /WX /Zc:wchar_t /Zc:forScope /Zc:inline + /wd4514 # unreferenced inline function has been removed + /wd4866 # compiler may not enforce left-to-right evaluation order for call to 'boost::multi::subarray >::operator[]' [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\test\array_ref.cpp.x.vcxproj] + /wd4868 # compiler may not enforce left-to-right evaluation order in braced initializer list + /wd5045 # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\include\boost\multi\adaptors\thrust\omp\test\omp.cpp.x.vcxproj] + /wd5031 # likely mismatch, popping warning state pushed in different file + > + $<$: + -Xcompiler=/W4 # /volatile:iso /WX /Zc:wchar_t /Zc:forScope /Zc:inline /W4 + -Xcompiler=/wd4464 # relative include path contains '..' + -Xcompiler=/wd4514 # unreferenced inline function has been removed + -Xcompiler=/wd4668 # warning C4668: '__NV_NO_HOST_COMPILER_CHECK' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' + -Xcompiler=/wd4866 # compiler may not enforce left-to-right evaluation order for call + -Xcompiler=/wd4868 # compiler may not enforce left-to-right evaluation order in braced initializer list + -Xcompiler=/wd5045 # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified [C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\build\include\boost\multi\adaptors\thrust\omp\test\omp.cpp.x.vcxproj] + > + > + ) + + file( + GLOB TEST_SRCS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + *.cpp + ) - target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include) - target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS} ) - target_link_libraries (${TEST_EXE} PRIVATE Boost::unit_test_framework ) - target_compile_definitions(${TEST_EXE} PUBLIC BOOST_TEST_MODULE="C++ Unit Tests for Multi ${TEST_EXE}") + if(NOT Boost_FOUND) + message(WARNING "post: Cannot find Boost, Multi library will have a very minimal test. If you want to test the library install Boost.Test, for example please run:\n sudo apt install libboost-test-dev\n sudo dnf install boost-devel") - target_compile_definitions(${TEST_EXE} PRIVATE "$<$:BOOST_PP_VARIADICS>") + add_executable(main main.cpp) + + target_link_libraries(main PUBLIC multi) + target_compile_features (main PUBLIC cxx_std_17) + target_include_directories(main PRIVATE ${PROJECT_SOURCE_DIR}/include) if(NOT DEFINED ENABLE_CIRCLE) - target_compile_options(${TEST_EXE} PRIVATE ${WARNS}) + target_compile_options(main PRIVATE ${WARNS}) else() - target_compile_definitions(${TEST_EXE} PRIVATE "BOOST_TEST_DYN_LINK=1") - target_compile_options (${TEST_EXE} PRIVATE -Werror -Wall) + target_compile_options (main PRIVATE -Werror -Wall) endif() - add_test(NAME ${TEST_EXE} COMMAND ./${TEST_EXE}) - endforeach() + add_test(NAME main COMMAND $) + + else() + + foreach(TEST_FILE ${TEST_SRCS}) + set(TEST_EXE "${TEST_FILE}.x") + add_executable(${TEST_EXE} ${TEST_FILE}) + if(ENABLE_CUDA) + set_source_files_properties(${TEST_FILE} PROPERTIES LANGUAGE CUDA) + endif() + + target_link_libraries(${TEST_EXE} PRIVATE multi) + target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) + + if(TBB_FOUND) + target_link_libraries (${TEST_EXE} PRIVATE TBB::tbb) + target_compile_definitions(${TEST_EXE} PUBLIC TBB_FOUND=1) + endif() + + set_property(TARGET ${TEST_EXE} PROPERTY COMPILE_WARNING_AS_ERROR ON) + + if(NOT DEFINED ENABLE_CIRCLE) + target_compile_options(${TEST_EXE} PRIVATE ${WARNS}) + else() + target_compile_options(${TEST_EXE} PRIVATE -Werror -Wall) + endif() + + set_target_properties(${TEST_EXE} PROPERTIES CXX_EXTENSIONS OFF) + + add_test(NAME ${TEST_EXE} COMMAND $) + endforeach() + + target_compile_options(allocator.cpp.x PRIVATE $<$:-Xcudafe=--diag_suppress=20014>) + target_compile_options(minimalistic_ptr.cpp.x PRIVATE $<$:-Xcudafe=--diag_suppress=20014>) + target_compile_options(move.cpp.x PRIVATE $<$:-Xcudafe=--diag_suppress=20014>) + target_compile_options(member_array_cast.cpp.x PRIVATE $<$:-Xcudafe=--diag_suppress=20014>) + target_compile_options(transform.cpp.x PRIVATE $<$:-Xcudafe=--diag_suppress=20014>) + target_compile_options(conor.cpp.x PRIVATE $<$:-Wno-psabi> ) + endif() endif() diff --git a/test/Jamfile b/test/Jamfile new file mode 100644 index 000000000..827e94736 --- /dev/null +++ b/test/Jamfile @@ -0,0 +1,93 @@ +# Copyright 2024 Matt Borland +# Distributed under the Boost Software License, Version 1.0. +# https://www.boost.org/LICENSE_1_0.txt + +import testing ; +import ../../config/checks/config : requires ; + +project : requirements + + # /boost/test//boost_unit_test_framework + + gcc:-Wall + gcc:-Wextra + + clang:-Wall + clang:-Wextra + + msvc:all + + # Additional flags by request + gcc:-Wsign-conversion + gcc:-Wconversion + #gcc:-Wundef # Fix pending in Boost.Test + gcc:-Wold-style-cast + #gcc:-Wduplicated-branches + gcc:-Wfloat-equal + + clang:-Wsign-conversion + clang:-Wconversion + clang:-Wundef + clang:-Wold-style-cast + clang:-Wfloat-equal + + # Clang-Cl likes to blow up builds with these compatibility checks + clang:-Wno-c++98-compat + clang:-Wno-c++98-compat-pedantic + clang:-Wno-c++17-compat + clang:-Wno-c++14-compat + clang:"_CRT_SECURE_NO_WARNINGS" + + msvc:on + #clang:on + gcc:on + + [ requires cxx17_if_constexpr cxx17_structured_bindings cxx17_std_apply ] + ; + +run allocator.cpp ; +run array_cref.cpp ; +run array_fancyref.cpp ; +run array_legacy_c.cpp ; +run array_ptr.cpp ; +run array_ref.cpp ; +run assignments.cpp ; +#run boost_array_concept.cpp ; +run comparisons.cpp ; +run concepts.cpp ; +run constructors.cpp ; +run conversions.cpp ; +run diagonal.cpp ; +run element_access.cpp ; +run element_transformed.cpp ; +#run execution_policy.cpp ; +run fill.cpp ; +run fix_complex.cpp ; +run flatted.cpp ; +run index_range.cpp ; +run initializer_list.cpp ; +run iterator.cpp ; +run layout.cpp ; +run member_array_cast.cpp ; +run minimalistic_ptr.cpp ; +run move.cpp ; +run nico_const_correctness.cpp ; +run one_based.cpp ; +run overload.cpp ; +run partitioned.cpp ; +run pmr.cpp ; +run ranges.cpp ; +run reextent.cpp ; +run reinterpret_array_cast.cpp ; +run reversed.cpp ; +run rotated.cpp ; +run scoped_allocator.cpp ; +run select_column.cpp ; +run sliced.cpp ; +run sort.cpp ; +run static_array_cast.cpp ; +run std_vector_substitutability.cpp ; +run subrange.cpp ; +run transform.cpp ; +run utility.cpp ; +run zero_dimensionality.cpp ; diff --git a/test/allocator.cpp b/test/allocator.cpp index c155d9c26..cb49ac049 100644 --- a/test/allocator.cpp +++ b/test/allocator.cpp @@ -1,141 +1,636 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi allocators" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#include +#include // TODO(correaa) export IWYU -#include +#include -#if defined(__cpp_lib_memory_resource) and (__cpp_lib_memory_resource >= 201603L) -#include // for polymorphic memory resource, monotonic buffer +#include // for transform, is_sorted +#include // for array, operator== +#include // for complex +#include // for __GLIBCXX__, size_t +#include // for size, back_insert... +#include // for make_unique, uniq... +#ifdef BOOST_MULTI_HAS_MEMORY_RESOURCE +#include // for monotonic_buffer_... #endif - -#include +#include // for operator new // NOLINT(misc-include-cleaner) +#include // for basic_string, string +#include // for move, forward +#include // for vector, allocator +// IWYU pragma: no_include // for __GLIBCXX__ // NOLINT(misc-include-cleaner) namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(std_vector_of_arrays) { - std::vector> va; - std::transform( - begin(multi::iextension(3)), end(multi::iextension(3)), - std::back_inserter(va), - [](auto idx){return multi::array({idx, idx}, static_cast(idx));} - ); - - BOOST_REQUIRE( size(va[0]) == 0 ); - BOOST_REQUIRE( size(va[1]) == 1 ); - BOOST_REQUIRE( size(va[2]) == 2 ); - BOOST_REQUIRE( va[1] [0][0] == 1 ); - BOOST_REQUIRE( va[2] [0][0] == 2 ); - - std::vector> const wa = { // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - multi::array({0, 0}, 0.0), - multi::array({1, 1}, 1.0), - multi::array({2, 2}, 2.0), - }; - BOOST_REQUIRE( size(va) == size(wa) ); - BOOST_REQUIRE( va == wa ); - - std::vector> ua(3, std::allocator>{}); - auto iex = multi::iextension(static_cast(ua.size())); - std::transform( - begin(iex), end(iex), - begin(ua), - [](auto idx) {return multi::array({idx, idx}, static_cast(idx));} - ); - BOOST_REQUIRE( ua == va ); -} +template +using small_array = multi::static_array>; +// https://godbolt.org/z/d8ozWahna -BOOST_AUTO_TEST_CASE(array1d_of_arrays2d) { - multi::array, 1> arr(multi::extensions_t<1>(multi::iextension{10}), multi::array{}); - BOOST_REQUIRE( size(arr) == 10 ); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // static_array_allocator + { + multi::array const ma({2, 3}, 99); + multi::static_array> const sma(ma(), std::allocator{}); + BOOST_TEST( sma == ma ); + } - std::transform( - begin(extension(arr)), end(extension(arr)), begin(arr), - [](auto idx) {return multi::array({idx, idx}, static_cast(idx));} - ); + // BOOST_AUTO_TEST_CASE(empty_stride) + { + multi::array ma; + BOOST_TEST(ma.size() == 0); + BOOST_TEST(ma.stride() != 0); + BOOST_TEST(size(ma) == 0); - BOOST_REQUIRE( size(arr[0]) == 0 ); - BOOST_REQUIRE( size(arr[1]) == 1 ); - BOOST_REQUIRE( size(arr[8]) == 8 ); - BOOST_REQUIRE( arr[8][4][4] == 8.0 ); -} + multi::array ma0({0, 0}, 0.0); + BOOST_TEST(ma0.size() == 0); + BOOST_TEST(ma0.stride() != 0); +#ifndef _MSC_VER // doesn't work with msvc 14.3 c++17 permissive mode + BOOST_TEST(size(ma0) == 0); +#endif + } + + // 4D array + { + multi::array, 4> ma(multi::extensions_t<4>({6, 12, 24, 12})); + BOOST_TEST( ma.size() == 6); + + ma[1][2][3][4] = std::complex{1.0, 2.0}; + BOOST_TEST(( ma[1][2][3][4] == std::complex{1.0, 2.0} )); + } + + // BOOST_AUTO_TEST_CASE(std_vector_of_arrays_check_size) + { + multi::array const ma; + BOOST_TEST( ma.size() == 0 ); + BOOST_TEST( ma.num_elements() == 0 ); + + std::vector> va(1); // NOLINT(fuchsia-default-arguments-calls) vector + + BOOST_TEST( va[0].size() == 0 ); + } + + // BOOST_AUTO_TEST_CASE(std_vector_of_arrays_manual_emplaceback_ctor) + { + std::vector> va; + + va.emplace_back(multi::extensions_t<2>{3, 3}, 3); + va.emplace_back(multi::extensions_t<2>{2, 2}, 2); + va.emplace_back(multi::extensions_t<2>{1, 1}, 1); + va.emplace_back(multi::extensions_t<2>{0, 0}, 0); + } + + // BOOST_AUTO_TEST_CASE(std_vector_of_arrays_manual_emplaceback) + { + std::vector> va; + + va.emplace_back(multi::array({2, 2}, 2)); + va.emplace_back(multi::array({1, 1}, 1)); + va.emplace_back(multi::array({0, 0}, 0)); + } + + // BOOST_AUTO_TEST_CASE(std_vector_of_arrays_manual_pushback) + { + std::vector> va; + + va.push_back(multi::array({2, 2}, 2)); + va.push_back(multi::array({1, 1}, 1)); + va.push_back(multi::array({0, 0}, 0)); + } -BOOST_AUTO_TEST_CASE(array_3d_of_array_2d) { - multi::array, 2> AA({10, 20}, multi::array{}); - std::transform(extension(AA).begin(), extension(AA).end(), AA.begin(), AA.begin(), [](auto idx, auto&& row) -> decltype(row) { - std::transform(extension(row).begin(), extension(row).end(), row.begin(), [idx](auto jdx) { - return multi::array({idx + jdx, idx + jdx, idx + jdx}, 99.0); + // BOOST_AUTO_TEST_CASE(std_vector_of_arrays) + { + std::vector> va; + + std::transform( + multi::iextension(3).begin(), multi::iextension(3).end(), + std::back_inserter(va), + [](auto idx) { return multi::array({idx, idx}, static_cast(idx)); } + ); + + // #ifndef _MSC_VER // doesn't work with msvc 14.3 c++17 permissive mode + BOOST_TEST( size(va[0]) == 0 ); + BOOST_TEST( size(va[1]) == 1 ); + BOOST_TEST( size(va[2]) == 2 ); + // #endif + + BOOST_TEST( va[1] [0][0] == 1 ); + BOOST_TEST( va[2] [0][0] == 2 ); + + using namespace std::string_literals; // NOLINT(build/namespaces) + + // #ifndef _MSC_VER // doesn't work with msvc 14.3 c++17 permissive mode + // NOLINTNEXTLINE(fuchsia-default-arguments-calls) + std::vector> const wa = { + multi::array({0, 0}, 0), + multi::array({1, 1}, 1), + multi::array({2, 2}, 2), + }; + // #else + // // NOLINTNEXTLINE(fuchsia-default-arguments-calls) + // std::vector> const wa = { + // multi::array(multi::extensions_t<2>(0, 0), 0), + // multi::array(multi::extensions_t<2>(1, 1), 1), + // multi::array(multi::extensions_t<2>(2, 2), 2), + // }; + // #endif + + BOOST_TEST( va.size() == wa.size() ); + BOOST_TEST( va == wa ); + + std::vector> ua(3, std::allocator>{}); + + auto iex = multi::iextension(static_cast(ua.size())); + + std::transform( + begin(iex), end(iex), + begin(ua), + [](auto idx) { return multi::array({idx, idx}, static_cast(idx)); } + ); + BOOST_TEST( ua == va ); + } + + // BOOST_AUTO_TEST_CASE(std_vector_of_arrays_with_string_instead_of_int) + { + // NOLINTBEGIN(fuchsia-default-arguments-calls) // string uses default parameter + std::vector> va; + std::transform( + begin(multi::iextension(3)), end(multi::iextension(3)), + std::back_inserter(va), + [](auto idx) { return multi::array({idx, idx}, std::to_string(idx)); } + ); + + // #ifndef _MSC_VER // doesn't work with msvc 14.3 c++17 permissive mode + BOOST_TEST( size(va[0]) == 0 ); + BOOST_TEST( size(va[1]) == 1 ); + BOOST_TEST( size(va[2]) == 2 ); + // #endif + using namespace std::string_literals; // NOLINT(build/namespaces) + + BOOST_TEST( va[1] [0][0] == "1"s ); // NOLINT(misc-include-cleaner) bug in clang-tidy 18 + BOOST_TEST( va[2] [0][0] == "2"s ); + + std::vector> const wa = { + multi::array({0, 0}, "0"s), + multi::array({1, 1}, "1"s), + multi::array({2, 2}, "2"s), + }; + +#ifndef _MSC_VER // doesn't work with msvc 14.3 c++17 permissive mode + BOOST_TEST( size(va) == size(wa) ); +#endif + BOOST_TEST( va == wa ); + + std::vector> ua(3, std::allocator>{}); + + auto iex = multi::iextension(static_cast(ua.size())); + + std::transform( + begin(iex), end(iex), + begin(ua), + [](auto idx) { return multi::array({idx, idx}, std::to_string(idx)); } + ); + + BOOST_TEST( ua == va ); + + // NOLINTEND(fuchsia-default-arguments-calls) // string uses default parameter + } + +// TODO(correaa) make this code work with nvcc compiler (non device function called from device host through adl uninitialized_fill) +#if !(defined(__NVCC__) || defined(__HIP_PLATFORM_NVIDIA__) || defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)) + // BOOST_AUTO_TEST_CASE(array1d_of_arrays2d) + { + multi::array, 1> arr(multi::extensions_t<1>(multi::iextension{10}), multi::array{}); + BOOST_TEST( size(arr) == 10 ); + + std::transform( + begin(extension(arr)), end(extension(arr)), begin(arr), + [](auto idx) { return multi::array({idx, idx}, std::to_string(idx)); } + ); + + BOOST_TEST( size(arr[0]) == 0 ); + BOOST_TEST( size(arr[1]) == 1 ); + BOOST_TEST( size(arr[8]) == 8 ); + + using namespace std::string_literals; // NOLINT(build/namespaces) + BOOST_TEST( arr[8][4][4] == "8"s ); + } + + // BOOST_AUTO_TEST_CASE(array_3d_of_array_2d) + { + multi::array, 2> AA({10, 20}, multi::array{}); + std::transform(AA.extension().begin(), AA.extension().end(), AA.begin(), AA.begin(), [](auto idx, auto&& row) -> decltype(row) { + std::transform(row.extension().begin(), row.extension().end(), row.begin(), [idx](auto jdx) { + return multi::array({idx + jdx, idx + jdx, idx + jdx}, 99); + }); + return std::forward(row); }); - return std::forward(row); - }); - BOOST_REQUIRE( size(AA[9][19]) == 9 + 19 ); - BOOST_REQUIRE( AA[9][19][1][1][1] == 99.0 ); -} + // BOOST_TEST( AA[9][19].size() == 9 + 19 ); + + // BOOST_TEST( std::size(AA[9][19]) == 9 + 19 ); // doesn't work on nvhpc 22.11 + // BOOST_TEST( size(AA[9][19]) == 9 + 19 ); -BOOST_AUTO_TEST_CASE(array_3d_of_array_2d_no_init) { - multi::array, 2> AA({10, 20}); - std::transform(extension(AA).begin(), extension(AA).end(), AA.begin(), AA.begin(), [](auto idx, auto&& row) -> decltype(row) { - std::transform(extension(row).begin(), extension(row).end(), row.begin(), [idx](auto jdx) { - return multi::array({idx + jdx, idx + jdx, idx + jdx}, 99.0); + // BOOST_TEST( AA[9][19][1][1][1] == 99 ); + } +#endif + + // BOOST_AUTO_TEST_CASE(array_3d_of_array_2d_no_init) + { + multi::array, 2> AA({10, 20}); + std::transform(extension(AA).begin(), extension(AA).end(), AA.begin(), AA.begin(), [](auto idx, auto&& row) -> decltype(row) { + std::transform(extension(row).begin(), extension(row).end(), row.begin(), [idx](auto jdx) { + return multi::array({idx + jdx, idx + jdx, idx + jdx}, 99); + }); + return std::forward(row); }); - return std::forward(row); - }); - BOOST_REQUIRE( size(AA[9][19]) == 9 + 19 ); - BOOST_REQUIRE( AA[9][19][1][1][1] == 99. ); -} + BOOST_TEST( AA[9][19].size() == 9 + 19 ); + // BOOST_TEST( std::size(AA[9][19]) == 9 + 19 ); // doesn't work on nvhpc 22.11 + BOOST_TEST( size(AA[9][19]) == 9 + 19 ); + BOOST_TEST( AA[9][19][1][1][1] == 99 ); + } -BOOST_AUTO_TEST_CASE(const_elements) { - auto ptr = std::make_unique(2.0); -// *ptr = 3.0; // ok, can't assign - BOOST_REQUIRE( *ptr == 2.0 ); + // BOOST_AUTO_TEST_CASE(const_elements) + { + auto ptr = std::make_unique(2); + // ok, can't assign // *ptr = 3.0; + BOOST_TEST( *ptr == 2 ); + } -// multi::array> arr({10, 10}, 99.0); -// -// BOOST_REQUIRE( arr[1][2] == 99.0 ); -} +#ifdef BOOST_MULTI_HAS_MEMORY_RESOURCE + // BOOST_AUTO_TEST_CASE(pmr) + { + std::array buffer = { + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'} + }; -#if defined(__cpp_lib_memory_resource) and (__cpp_lib_memory_resource >= 201603L) -BOOST_AUTO_TEST_CASE(pmr) { - std::array buffer = {{'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'}}; - std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; + std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; - multi::array> Aarr({2, 2}, 'a', &pool); - multi::array> Barr({3, 2}, 'b', &pool); + multi::array> Aarr({2, 2}, 'x', &pool); + Aarr[0][0] = 'x'; + Aarr[0][1] = 'y'; + Aarr[1][0] = 'z'; + Aarr[1][1] = '&'; - BOOST_REQUIRE(( buffer == std::array{{'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'X', 'X', 'X'}} )); + multi::array> Barr({3, 2}, 'o', &pool); - BOOST_REQUIRE(Aarr[0][0] == 'a'); - BOOST_REQUIRE(Barr[0][0] == 'b'); -} + BOOST_TEST(( buffer != std::array{{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'}} )); + +#ifdef __GLIBCXX__ + BOOST_TEST(( buffer == std::array{{'x', 'y', 'z', '&', 'o', 'o', 'o', 'o', 'o', 'o', 'A', 'B', 'C'}} )); #endif +#ifdef _LIBCPP_VERSION + BOOST_TEST(( buffer == std::array{{'0', '1', '2', 'o', 'o', 'o', 'o', 'o', 'o', 'x', 'y', 'z', '&'}} )); +#endif + + BOOST_TEST(Aarr[0][0] == 'x'); + BOOST_TEST(Barr[0][0] == 'o'); + } -#if(MULTI_PROVIDES_PMR_ARRAY) -BOOST_AUTO_TEST_CASE(pmr2) { + // BOOST_AUTO_TEST_CASE(pmr2) + { + // clang-format off std::array buffer = {{'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'}}; - std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; + // clang-format on - multi::pmr::array Aarr({2, 2}, 'a', &pool); - multi::pmr::array Barr({3, 2}, 'b', &pool); + std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)}; - BOOST_REQUIRE(( buffer == std::array{{'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'X', 'X', 'X'}} )); +#ifndef _MSC_VER + multi::pmr::array Aarr({2, 2}, 'a', &pool); + multi::pmr::array Barr({3, 2}, 'b', &pool); +#else + multi::pmr::array Aarr(multi::extensions_t<2>{2, 2}, 'a', &pool); + multi::pmr::array Barr(multi::extensions_t<2>{3, 2}, 'b', &pool); +#endif - BOOST_REQUIRE(Aarr[0][0] == 'a'); - BOOST_REQUIRE(Barr[0][0] == 'b'); -} +#ifdef __GLIBCXX__ + BOOST_TEST(( buffer == std::array{{'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'X', 'X', 'X'}} )); +#endif +#ifdef _LIBCPP_VERSION + BOOST_TEST(( buffer == std::array{{'X', 'X', 'X', 'b', 'b', 'b', 'b', 'b', 'b', 'a', 'a', 'a', 'a'}} )); +#endif -BOOST_AUTO_TEST_CASE(pmr_double_uninitialized) { - std::array buffer = {{4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 999.9, 999.9, 999.9, 999.9}}; - std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12*sizeof(double)}; + BOOST_TEST(Aarr[0][0] == 'a'); + BOOST_TEST(Barr[0][0] == 'b'); + } - multi::pmr::array Aarr({2, 2}, &pool); + // BOOST_AUTO_TEST_CASE(pmr_double_uninitialized) + { + std::array buffer{ + {4, 5, 6, 7, 8, 9, 10, 11, 996, 997, 998, 999} + }; - BOOST_TEST( buffer[0] == 4.0 ); - BOOST_TEST( buffer[1] == 5.0 ); + std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12 * sizeof(int)}; - BOOST_REQUIRE(Aarr[0][0] == 4.0); -} + multi::pmr::array Aarr({2, 2}, &pool); + + BOOST_TEST( buffer[0] == 4 ); + BOOST_TEST( buffer[1] == 5 ); + +#ifdef __GLIBCXX__ + BOOST_TEST(Aarr[0][0] == 4); +#endif +#ifdef _LIBCPP_VERSION + BOOST_TEST(Aarr[0][0] == 996); +#endif + } #endif + + // BOOST_AUTO_TEST_CASE(static_allocator) + { + using T = int; + multi::detail::static_allocator sa{}; + + auto* pp = sa.allocate(10); + + new (std::next(pp, 8)) T{42}; + + BOOST_TEST( *std::next(pp, 8) == 42 ); + // (pp + 8)->~double(); + sa.deallocate(pp, 10); + } + +// Clang-20 likely has incompatibility with GCC-14 here since the error points to the STL: +/* + *libs/boost-multi/test/allocator.cpp:378:18: error: constexpr variable 'gg' must be initialized by a constant expression + 378 | constexpr auto gg = g(); + | ^ ~~~ +/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_algobase.h:403:18: note: assignment to object outside its lifetime is not allowed in a constant expression + 403 | *__result = *__first; + | ~~~~~~~~~~^~~~~~~~~~ +/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_algobase.h:517:9: note: in call to '__copy_m>(&{4, 5, 6}[0], &{4, 5, 6}[3], {{}, {{}, {}}, {{}}, {{{{}}}}, {}, &{*new int[3]#0}[0], 1})' + 517 | return std::__copy_move<_IsMove, false, _Category>:: + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 518 | __copy_m(__first, __last, __result); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_algobase.h:548:14: note: in call to '__copy_move_a2>(&{4, 5, 6}[0], &{4, 5, 6}[3], {{}, {{}, {}}, {{}}, {{{{}}}}, {}, &{*new int[3]#0}[0], 1})' + 548 | { return std::__copy_move_a2<_IsMove>(__first, __last, __result); } + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_algobase.h:556:3: note: in call to '__copy_move_a1>(&{4, 5, 6}[0], &{4, 5, 6}[3], {{}, {{}, {}}, {{}}, {{{{}}}}, {}, &{*new int[3]#0}[0], 1})' + 556 | std::__copy_move_a1<_IsMove>(std::__niter_base(__first), + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 557 | std::__niter_base(__last), + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + 558 | std::__niter_base(__result))); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/stl_algobase.h:650:14: note: in call to '__copy_move_a>(&{4, 5, 6}[0], &{4, 5, 6}[3], {{}, {{}, {}}, {{}}, {{{{}}}}, {}, &{*new int[3]#0}[0], 1})' + 650 | return std::__copy_move_a<__is_move_iterator<_II>::__value> + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 651 | (std::__miter_base(__first), std::__miter_base(__last), __result); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +./boost/multi/detail/adl.hpp:306:11: note: in call to 'copy>(&{4, 5, 6}[0], &{4, 5, 6}[3], {{}, {{}, {}}, {{}}, {{{{}}}}, {}, &{*new int[3]#0}[0], 1})' + 306 | return std:: copy(first, last, d_first); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +./boost/multi/detail/adl.hpp:327:93: note: (skipping 5 calls in backtrace; use -fconstexpr-backtrace-limit=0 to see all) + 327 | template constexpr auto operator()(As&&... args) const BOOST_MULTI_DECLRETURN(_(priority<6>{}, std::forward(args)...)) + | ^ +./boost/multi/array.hpp:271:55: note: in call to 'static_array(&{4, 5, 6}[0], &{4, 5, 6}[3], allocator_type{})' + 271 | constexpr explicit static_array(It first, It last) : static_array(first, last, allocator_type{}) {} + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +./boost/multi/array.hpp:1215:35: note: in call to 'static_array(&{4, 5, 6}[0], &{4, 5, 6}[3])' + 1215 | using static_array::static_array; // MSVC wants fullname here? // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) passing c-arrays to base + | ^~~~~~~~~~~~ +./boost/multi/array.hpp:1232:42: note: in implicit initialization for inherited constructor of 'array' + 1232 | : static_{(ilv.size()==0)?array():array(ilv.begin(), ilv.end())} { + | ^ +libs/boost-multi/test/allocator.cpp:364:4: note: in call to 'array({&{4, 5, 6}[0], 3})' + 364 | {4, 5, 6}, + | ^~~~~~~~~ +libs/boost-multi/test/allocator.cpp:378:23: note: in call to 'g.operator()()' + 378 | constexpr auto gg = g(); + | ^~~ +libs/boost-multi/test/allocator.cpp:379:17: error: static assertion expression is not an integral constant expression + 379 | static_assert(gg == 10); + | ^~~~~~~~ +libs/boost-multi/test/allocator.cpp:379:17: note: initializer of 'gg' is not a constant expression +libs/boost-multi/test/allocator.cpp:378:18: note: declared here + 378 | constexpr auto gg = g(); + | ^ +2 errors generated. +*/ +#if defined(__cpp_constexpr) && (__cpp_constexpr > 202306L) && (!defined(__clang__) || __clang_major__ != 20) + auto f = []() { + std::vector v = {1, 2, 3}; + return v.size(); + }; + + // constexpr_allocator_vector + { + static_assert(f() == 3); + BOOST_TEST( f() == 3 ); + } + + auto g = []() { + multi::array arr = { + {4, 5, 6}, + {1, 2, 3}, + {7, 8, 9}, + }; + std::sort(arr.begin(), arr.end()); + for(auto it = arr.diagonal().begin(); it != arr.diagonal().end(); ++it) { + *it += 5; + } + auto ret = arr[1][1]; + return ret; + }; + + // constexpr_allocator + { + constexpr auto gg = g(); + static_assert(gg == 10); + BOOST_TEST( gg == 10 ); + } +#endif + +#if !defined(_MSC_VER) // static allocator does not work with MSVC implementation pf vector + // BOOST_AUTO_TEST_CASE(static_allocator_on_vector_int) + { + std::vector> vv(10, 42); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( vv[3] == 42 ); + + // auto ww = vv; + // BOOST_TEST( ww[3] == 42 ); + + // ww[3] = 51; + // BOOST_TEST( ww[3] == 51 ); + // BOOST_TEST( vv[3] == 42 ); + + // auto xx = std::move(ww); + // BOOST_TEST( ww.empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + // BOOST_TEST( vv[3] == 42 ); + // BOOST_TEST( xx[3] == 51 ); + + // { + // std::vector>> const VV = {vv, xx, vv}; // NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( VV.size() == 3 ); + // } + } + + // BOOST_AUTO_TEST_CASE(static_allocator_on_vector_string) + { + std::string const cat = "catcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcatcat"; // NOLINT(fuchsia-default-arguments-calls) + std::string const dog = "dogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdogdog"; // NOLINT(fuchsia-default-arguments-calls) + + std::vector> vv(10, cat); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( vv[3] == cat ); + + auto ww = vv; + BOOST_TEST( ww[3] == cat ); + + ww[3] = dog; + BOOST_TEST( ww[3] == dog ); // cppcheck-suppress knownConditionTrueFalse ; + BOOST_TEST( vv[3] == cat ); + + auto xx = std::move(ww); + BOOST_TEST( vv[3] == cat ); + BOOST_TEST( xx[3] == dog ); + BOOST_TEST( ww.empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + + // vv.resize(15); + + // swap(xx, vv); + // BOOST_TEST( vv[3] == dog ); + // BOOST_TEST( xx[3] == cat ); + + { + std::vector>> const VV = {vv, xx, vv}; // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( VV.size() == 3 ); + // swap(VV[0], VV[1]); + // std::sort(VV.begin(), VV.end()); + // BOOST_TEST( std::is_sorted(VV.begin(), VV.end()) ); + // VV.resize(10, xx); + // std::sort(VV.begin(), VV.end()); + // BOOST_TEST( std::is_sorted(VV.begin(), VV.end()) ); + } + } +#endif + +#if !defined(_MSC_VER) || (_MSC_VER > 193030706) // TODO(correaa) doesn't work on MSVC 14.3 in c++17 mode + // BOOST_AUTO_TEST_CASE(small_array_int) { + // std::vector> vv(5, std::vector(10, 99)); + // multi::array_ref, 2> ww(vv.data() + 1, {2, 2}); + // // multi::what(ww.element_moved()); + // multi::static_array, 2> sa(ww.element_moved()); + + // //BOOST_TEST( ww[0][0].empty() ); + // } + + // BOOST_AUTO_TEST_CASE(small_array_int) + { + small_array vv({4, 4}, 42); + + BOOST_TEST( vv[3][3] == 42 ); + + auto ww = vv; + + BOOST_TEST( ww[3][3] == 42 ); + BOOST_TEST( ww.base() != vv.base() ); + + auto const* wwb = ww.base(); + auto const* vvb = vv.base(); + + ww[3][3] = 51; + + BOOST_TEST( ww[3][3] == 51 ); // cppcheck-suppress knownConditionTrueFalse ; + BOOST_TEST( vv[3][3] == 42 ); + + swap(ww, vv); + + BOOST_TEST( vv[3][3] == 51 ); + BOOST_TEST( ww[3][3] == 42 ); + + BOOST_TEST( ww.base() == wwb ); + BOOST_TEST( vv.base() == vvb ); + + auto xx{std::move(ww)}; + + BOOST_TEST( vv[3][3] == 51 ); + // BOOST_TEST( xx[3][3] == 42 ); + // BOOST_TEST( ww[3][3] == 42 ); + BOOST_TEST( xx.base() != vv.base() ); + // BOOST_TEST( ww.empty() ); + + small_array yy({4, 4}); + yy = vv; + + BOOST_TEST( yy == vv ); // cppcheck-suppress knownConditionTrueFalse ; + + yy = std::move(vv); + BOOST_TEST( vv.size() == 4 ); // NOLINT(clang-analyzer-cplusplus.Move,bugprone-use-after-move,hicpp-invalid-access-moved) + + { + std::vector> VV = {vv, xx, vv}; // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( VV.size() == 3 ); + + swap(VV[0], VV[1]); + BOOST_TEST( VV[0] == xx ); + BOOST_TEST( VV[1] == vv ); + + std::sort(VV.begin(), VV.end()); // NOLINT(modernize-use-ranges) for C++20 + BOOST_TEST( std::is_sorted(VV.begin(), VV.end()) ); // NOLINT(modernize-use-ranges) for C++20 + + VV.resize(10, xx); + std::sort(VV.begin(), VV.end()); // NOLINT(modernize-use-ranges) for C++20 + BOOST_TEST( std::is_sorted(VV.begin(), VV.end()) ); // NOLINT(modernize-use-ranges) for C++20 + } + } +#endif + + // BOOST_AUTO_TEST_CASE(props_of_static_allocator) + { + { + std::vector vv(20, 11); // NOLINT(fuchsia-default-arguments-calls) + std::vector ww = vv; + BOOST_TEST( ww == vv ); // cppcheck-suppress knownConditionTrueFalse ; + + ww = vv; + BOOST_TEST( ww == vv ); // cppcheck-suppress knownConditionTrueFalse ; + + ww = std::move(vv); + BOOST_TEST( vv.size() == 0 ); // NOLINT(readability-container-size-empty,bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + + std::vector xx(20, 22); // NOLINT(fuchsia-default-arguments-calls) + swap(ww, xx); + BOOST_TEST( ww == std::vector(20, 22) ); // NOLINT(fuchsia-default-arguments-calls) + } +#if !defined(_MSC_VER) // static_allocator doesn't work with MSVC implementation of vector + { + std::vector> vv(20, 11); // NOLINT(fuchsia-default-arguments-calls) + std::vector> ww = vv; + BOOST_TEST( ww == vv ); // cppcheck-suppress knownConditionTrueFalse ; for testing purposes + + ww = vv; + BOOST_TEST( ww == vv ); // cppcheck-suppress knownConditionTrueFalse ; for testing purposes + + ww = std::move(vv); + BOOST_TEST( vv.size() == 0 ); // NOLINT(readability-container-size-empty,bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + + std::vector> xx(20, 22); // NOLINT(fuchsia-default-arguments-calls) + swap(ww, xx); + BOOST_TEST(( ww == std::vector>(20, 22) )); // NOLINT(fuchsia-default-arguments-calls) + } +#endif + } + + // BOOST_AUTO_TEST_CASE(assignment_2D_mutant) + { + multi::array arr = { + {10, 20, 30}, + {40, 50, 60}, + }; + multi::array const arr2 = { + {11, 22, 33}, + {44, 55, 66}, + }; + + arr = arr2; + + BOOST_TEST( arr[0][0] == 11 ); + } + + return boost::report_errors(); +} diff --git a/test/apl.cpp b/test/apl.cpp new file mode 100644 index 000000000..ea15c0162 --- /dev/null +++ b/test/apl.cpp @@ -0,0 +1,129 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#if defined(__NVCC__) && !defined(_MSC_VER) +#pragma nv_diag_suppress 177 // Suppress warning number 177 variable "apl::symbols::::\u019f" was declared but never referenced +#endif + +#include + +// #if (__cplusplus >= 202002L) +// # include +// #endif +#include + +#include +#include + +namespace multi = boost::multi; + +namespace apl { + +namespace { + +template> +constexpr auto iota(Extensions const& exts) { + auto ext = multi::extension_t{std::ptrdiff_t{0}, exts.num_elements()}; + auto beg = ext.begin(); // std::views::iota(0, exts.num_elements()).begin(); + return multi::array_ref>(exts, beg); +} + +template +constexpr auto iota([[maybe_unused]] Es... es) { // for nvcc 14 + ((void)es, ...); + return iota(multi::extensions_t(sizeof...(Es))>{es...}); +} + +} // end namespace + +namespace symbols { + +namespace { +#if !defined(__NVCOMPILER) && (!defined(__GNUC__) || __GNUC__ > 9) +// cppcheck-suppress [syntaxError] -begin +template [[maybe_unused]] auto ι(Es... es) { return iota(es...); } +#endif +// cppcheck-suppress [syntaxError] -end +} // end namespace + +} // end namespace symbols + +[[maybe_unused]] constexpr auto const Zilde = iota<1>(multi::extensions_t<1>{std::ptrdiff_t{0}}); + +namespace symbols { + +namespace { + +#if !defined(__GNUC__) || __GNUC__ > 9 +#if !defined(__NVCOMPILER) +[[maybe_unused]] constexpr auto const& Ɵ = Zilde; // NOLINT(misc-confusable-identifiers) +#if !defined(_MSC_VER) +[[maybe_unused]] constexpr auto const& θ = Zilde; // NOLINT(misc-confusable-identifiers) +[[maybe_unused]] constexpr auto const& Ө = Zilde; // NOLINT(misc-confusable-identifiers) +[[maybe_unused]] constexpr auto const& ϑ = Zilde; // NOLINT(misc-confusable-identifiers) +[[maybe_unused]] constexpr auto const& Ø = Zilde; +#endif + +#if !defined(_MSC_VER) +#if !defined(__GNUC__) || __GNUC__ > 9 +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wc99-compat" +#endif +[[maybe_unused]] constexpr auto const& ϴ = Zilde; // NOLINT(misc-confusable-identifiers) +#endif +#endif +#endif +#endif + +struct underscore_t { +#if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + template + [[maybe_unused]] +#if (__cpp_multidimensional_subscript >= 202211L) + static +#endif + constexpr auto operator[](U u, Us... us) +#if !(__cpp_multidimensional_subscript >= 202211L) + const +#endif + { + if constexpr(std::is_same_v) { + return multi::array{u, us...}; + } else { + return multi::array{u, us...}; + } + } +#endif +}; + +#ifndef _MSC_VER +[[maybe_unused]] constexpr underscore_t _; +#endif + +} // end namespace + +} // end namespace symbols + +} // end namespace apl + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_TEST(( apl::iota<2>({2, 3}) == multi::array({{0, 1, 2}, {3, 4, 5}}) )); + + // #if !defined(_MSC_VER) + BOOST_TEST(( apl::iota(2, 3) == multi::array({{0, 1, 2}, {3, 4, 5}}) )); + BOOST_TEST(( apl::iota(4) == multi::array({0, 1, 2, 3}) )); + +#if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + // NOLINTNEXTLINE(google-build-using-namespace) + using namespace apl::symbols; // NOLINT(build/namespaces) +#if !defined(__NVCOMPILER) && !defined(_MSC_VER) && (!defined(__GNUC__) || __GNUC__ > 9) + BOOST_TEST(( ι(4) == _[0, 1, 2, 3] )); + BOOST_TEST(( ι(2, 3) == _[ _[0, 1, 2], _[3, 4, 5] ] )); + BOOST_TEST(( Ɵ == ι(0) )); +#endif +#endif + // #endif + + return boost::report_errors(); +} diff --git a/test/array_cref.cpp b/test/array_cref.cpp index 7a8f678f2..0d0784c96 100644 --- a/test/array_cref.cpp +++ b/test/array_cref.cpp @@ -1,97 +1,132 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi References" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#include -#include +#ifdef __NVCC__ // affects cuda 11.4.3 +#pragma nv_diag_suppress 20014 // error #20014-D: calling a __host__ function from a __host__ __device__ function is not allowed +#endif -#include -#include +#include // for array, array_ref, subarray, arra... + +#include // for complex, operator== +#include // for initializer_list +#include // for pointer_traits +#include // for get // NOLINT(misc-include-cleaner) +#include // for is_same +#include // for vector namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(array_cref) { - using complex = std::complex; +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(array_cref) { + { + using complex = std::complex; - static_assert( std::is_same::element_type, complex>{}, "!"); - static_assert( std::is_same::rebind, complex const*>{}, "!"); + static_assert(std::is_same::element_type, complex>{}, "!"); + static_assert(std::is_same::rebind, complex const*>{}, "!"); - std::vector vec(100, 0.); // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - std::vector const cvec(100); // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + std::vector vec(100, 0.0); // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + std::vector const cvec(100); // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - multi::array_ref A2D(vec.data(), multi::extensions_t<2>{10, 10}); - multi::array_ref B2D(vec.data(), {10, 10}); + multi::array_ref A2D(vec.data(), multi::extensions_t<2>{10, 10}); + multi::array_ref B2D(vec.data(), {10, 10}); - static_assert( multi::array_ref::rank::value == 2 , "!" ); + using std::get; - BOOST_REQUIRE( &A2D[3][4] == &B2D[3][4] ); + BOOST_TEST( get<0>( A2D().sizes() ) == 10 ); + BOOST_TEST( get<1>( A2D().sizes() ) == 10 ); - multi::array_ref const D2D(cvec.data(), {10, 10}); - multi::array_cref F2D( vec.data(), {10, 10}); + BOOST_TEST( get<0>( A2D().sizes() ) == 10 ); + BOOST_TEST( get<1>( A2D().sizes() ) == 10 ); - BOOST_REQUIRE( D2D.layout() == F2D.layout() ); + static_assert(multi::array_ref::rank::value == 2); - A2D[7][8] = 3.; - BOOST_REQUIRE( F2D[7][8] == 3. ); - BOOST_REQUIRE( &A2D[7][8] == &F2D[7][8] ); + BOOST_TEST( &A2D[3][4] == &B2D[3][4] ); -// #if defined(__cpp_deduction_guides) and not defined(__NVCC__) -// multi::array_ref G2D(dc.data(), {10, 10}); // TODO(correaa) -// BOOST_REQUIRE( G2D == D2D ); -// #endif -} + multi::array_ref const D2D(cvec.data(), {10, 10}); + multi::array_cref F2D(vec.data(), {10, 10}); -BOOST_AUTO_TEST_CASE(arrays_1D_from_carray) { - double a_c_array[] = {1.0, 2.0, 3.0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types - multi::array an_array_value (a_c_array); // ok, it is a copy - multi::array_cref an_array_const_reference(a_c_array); // ok, it is read only reference - multi::array_ref an_array_reference (a_c_array); // ok, it is a reference + BOOST_TEST( D2D.layout() == F2D.layout() ); - BOOST_REQUIRE( an_array_value .size() == 3 and an_array_value [1] == 2.0 ); - BOOST_REQUIRE( an_array_const_reference.size() == 3 and an_array_const_reference[1] == 2.0 ); - BOOST_REQUIRE( an_array_reference .size() == 3 and an_array_reference [1] == 2.0 ); -} + A2D[7][8] = 3.0; + BOOST_TEST( F2D[7][8] == 3.0 ); + BOOST_TEST( &A2D[7][8] == &F2D[7][8] ); -BOOST_AUTO_TEST_CASE(arrays_1D_from_const_carray) { - double const a_c_array[] = {1.0, 2.0, 3.0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types - multi::array an_array_value (a_c_array); // ok, it is a copy - multi::array_cref an_array_const_reference(a_c_array); // ok, it is read only reference -// multi::array_ref an_array_reference (a_c_array); // not ok, c array is const + // #if defined(__cpp_deduction_guides) and not defined(__NVCC__) + // multi::array_ref G2D(dc.data(), {10, 10}); // TODO(correaa) + // BOOST_TEST( G2D == D2D ); + // #endif + } - BOOST_REQUIRE( an_array_value .size() == 3 and an_array_value [1] == 2.0 ); - BOOST_REQUIRE( an_array_const_reference.size() == 3 and an_array_const_reference[1] == 2.0 ); -// BOOST_REQUIRE( an_array_reference .size() == 3 and an_array_reference [1] == 2. ); -} +#ifndef _MSC_VER // TODO(correaa) doesn't work on MSVC 14.3 in c++17 mode + // BOOST_AUTO_TEST_CASE(arrays_1D_from_carray) + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types + int a_c_array[] = {10, 20, 30}; + multi::array an_array_value(a_c_array); // ok, it is a copy + multi::array_cref an_array_const_reference(a_c_array); // ok, it is read only reference + multi::array_ref an_array_reference(a_c_array); // ok, it is a reference -BOOST_AUTO_TEST_CASE(arrays_1D_from_explict_init_list) { - std::initializer_list const il = {1.0, 2.0, 3.0}; - multi::array an_array_value (il); // ok, it is a copy - multi::array_cref an_array_const_reference(il); // ok, it is read only -// multi::array_ref an_array_reference ({1., 2., 3.}); // not allowed, the init list elems are const + BOOST_TEST( an_array_value .size() == 3 && an_array_value [1] == 20 ); + BOOST_TEST( an_array_const_reference.size() == 3 && an_array_const_reference[1] == 20 ); + BOOST_TEST( an_array_reference .size() == 3 && an_array_reference [1] == 20 ); + } - BOOST_REQUIRE( an_array_value .size() == 3 and an_array_value [1] == 2.0 ); - BOOST_REQUIRE( an_array_const_reference.size() == 3 and an_array_const_reference[1] == 2.0 ); -// BOOST_REQUIRE( an_array_reference .size() == 3 and an_array_reference [1] == 2. ); -} + // BOOST_AUTO_TEST_CASE(arrays_1D_from_const_carray) + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types + int const a_c_array[] = {10, 20, 30}; + multi::array an_array_value(a_c_array); // ok, it is a copy + multi::array_cref an_array_const_reference(a_c_array); // ok, it is read only reference -BOOST_AUTO_TEST_CASE(arrays_1D_from_explict_auto_init_list) { - auto il = {1.0, 2.0, 3.0}; - multi::array an_array_value (il); // ok, it is a copy - multi::array_cref an_array_const_reference(il); // ok, it is read only -// multi::array_ref an_array_reference ({1., 2., 3.}); // not allowed, the init list elems are const + // multi::array_ref an_array_reference (a_c_array); // not ok, c array is const - BOOST_REQUIRE( an_array_value .size() == 3 and an_array_value [1] == 2.0 ); - BOOST_REQUIRE( an_array_const_reference.size() == 3 and an_array_const_reference[1] == 2.0 ); -// BOOST_REQUIRE( an_array_reference .size() == 3 and an_array_reference [1] == 2. ); -} + BOOST_TEST( an_array_value .size() == 3 && an_array_value [1] == 20 ); + BOOST_TEST( an_array_const_reference.size() == 3 && an_array_const_reference[1] == 20 ); + // BOOST_TEST( an_array_reference .size() == 3 && an_array_reference [1] == 20 ); + } +#endif + + // BOOST_AUTO_TEST_CASE(arrays_1D_from_explict_init_list) + { + std::initializer_list const il = {10, 20, 30}; + multi::array an_array_value(il); // ok, it is a copy + multi::array_cref an_array_const_reference(il); // ok, it is read only + + // multi::array_ref an_array_reference ({10, 20, 30}); // not allowed, the init list elems are const + + BOOST_TEST( an_array_value .size() == 3 && an_array_value [1] == 20 ); + BOOST_TEST( an_array_const_reference.size() == 3 && an_array_const_reference[1] == 20 ); + // BOOST_TEST( an_array_reference .size() == 3 && an_array_reference [1] == 20 ); + } + + // BOOST_AUTO_TEST_CASE(arrays_1D_from_explict_auto_init_list) + { + auto il = {10, 20, 30}; + multi::array an_array_value(il); // ok, it is a copy + multi::array_cref an_array_const_reference(il); // ok, it is read only + + // multi::array_ref an_array_reference ({10, 20, 30}); // not allowed, the init list elems are const + + BOOST_TEST( an_array_value .size() == 3 && an_array_value [1] == 20 ); + BOOST_TEST( an_array_const_reference.size() == 3 && an_array_const_reference[1] == 20 ); + // BOOST_TEST( an_array_reference .size() == 3 && an_array_reference [1] == 20 ); + } + + // BOOST_AUTO_TEST_CASE(arrays_1D_from_init_list) + { + multi::array an_array_value({10, 20, 30}); // ok, it is a copy + + // multi::array_cref an_array_const_reference({10, 20, 30}); // not ok, constructor disable because memcheck detects use after scope + // multi::array_ref an_array_reference ({10, 20, 30}); // not allowed, the init list elems are const -BOOST_AUTO_TEST_CASE(arrays_1D_from_init_list) { - multi::array an_array_value ({1.0, 2.0, 3.0}); // ok, it is a copy -// multi::array_cref an_array_const_reference({1.0, 2.0, 3.0}); // not ok, constructor disable because memcheck detects use after scope -// multi::array_ref an_array_reference ({1., 2., 3.}); // not allowed, the init list elems are const + BOOST_TEST( an_array_value .size() == 3 && an_array_value [1] == 20 ); + // BOOST_TEST( an_array_const_reference.size() == 3 && an_array_const_reference[1] == 20 ); + // BOOST_TEST( an_array_reference .size() == 3 && an_array_reference [1] == 20 ); + } - BOOST_REQUIRE( an_array_value .size() == 3 and an_array_value [1] == 2. ); -// BOOST_REQUIRE( an_array_const_reference.size() == 3 and an_array_const_reference[1] == 2.0 ); -// BOOST_REQUIRE( an_array_reference .size() == 3 and an_array_reference [1] == 2. ); + return boost::report_errors(); } diff --git a/test/array_fancyref.cpp b/test/array_fancyref.cpp index e89ab5726..5e9ce1839 100644 --- a/test/array_fancyref.cpp +++ b/test/array_fancyref.cpp @@ -1,9 +1,15 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#include +#include // for array, array_iterator, static_array -#include +#include // for size_t, nullptr_t, ptrdiff_t +#include // for random_access_iterator_tag +#include // for allocator +#include // for decay_t +// IWYU pragma: no_include // for __GLIBCXX__ // NOLINT(misc-include-cleaner) namespace fancy { @@ -13,44 +19,45 @@ template class ptr { // NOLINT(cppcoreguidelines-special-member static double const value; public: - using difference_type = std::ptrdiff_t; - using value_type = std::decay_t; - using pointer = T*; - using reference = ref; + using difference_type = std::ptrdiff_t; + using value_type = std::decay_t; + using pointer = T*; + using reference = ref; using iterator_category = std::random_access_iterator_tag; ptr() noexcept = default; explicit ptr(std::nullptr_t) noexcept {} - template constexpr explicit ptr(ptr const& /*other*/) noexcept {} - constexpr ptr(ptr const& /*other*/) noexcept {} // NOLINT(hicpp-use-equals-default,modernize-use-equals-default) + template + constexpr explicit ptr(ptr const& /*other*/) noexcept {} + // template(std::declval()))> + // constexpr explicit ptr(ptr const& /*other*/) noexcept {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR(cpp:S1709) + constexpr ptr(ptr const& /*other*/) = default; // vvv it is important that these two functions are device or device host functions - // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): this class simulates pointer - constexpr auto operator*() const noexcept -> reference {return reference{};} - // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): this class simulates pointer - constexpr auto operator+(difference_type /*unused*/) const noexcept -> ptr {return *this;} - // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): this class simulates pointer - - auto operator+=(difference_type /*difference*/) noexcept -> ptr& {return *this;} - // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): this class simulates pointer - auto operator++() noexcept -> ptr& {return operator+=(1);} - // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): this class simulates pointer - friend auto operator-(ptr const& /*a*/, ptr const& /*b*/) noexcept -> difference_type {return 0;} - // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): this class simulates pointer - auto operator==(ptr const& /*other*/) const noexcept -> bool {return true;} - // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): this class simulates pointer - auto operator!=(ptr const& /*other*/) const noexcept -> bool {return false;} -// explicit operator T*() const{return &value;} - // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): this class simulates pointer - auto operator->() const noexcept -> ptr const& {return *this;} - // NOLINTNEXTLINE(fuchsia-trailing-return): this class simulates pointer -// friend auto to_address(ptr const& pointer) -> ptr {return pointer;} - explicit operator bool() const noexcept {return false;} -// operator double*() const{return &value;} - friend auto get_allocator(ptr const& /*self*/) noexcept {return std::allocator{};} + constexpr auto operator*() const noexcept -> reference { return reference{}; } + + constexpr auto operator+(difference_type /*unused*/) const noexcept -> ptr { return *this; } + + constexpr friend auto operator+(difference_type n, ptr self) { return self + n; } + + constexpr auto operator[](difference_type dist) const noexcept -> reference { return *(*this + dist); } + + auto operator+=(difference_type /*difference*/) noexcept -> ptr& { return *this; } + + auto operator++() noexcept -> ptr& { return operator+=(1); } + + friend auto operator-(ptr const& /*a*/, ptr const& /*b*/) noexcept -> difference_type { return 0; } + + auto operator==(ptr const& /*other*/) const noexcept -> bool { return true; } + auto operator!=(ptr const& /*other*/) const noexcept -> bool { return false; } + + explicit operator bool() const noexcept { return false; } + + friend auto get_allocator(ptr const& /*self*/) noexcept { return std::allocator{}; } }; -template<> double const ptr::value = 42.0; +template<> double const ptr::value = 42.0; template<> double const ptr::value = 42.0; template class ref { @@ -59,50 +66,62 @@ template class ref { ref() = default; public: -// explicit ref(ref> const& other) : p_{other.p_} {} - ~ref() = default; - auto operator=(ref const& other) -> ref& = delete; - constexpr ref(ref const& /*other*/) = delete; - constexpr ref(ref&& /*other*/) noexcept {} // this is needed by nvcc, needs to be a device function for nvcc 11.2 and lower - - auto operator=(ref && other) noexcept -> ref& = delete; // {*p_ = std::move(*other.p_); return *this;} - constexpr operator T const&() const& {return ptr::value;} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + // explicit ref(ref> const& other) : p_{other.p_} {} + // ~ref() = default; + // auto operator=(ref const& other) -> ref& = delete; + // constexpr ref(ref const& /*other*/) = delete; + // constexpr ref(ref&& /*other*/) noexcept {} // this is needed by nvcc, needs to be a device function for nvcc 11.2 and lower + + // auto operator=(ref && other) noexcept -> ref& = delete; // {*p_ = std::move(*other.p_); return *this;} + + constexpr operator T const&() const& { return ptr::value; } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) // NOSONAR(cpp:S1709) // NOLINTNEXTLINE(fuchsia-overloaded-operator): this class simulates a reference - auto operator==(ref const& /*other*/) const {return true;} + auto operator==(ref const& /*other*/) const { return true; } // NOLINTNEXTLINE(fuchsia-overloaded-operator): this class simulates a reference - auto operator!=(ref const& /*other*/) const {return false;} + auto operator!=(ref const& /*other*/) const { return false; } using decay_t = std::decay_t; }; template struct allocator { - using pointer = ptr; + using pointer = ptr; using value_type = T; - auto allocate(std::size_t /*size*/) {return pointer{};} - void deallocate(pointer /*base*/, std::size_t /*size*/) {} -// std::true_type operator==(allocator const&){return {};} + auto allocate(std::size_t /*size*/) { return pointer{}; } + void deallocate(pointer /*base*/, std::size_t /*size*/) { + /*no-op;*/ + } + // std::true_type operator==(allocator const&){return {};} allocator() = default; - template explicit allocator(allocator const& /*other*/) {} + template explicit allocator(allocator const& /*other*/) { + /*no-op;*/ + } template - void construct(pointer /*location*/, Args&&... /*args*/) {} - void destroy(pointer /*location*/) {} + void construct(pointer /*location*/, Args const&... /*args*/) { + /*no-op;*/ + } + void destroy(pointer /*location*/) { + /*no-op;*/ + } }; -// all these are optional, depending on the level of specialization needed -template -auto copy_n(Ptr /*first*/, Size /*count*/, ptr result) { -// std::cerr<< "called Pointer-based copy_n(Ptr, n, fancy::ptr)" < -auto copy_n(ptr /*first*/, Size /*count*/, Ptr result) { -// std::cerr<< "called Pointer-based copy_n(fancy::ptr, n, Ptr)" < -auto copy_n(ptr /*first*/, Size /*count*/, ptr result) { -// std::cerr<< "called Pointer-based copy_n(fancy::ptr, n, fancy::ptr)" < +// auto copy_n(Ptr /*first*/, Size /*count*/, ptr result) { +// // std::cerr<< "called Pointer-based copy_n(Ptr, n, fancy::ptr)" < +// auto copy_n(ptr /*first*/, Size /*count*/, Ptr result) { +// // std::cerr<< "called Pointer-based copy_n(fancy::ptr, n, Ptr)" < +// auto copy_n(ptr /*first*/, Size /*count*/, ptr result) { +// // std::cerr<< "called Pointer-based copy_n(fancy::ptr, n, fancy::ptr)" < /*first*/, Size /*count*/, ptr result) { namespace boost::multi { -template -auto copy(It first, It last, fancy::ptr dest) { - return copy(first, last, multi::array_iterator>{dest}); -// std::cerr << "1D copy(it1D, it1D, it1D) with strides " << stride(first) << " " << stride(dest) << std::endl; +// namespace { +// template +// auto copy(It first, It last, fancy::ptr dest) { +// return copy(first, last, multi::array_iterator>{dest}); +// // std::cerr << "1D copy(it1D, it1D, it1D) with strides " << stride(first) << " " << stride(dest) << std::endl; +// // return dest; +// } + +// template // custom copy 1D (aka strided copy) +// auto copy(It /*first*/, It /*last*/, multi::array_iterator> dest) { +// // std::cerr << "1D copy(it1D, it1D, it1D) with strides " << stride(first) << " " << stride(dest) << std::endl; // return dest; -} +// } -template // custom copy 1D (aka strided copy) -auto copy(It/*first*/, It/*last*/, multi::array_iterator> dest) { -// std::cerr << "1D copy(it1D, it1D, it1D) with strides " << stride(first) << " " << stride(dest) << std::endl; - return dest; -} - -template // custom copy 2D (aka double strided copy) -auto copy(It/*first*/, It/*last*/, multi::array_iterator> dest) { -// std::cerr<<"2D copy(It, It, it2D) with strides 1"<< first.stride() <<" "<< dest.stride() < // custom copy 2D (aka double strided copy) +// auto copy(It /*first*/, It /*last*/, multi::array_iterator> dest) { +// // std::cerr<<"2D copy(It, It, it2D) with strides 1"<< first.stride() <<" "<< dest.stride() < // custom copy 2D (aka double strided copy) -// auto uninitialized_copy(Alloc&, It first, It last, multi::array_iterator> const& dest){ -// std::cerr << "2D uninitialized_copy(...) calls raw copy 2D" << std::endl; -// return copy(first, last, dest); -// } +// } // end unnamed namespace } // end namespace boost::multi -//////////////////////////////////////////////////////////////////////////////// -// user code -//////////////////////////////////////////////////////////////////////////////// +#include + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_fancy) + { + namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_fancy) { - namespace multi = boost::multi; + multi::array> arr({5, 5}); + BOOST_TEST( arr.size() == 5 ); + BOOST_TEST( arr[1][1] == arr[2][2] ); - multi::array> arr({5, 5}); - BOOST_REQUIRE( arr.size() == 5 ); - BOOST_REQUIRE( arr[1][1] == arr[2][2] ); + multi::array> const arr2({0, 0}); + BOOST_TEST( arr2.size() == 0 ); + } - multi::array> const arr2({0, 0}); - BOOST_REQUIRE( arr2.size() == 0 ); + return boost::report_errors(); } diff --git a/test/array_legacy_c.cpp b/test/array_legacy_c.cpp index 19169c983..735c1bfbd 100644 --- a/test/array_legacy_c.cpp +++ b/test/array_legacy_c.cpp @@ -1,13 +1,17 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi legacy adaptor example" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#include // for array, rotated, subarray, dimens... -#include "multi/array.hpp" +#include -#include -#include +#include // for array +#include // for complex +#include // for apply // IWYU pragma: keep +// IWYU pragma: no_include // for remove_reference<>::type +#include // for move namespace multi = boost::multi; @@ -16,73 +20,66 @@ namespace fake { // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) testing a legacy interface using fftw_complex = double[2]; -void fftw_plan_dft( - int rank, const int* n, - fftw_complex* in, fftw_complex* out, int sign, unsigned flags -); +namespace { -void fftw_plan_dft( - int rank, const int* n, - fftw_complex* in, fftw_complex* out, int sign, unsigned flags -) { +void fftw_plan_dft(int rank, int const* n, fftw_complex* in, fftw_complex* out, int sign, unsigned flags); + +void fftw_plan_dft(int rank, int const* n, fftw_complex* in, fftw_complex* out, int sign, unsigned flags) { (void)rank, (void)n, (void)in, (void)out, (void)sign, (void)flags; } +} // end unnamed namespace + } // end namespace fake -BOOST_AUTO_TEST_CASE(array_legacy_c) { - using complex = std::complex; - multi::array const in = { - {{150.0, 0.0}, {16.0, 0.0}, {17.0, 0.0}, {18.0, 0.0}, {19.0, 0.0}}, - {{ 5.0, 0.0}, { 5.0, 0.0}, { 5.0, 0.0}, { 5.0, 0.0}, { 5.0, 0.0}}, - {{100.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}, {13.0, 0.0}, {14.0, 0.0}}, - {{ 50.0, 0.0}, { 6.0, 0.0}, { 7.0, 0.0}, { 8.0, 0.0}, { 9.0, 0.0}} - }; - - multi::array, 2> out(extensions(in)); - - BOOST_REQUIRE( dimensionality(out) == dimensionality(in) ); - BOOST_REQUIRE( sizes(out) == sizes(in) ); - - static_assert( sizeof(complex) == sizeof(fake::fftw_complex), "!" ); - fake::fftw_plan_dft( - decltype(in)::dimensionality, - std::apply([](auto... sizes) {return std::array{{static_cast(sizes)...}};}, in.sizes()).data(), - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) testing legacy code - reinterpret_cast(const_cast(in .data_elements())), - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): testing legacy code - reinterpret_cast( out.data_elements() ), - 1, 0 - ); +namespace { +constexpr auto f2(multi::array_ref&& array) -> double& { return std::move(array)[2]; } +} // end unnamed namespace +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(array_legacy_c) { - multi::array d2D = { - {150.0, 16.0, 17.0, 18.0, 19.0}, - { 30.0, 1.0, 2.0, 3.0, 4.0}, - {100.0, 11.0, 12.0, 13.0, 14.0}, - { 50.0, 6.0, 7.0, 8.0, 9.0} + using complex = std::complex; + + multi::array const in = { + {{150.0, 0.0}, {16.0, 0.0}, {17.0, 0.0}, {18.0, 0.0}, {19.0, 0.0}}, + { {5.0, 0.0}, {5.0, 0.0}, {5.0, 0.0}, {5.0, 0.0}, {5.0, 0.0}}, + {{100.0, 0.0}, {11.0, 0.0}, {12.0, 0.0}, {13.0, 0.0}, {14.0, 0.0}}, + { {50.0, 0.0}, {6.0, 0.0}, {7.0, 0.0}, {8.0, 0.0}, {9.0, 0.0}}, }; -// #if __has_cpp_attribute(no_unique_address) >=201803L and not defined(__NVCC__) and not defined(__PGI) -// BOOST_REQUIRE( sizeof(d2D)==sizeof(double*)+7*sizeof(std::size_t) ); -// #endif - BOOST_REQUIRE( d2D.is_compact() ); - BOOST_REQUIRE( rotated(d2D).is_compact() ); - BOOST_REQUIRE( d2D[3].is_compact() ); - BOOST_REQUIRE( not rotated(d2D)[2].is_compact() ); + multi::array, 2> out(extensions(in)); + + BOOST_TEST( dimensionality(out) == dimensionality(in) ); + BOOST_TEST( sizes(out) == sizes(in) ); + + static_assert(sizeof(complex) == sizeof(fake::fftw_complex), "!"); + fake::fftw_plan_dft( + decltype(in)::dimensionality, + std::apply([](auto... sizes) { return std::array{{static_cast(sizes)...}}; }, in.sizes()).data(), + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast) testing legacy code + reinterpret_cast(const_cast(in.data_elements())), // NOSONAR + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): testing legacy code + reinterpret_cast(out.data_elements()), + 1, 0 + ); } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + // BOOST_AUTO_TEST_CASE(array_legacy_c_2) { - multi::array d2D({5, 3}); - BOOST_REQUIRE( d2D.is_compact() ); - BOOST_REQUIRE( rotated(d2D).is_compact() ); - BOOST_REQUIRE( d2D[3].is_compact() ); - BOOST_REQUIRE( not rotated(d2D)[2].is_compact() ); + double arr[5] = {150.0, 16.0, 17.0, 18.0, 19.0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + BOOST_TEST( &f2(arr) == &arr[2] ); } -} -inline constexpr auto f2(multi::array_ref&& array) -> double& {return array[2];} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif -BOOST_AUTO_TEST_CASE(array_legacy_c_2) { - double arr[5] = {150.0, 16.0, 17.0, 18.0, 19.0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - BOOST_REQUIRE( &f2(arr) == &arr[2] ); + return boost::report_errors(); } diff --git a/test/array_ptr.cpp b/test/array_ptr.cpp index 504c25408..7815170f7 100644 --- a/test/array_ptr.cpp +++ b/test/array_ptr.cpp @@ -1,173 +1,280 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#include +#include // for layout_t, apply, subarray, array... // IWYU pragma: keep // bug in iwyu 8.22 -#include "multi/array.hpp" +#include -namespace multi = boost::multi; +#include // for equal +#include // for array // IWYU pragma: keep // bug in iwyu 8.22 +#include // for addressof // IWYU pragma: keep +#include // for is_trivially_copy_assignable_v +#include // for as_const, addressof, exchange, move +#include // for vector +namespace { // NOLINTNEXTLINE(fuchsia-trailing-return): trailing return helps readability template auto fwd_array(T&& array) -> T&& { return std::forward(array); } +} // end unnamed namespace -BOOST_AUTO_TEST_CASE(multi_array_ptr_equality) { - multi::array arr = { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - {7.0, 8.0, 9.0}, - {1.0, 2.0, 3.0}, - }; - BOOST_REQUIRE( arr[2] == arr[2] ); - BOOST_REQUIRE( &arr[2] == &arr[2] ); - BOOST_REQUIRE( &arr[2] == &fwd_array(arr[2]) ); - BOOST_REQUIRE( &fwd_array(arr[2]) == &arr[2] ); - - auto const& carr2 = arr[2]; - BOOST_REQUIRE( carr2[0] == arr[2][0] ); - BOOST_REQUIRE( carr2.base() == arr[2].base() ); - BOOST_REQUIRE( &carr2 == &std::as_const(arr)[2] ); - BOOST_REQUIRE( &carr2 == & arr [2] ); - - auto const& ac2 = carr2; // fwd_array(A[2]); - BOOST_REQUIRE( &ac2 == &std::as_const(arr)[2] ); - BOOST_REQUIRE( &ac2 == & arr [2] ); -} +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_array_ptr) { + // BOOST_AUTO_TEST_CASE(multi_array_ptr_equality) { - std::array, 4> arr{ - {{{0.0, 1.0, 2.0, 3.0, 4.0}}, - {{5.0, 6.0, 7.0, 8.0, 9.0}}, - {{10.0, 11.0, 12.0, 13.0, 14.0}}, - {{15.0, 16.0, 17.0, 18.0, 19.0}}}, + multi::array arr = { + {10, 20, 30}, + {40, 50, 60}, + {70, 80, 90}, + {10, 20, 30}, }; + BOOST_TEST( arr[2] == arr[2] ); + BOOST_TEST( &arr[2] == &arr[2] ); + BOOST_TEST( !(&arr[2] == &(arr[2]({0, 2}))) ); + + BOOST_TEST( arr[2].base() == arr[2]({0, 2}).base() ); + BOOST_TEST( arr[2].layout() != arr[2]({0, 2}).layout() ); + + // what( arr[2], arr[2].sliced(0, 2), &(arr[2].sliced(0, 2)) ); + BOOST_TEST( &arr[2] != &(arr[2].sliced(0, 2)) ); - multi::array_ptr const arrP{&arr}; + // comparison of different provenance is undefined + // BOOST_TEST( !( &arr[2] == &std::as_const(arr)[2]({0, 2})) ); + BOOST_TEST( &arr[2] == &fwd_array(arr[2]) ); + BOOST_TEST( &fwd_array(arr[2]) == &arr[2] ); - BOOST_REQUIRE( arrP->extensions() == multi::extensions(arr) ); - BOOST_REQUIRE( extensions(*arrP) == multi::extensions(arr) ); + auto arr_ptr = &arr[2]; + BOOST_TEST( arr_ptr == arr_ptr ); - using multi::extensions; - BOOST_REQUIRE( extensions(*arrP) == extensions(arr) ); - BOOST_REQUIRE( &arrP->operator[](1)[1] == &arr[1][1] ); + auto& arr_ptr_ref = arr_ptr; + arr_ptr = arr_ptr_ref; - multi::array_ptr const arrP2{&arr}; - BOOST_REQUIRE( arrP == arrP2 ); - BOOST_REQUIRE( not (arrP != arrP2) ); + auto arr_ptr2 = &std::as_const(arr)[2]; + BOOST_TEST( arr_ptr == arr_ptr2 ); + BOOST_TEST( arr_ptr2 == arr_ptr ); + BOOST_TEST( !(arr_ptr != arr_ptr) ); - std::array, 4> arr2{}; - multi::array_ptr arr2P{&arr2}; - BOOST_REQUIRE( arr2P != arrP ); - BOOST_REQUIRE( not (arr2P == arrP) ); + auto& arr_ptr2_ref = arr_ptr2; + arr_ptr2 = arr_ptr2_ref; + arr_ptr2_ref = arr_ptr2; // cppcheck-suppress selfAssignment ; - arr2P = arrP; - BOOST_REQUIRE( arrP == arr2P ); - BOOST_REQUIRE( *arrP == *arr2P ); - BOOST_REQUIRE( arrP->operator==(*arrP) ); + auto const& carr2 = arr[2]; + BOOST_TEST( carr2[0] == arr[2][0] ); + BOOST_TEST( carr2.base() == arr[2].base() ); + BOOST_TEST( &carr2 == &std::as_const(arr)[2] ); + BOOST_TEST( &carr2 == & arr [2] ); + BOOST_TEST( &carr2 == & arr [2] ); - auto&& arrR = *arrP; - BOOST_REQUIRE( &arrR[1][1] == &arr[1][1] ); - BOOST_REQUIRE( arrR == *arrP ); - BOOST_REQUIRE( std::equal(arrR.begin(), arrR.end(), arrP->begin(), arrP->end()) ); - BOOST_REQUIRE( size(arrR) == arrP->size() ); + // comparing array-pointer of different provenance is undefined + // BOOST_TEST( &carr2 != & arr [2]({0, 2}) ); + // BOOST_TEST( !(&carr2 == & arr [2]({0, 2})) ); + + auto const& ac2 = carr2; // fwd_array(A[2]); + BOOST_TEST( &ac2 == &std::as_const(arr)[2] ); + BOOST_TEST( &std::as_const(arr)[2] == &ac2 ); + BOOST_TEST( &ac2 == & arr [2] ); + + auto pac2 = &ac2; + auto parr2 = &arr[2]; + BOOST_TEST( pac2 == parr2 ); // cppcheck-suppress knownConditionTrueFalse ; + + pac2 = nullptr; + BOOST_TEST( pac2 != parr2 ); + + parr2 = nullptr; + BOOST_TEST( pac2 == parr2 ); // cppcheck-suppress knownConditionTrueFalse ; } + + // BOOST_AUTO_TEST_CASE(multi_array_ptr) { - std::array, 4> arr = { - {std::array{{0.0, 1.0, 2.0, 3.0, 4.0}}, - std::array{{5.0, 6.0, 7.0, 8.0, 9.0}}, - std::array{{10.0, 11.0, 12.0, 13.0, 14.0}}, - std::array{{15.0, 16.0, 17.0, 18.0, 19.0}}}, + { + // clang-format off + std::array, 4> arr{ + {{{0.0, 1.0, 2.0, 3.0, 4.0}}, + {{5.0, 6.0, 7.0, 8.0, 9.0}}, + {{10.0, 11.0, 12.0, 13.0, 14.0}}, + {{15.0, 16.0, 17.0, 18.0, 19.0}}}, }; - - std::vector> ptrs; - ptrs.emplace_back(&arr[0][0], 5); // NOLINT(readability-container-data-pointer) test access - ptrs.emplace_back(arr[2].data(), 5); - ptrs.emplace_back(&arr[3][0], 5); // NOLINT(readability-container-data-pointer) test access - - BOOST_REQUIRE( &(*ptrs[2])[4] == &arr[3][4] ); - BOOST_REQUIRE( (*ptrs[2])[4] == 19 ); - BOOST_REQUIRE( ptrs[2]->operator[](4) == 19 ); + // clang-format on + + multi::array_ptr const arrP{&arr}; + + static_assert(std::is_trivially_copy_assignable_v>); + static_assert(std::is_trivially_copyable_v>); + + // #ifndef _MSC_VER + static_assert(std::is_trivially_default_constructible_v>); + static_assert(std::is_trivially_default_constructible_v>); + static_assert(std::is_trivially_default_constructible_v>); + // #endif + + static_assert(std::is_trivially_copyable_v>); + static_assert(std::is_trivially_copyable_v>); + static_assert(std::is_trivially_copyable_v>); + + // static_assert(std::is_trivially_copy_assignable_v>); + // static_assert(std::is_trivially_copyable_v>); + + BOOST_TEST( (*arrP).extensions() == multi::extensions(arr) ); + BOOST_TEST( arrP->extensions() == multi::extensions(arr) ); + BOOST_TEST( extensions(*arrP) == multi::extensions(arr) ); + + using multi::extensions; + BOOST_TEST( extensions(*arrP) == extensions(arr) ); + + BOOST_TEST( &(*arrP).operator[](1)[1] == &arr[1][1] ); + BOOST_TEST( &arrP->operator[](1)[1] == &arr[1][1] ); + + multi::array_ptr const arrP2{&arr}; + BOOST_TEST( arrP == arrP2 ); + BOOST_TEST( !(arrP != arrP2) ); + + std::array, 4> arr2{}; + multi::array_ptr arr2P{&arr2}; + BOOST_TEST( arr2P != arrP ); + BOOST_TEST( !(arr2P == arrP) ); + + arr2P = arrP; + BOOST_TEST( arrP == arr2P ); // cppcheck-suppress knownConditionTrueFalse ; + BOOST_TEST( *arrP == *arr2P ); + + BOOST_TEST( (*arrP).operator==(*arrP) ); + BOOST_TEST( arrP->operator==(*arrP) ); + + auto&& arrR = *arrP; + BOOST_TEST( &arrR[1][1] == &arr[1][1] ); + BOOST_TEST( arrR == *arrP ); + + BOOST_TEST( std::equal(arrR.begin(), arrR.end(), (*arrP).begin(), (*arrP).end()) ); + BOOST_TEST( std::equal(arrR.begin(), arrR.end(), arrP->begin(), arrP->end()) ); + + BOOST_TEST( arrR.size() == (*arrP).size() ); + BOOST_TEST( size(arrR) == arrP->size() ); + } + { + // clang-format off + std::array, 4> arr = {{ + std::array{ { 00, 10, 20, 30, 40 } }, + std::array{ { 50, 60, 70, 80, 90 } }, + std::array{ { 100, 110, 120, 130, 140 } }, + std::array{ { 150, 160, 170, 180, 190 } }, + }}; + // clang-format on + + std::vector> ptrs; + ptrs.emplace_back(&arr[0][0], 5); // NOLINT(readability-container-data-pointer) test access + ptrs.emplace_back(arr[2].data(), 5); + ptrs.emplace_back(&arr[3][0], 5); // NOLINT(readability-container-data-pointer) test access + + BOOST_TEST( &(*ptrs[2])[4] == &arr[3][4] ); // cppcheck-suppress mismatchingContainers ; + BOOST_TEST( (*ptrs[2])[4] == 190 ); + BOOST_TEST( ptrs[2]->operator[](4) == 190 ); + } + { + std::vector v1(100, 30); // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) + std::vector const v2(100, 40); // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) + + multi::array_ptr const v1P2D(v1.data(), {10, 10}); + multi::array_cptr const v2P2D(v2.data(), {10, 10}); + + *v1P2D = *v2P2D; + (*v1P2D).operator=(*v2P2D); + BOOST_TEST( v1[8] == 40 ); + + v1P2D->operator=(*v2P2D); + BOOST_TEST( v1[8] == 40 ); + } } + + // BOOST_AUTO_TEST_CASE(span_like) { - std::vector v1(100, 3.0); // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) - std::vector const v2(100, 4.0); // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) - multi::array_ptr const v1P2D(v1.data(), {10, 10}); - multi::array_cptr const v2P2D(v2.data(), {10, 10}); + std::vector vec = {00, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) - *v1P2D = *v2P2D; - v1P2D->operator=(*v2P2D); + using my_span = multi::array_ref; - BOOST_REQUIRE( v1[8] == 4.0 ); - } -} +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif -BOOST_AUTO_TEST_CASE(span_like) { - std::vector vec = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}; // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) + auto aP = &my_span{vec.data() + 2, {5}}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - using my_span = multi::array_ref; +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif - auto aP = &my_span{vec.data() + 2, {5}}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - BOOST_REQUIRE( aP->size() == 5 ); - BOOST_REQUIRE( (*aP)[0] == 2.0 ); + BOOST_TEST( (*aP).size() == 5 ); // cppcheck-suppress danglingTemporaryLifetime ; library idiom + BOOST_TEST( aP->size() == 5 ); - auto const& aCRef = *aP; - BOOST_REQUIRE( aCRef.size() == 5 ); + BOOST_TEST( (*aP)[0] == 20 ); - BOOST_REQUIRE( &aCRef[0] == &vec[2] ); - BOOST_REQUIRE( aCRef[0] == 2. ); + auto const& aCRef = *aP; // cppcheck-suppress danglingTempReference ; library idiom - auto&& aRef = *aP; - aRef[0] = 99.0; - BOOST_REQUIRE( vec[2] == 99.0 ); -} + BOOST_TEST( aCRef.size() == 5 ); // cppcheck-suppress danglingTempReference ; library idiom -BOOST_AUTO_TEST_CASE(multi_array_ptr_assignment) { - multi::array arr = { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - {7.0, 8.0, 9.0}, - {1.0, 2.0, 3.0}, - }; + BOOST_TEST( &aCRef[0] == &vec[2] ); // cppcheck-suppress danglingTempReference ; library idiom + BOOST_TEST( aCRef[0] == 20 ); // cppcheck-suppress danglingTempReference ; library idiom + + auto&& aRef = *aP; // cppcheck-suppress danglingTempReference ; library idiom + // what(aP, aRef); + // (*aP)[0] = 990; + aRef[0] = 990; // cppcheck-suppress danglingTempReference ; library idiom + BOOST_TEST( vec[2] == 990 ); + } + + // BOOST_AUTO_TEST_CASE(multi_array_ptr_assignment) { - auto rowP = &arr[2]; + multi::array arr = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + {7.0, 8.0, 9.0}, + {1.0, 2.0, 3.0}, + }; + { + auto rowP = &arr[2]; - rowP = *std::addressof(rowP); + rowP = *std::addressof(rowP); - auto rowP2 = rowP; - rowP2 = rowP; // self assigment + auto rowP2 = rowP; + rowP2 = rowP; // self assigment - BOOST_REQUIRE( rowP == rowP2 ); - BOOST_REQUIRE( not(rowP != rowP2) ); + BOOST_TEST( rowP == rowP2 ); // cppcheck-suppress knownConditionTrueFalse ; + BOOST_TEST( !(rowP != rowP2) ); // cppcheck-suppress knownConditionTrueFalse ; - auto rowP0 = &arr[0]; + auto rowP0 = &arr[0]; - BOOST_REQUIRE( rowP0 != rowP2 ); - BOOST_REQUIRE( not(rowP0 == rowP2) ); + BOOST_TEST( rowP0 != rowP2 ); + BOOST_TEST( !(rowP0 == rowP2) ); - rowP2 = decltype(rowP2){nullptr}; - BOOST_REQUIRE( not rowP2 ); + rowP2 = decltype(rowP2){nullptr}; + BOOST_TEST( !rowP2 ); - auto rowP3 = std::exchange(rowP, nullptr); - BOOST_REQUIRE( rowP3 == &arr[2] ); - BOOST_REQUIRE( rowP == nullptr ); - BOOST_REQUIRE( not rowP ); - } - { - auto rowP = &arr(); + auto rowP3 = std::exchange(rowP, nullptr); + BOOST_TEST( rowP3 == &arr[2] ); + BOOST_TEST( rowP == nullptr ); + // BOOST_TEST( !rowP ); + } + { + auto rowP = &arr(); - rowP = *std::addressof(rowP); + rowP = *std::addressof(rowP); - decltype(rowP) rowP2; - rowP2 = rowP; + decltype(rowP) rowP2; + rowP2 = rowP; - BOOST_REQUIRE( rowP == rowP2 ); + BOOST_TEST( rowP == rowP2 ); // cppcheck-suppress knownConditionTrueFalse ; - rowP2 = decltype(rowP2){nullptr}; - BOOST_REQUIRE( not rowP2 ); + rowP2 = decltype(rowP2){nullptr}; + BOOST_TEST( !rowP2 ); - auto rowP3 = std::exchange(rowP, nullptr); - BOOST_REQUIRE( rowP3 == &arr() ); - BOOST_REQUIRE( rowP == nullptr ); - BOOST_REQUIRE( not rowP ); + auto rowP3 = std::exchange(rowP, nullptr); + BOOST_TEST( rowP3 == &arr() ); + BOOST_TEST( rowP == nullptr ); + BOOST_TEST( !rowP ); + } } + + return boost::report_errors(); } diff --git a/test/array_ref.cpp b/test/array_ref.cpp index 9cb34e05f..b54dd3eae 100644 --- a/test/array_ref.cpp +++ b/test/array_ref.cpp @@ -1,668 +1,1360 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for implicit_cast, explicit_cast + +#include + +#include // for for_each, equal +#include // for array +#include // for int64_t +#include // for char_traits, operator<<, basic_o... +#include // for size +#include // for allocator, unique_ptr +#include // for accumulate, iota +#ifdef BOOST_MULTI_HAS_SPAN +#include // for span +#endif +#include // for basic_string, operator""s, string +#include // for std::tie +#include // for remove_reference, remove_const +// #include // for bad_cast +#include // for as_const, move +#include // for vector + +namespace multi = boost::multi; -#include +namespace boost::multi { + +// NOLINTBEGIN(whitespace/indent_namespace) bug in cpplint +template>> +using Array = std::conditional_t< + std::is_reference_v, + std::conditional_t< + std::is_const_v>, + boost::multi::array_ref>, D> const&, + boost::multi::array_ref, D>&>, + multi::array>; +// NOLINTEND(whitespace/indent_namespace) + +} // end namespace boost::multi + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif -#include +namespace { +auto f1d5(int const (&carr)[5]) -> int; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +auto f1d5(int const (&carr)[5]) -> int { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + return carr[1]; +} + +void f2d54(int const (&carr)[5][4]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +void f2d54(int const (&carr)[5][4]) { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + BOOST_TEST(carr[0][1] == 1); +} -#include // for std::cout -#include // for std::iota -#if defined(__cpp_lib_span) and (__cpp_lib_span >= 202002L) -#include +#ifdef __clang__ +#pragma clang diagnostic pop #endif +template +auto trace_array_deduce(multi::array const& arr) -> T { + auto const& diag = arr.diagonal(); + return std::accumulate(diag.begin(), diag.end(), T{0}); +} -namespace multi = boost::multi; +template int trace_array_deduce(multi::array const&); -BOOST_AUTO_TEST_CASE(array_ref_from_carray) { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): test - double arr[4][5] = { - { 0.0, 1.0, 2.0, 3.0, 4.0}, - { 5.0, 6.0, 7.0, 8.0, 9.0}, - {10.0, 11.0, 12.0, 13.0, 14.0}, - {15.0, 16.0, 17.0, 18.0, 19.0}, - }; +// template +// auto trace_generic(Array const& arr) -> T { +// auto const& diag = arr.diagonal(); +// return std::accumulate(diag.begin(), diag.end(), T{0}); +// } - multi::array_ptr const map{&arr}; - BOOST_REQUIRE( &map->operator[](1)[1] == &arr[1][1] ); - BOOST_REQUIRE( (*&arr)[1][1] == 6.0 ); +// template double trace_generic>(multi::array const&); - multi::array_ref&& mar = *map; +inline auto trace_separate_ref(multi::array_ref const& arr) -> int { + auto const& diag = arr.diagonal(); + return std::accumulate(diag.begin(), diag.end(), 0); +} - BOOST_REQUIRE( &mar[1][1] == &arr[1][1] ); +inline auto trace_separate_sub(multi::subarray const& arr) -> int { + auto const& diag = arr.diagonal(); + return std::accumulate(diag.begin(), diag.end(), 0); +} - mar[1][1] = 9.0; - BOOST_REQUIRE( &mar[1][1] == &arr[1][1] ); +// inline auto trace_separate_ref2(multi::array_const_view arr) -> int { +// auto const& diag = arr.diagonal(); +// return std::accumulate(diag.begin(), diag.end(), 0); +// } - auto const& a_const = arr; - // double const(&a_const)[4][5] = a; - BOOST_REQUIRE(&a_const[1][1] == &arr[1][1]); +// unusable for arrays +// inline auto trace_separate_ref3(multi::array_view arr) -> int { +// auto const& diag = arr.diagonal(); +// return std::accumulate(diag.begin(), diag.end(), 0); +// } - static_assert(decltype(mar(2, {1, 3}))::rank_v == 1); +// unusable for arrays +// inline auto trace_separate_ref4(multi::array_ref arr) -> int { +// auto const& diag = arr.diagonal(); +// return std::accumulate(diag.begin(), diag.end(), 0); +// } - BOOST_REQUIRE( size(mar(2, {1, 3})) == 2 ); - BOOST_REQUIRE( &mar(2, {1, 3})[1] == &arr[2][2] ); -} +// unusable for arrays +// inline auto trace_separate_sub4(multi::subarray arr) -> int { +// auto const& diag = arr.diagonal(); +// return std::accumulate(diag.begin(), diag.end(), 0); +// } + +// template +// auto mut_trace_array_deduce(multi::array& arr) -> T { +// arr[0][1] = 40; + +// auto const& diag = arr.diagonal(); +// return std::accumulate(diag.begin(), diag.end(), T{0}); +// } -BOOST_AUTO_TEST_CASE(array_ref_1D_reindexed) { - using namespace std::string_literals; // NOLINT(build/namespaces) for literal "string"s - std::array stdarr = {{"a"s, "b"s, "c"s, "d"s, "e"s}}; +// template double mut_trace_array_deduce(multi::array&); - multi::array_ref mar = *multi::array_ptr(&stdarr); +// template +// auto mut_trace_generic(Array& arr) -> T { +// arr[0][1] = 40; - BOOST_REQUIRE( &mar[1] == &stdarr[1] ); - BOOST_REQUIRE( sizes(mar.reindexed(1)) == sizes(mar) ); +// auto const& diag = arr.diagonal(); +// return std::accumulate(diag.begin(), diag.end(), T{0}); +// } - auto diff = &(mar.reindexed(1)[1]) - &mar[0]; - BOOST_REQUIRE( diff == 0 ); +} // end unnamed namespace - BOOST_REQUIRE( &mar.blocked(2, 4)[2] == &mar[2] ); - for(auto idx : extension(mar.stenciled({2, 4}))) { - BOOST_REQUIRE( &mar.stenciled({2, 4})[idx] == &mar[idx] ); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for test + int icarr[5] = {}; + multi::array_ref const iarrr(std::data(icarr), 5); + static_assert(std::is_base_of_v< + std::random_access_iterator_tag, + std::iterator_traits::iterator>::iterator_category>); + + BOOST_TEST( &iarrr[0] == std::data(icarr) ); + // static_assert( std::is_base_of_v< + // std::contiguous_iterator_tag, + // std::iterator_traits::iterator>::iterator_category + // >); } - multi::array arr({{2, 7}}, std::string{"xx"}); // std::string NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( size(arr) == 5 ); - BOOST_REQUIRE( extension(arr) == multi::iextension(2, 7) ); - arr[2] = "a"; - arr[3] = "b"; - arr[4] = "c"; - arr[5] = "d"; - arr[6] = "e"; - BOOST_REQUIRE( std::equal(arr.begin(), arr.end(), mar.begin(), mar.end()) ); + // BOOST_AUTO_TEST_CASE(array_ref_from_carray) + { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif - auto arrB = multi::array({"a", "b", "c", "d", "e"}).reindex(2); // std::string NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( size(arrB) == 5 ); - BOOST_REQUIRE( arrB[2] == "a" ); - BOOST_REQUIRE( arrB[6] == "e" ); -} + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for test + int arr[4][5] = { + { 0, 10, 20, 30, 40}, + { 50, 60, 70, 80, 90}, + {100, 110, 120, 130, 140}, + {150, 160, 170, 180, 190}, + }; -BOOST_AUTO_TEST_CASE(array_ref_of_nested_std_array_reindexed) { - std::array, 4> arr = {{ - {{ 0.0, 1.0, 2.0, 3.0, 4.0}}, - {{ 5.0, 6.0, 7.0, 8.0, 9.0}}, - {{10.0, 11.0, 12.0, 13.0, 14.0}}, - {{15.0, 16.0, 17.0, 18.0, 19.0}} - }}; + multi::array_ptr const map{&arr}; + BOOST_TEST( &(*map).operator[](1)[1] == &arr[1][1] ); + BOOST_TEST( &map->operator[](1)[1] == &arr[1][1] ); - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): test type - multi::array_ref mar = *multi::array_ptr(&arr); - BOOST_REQUIRE( &mar[1][1] == &arr[1][1] ); -} + BOOST_TEST( (*&arr)[1][1] == 60 ); -BOOST_AUTO_TEST_CASE(array_ref_reindexed) { - double (&&arr)[4][5] = { // NOLINT(hicpp-avoid-c-arrays, modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): test - { 0.0, 1.0, 2.0, 3.0, 4.0}, - { 5.0, 6.0, 7.0, 8.0, 9.0}, - {10.0, 11.0, 12.0, 13.0, 14.0}, - {15.0, 16.0, 17.0, 18.0, 19.0} - }; + multi::array_ref&& mar = *map; - // NOLINTNEXTLINE(hicpp-avoid-c-arrays, modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): special type - multi::array_ref mar = *multi::array_ptr(&arr); + BOOST_TEST( &mar[1][1] == &arr[1][1] ); - BOOST_REQUIRE( &mar[1][1] == &arr[1][1] ); - BOOST_REQUIRE( size(mar .reindexed(1)) == size(mar) ); - BOOST_REQUIRE( size(mar[0].reindexed(1)) == size(mar[0]) ); + mar[1][1] = 90; + BOOST_TEST( mar[1][1] == 90 ); + BOOST_TEST( &mar[1][1] == &arr[1][1] ); - BOOST_REQUIRE( sizes(mar.reindexed(1)) == sizes(mar) ); + auto const& a_const = arr; + // int const(&a_const)[4][5] = a; + BOOST_TEST(&a_const[1][1] == &arr[1][1]); - BOOST_REQUIRE( &mar.reindexed(1)[1][0] == &mar[0][0] ); + static_assert(decltype(mar(2, {1, 3}))::rank_v == 1); - BOOST_REQUIRE( sizes(mar[0].reindexed(1)) == sizes(mar[0]) ); - BOOST_REQUIRE( mar[0].reindexed(1).extension().start () == mar[0].extension().start () + 1 ); - BOOST_REQUIRE( mar[0].reindexed(1).extension().finish() == mar[0].extension().finish() + 1 ); + BOOST_TEST( size(mar(2, {1, 3})) == 2 ); + BOOST_TEST( &mar(2, {1, 3})[1] == &arr[2][2] ); - auto diff = &mar[0].reindexed(1)[1] - &mar[0][0]; - BOOST_REQUIRE( diff == 0 ); + [[maybe_unused]] multi::array_ref const& cmar = *map; + // *(cmar.base()) = 99.0; + // *(cmar[0].base()) = 88.0; + // *(cmar.data_elements()) = 99.0; - // BOOST_REQUIRE( &(((mar<<1).reindexed(2)>>1).reindexed(1))[1][2] == &mar[0][0] ); - BOOST_REQUIRE( &mar.reindexed(1, 2)[1][2] == &mar[0][0] ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } - BOOST_REQUIRE( &mar.reindexed(1)({1, 5})[1][0] == &mar[0][0] ); +#ifndef __NVCC__ + // BOOST_AUTO_TEST_CASE(array_ref_test_ub) + { +#if defined(__GNUC__) || defined(__NVCC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif - BOOST_REQUIRE(( sizes(mar.stenciled({2, 4})) == decltype(sizes(mar.stenciled({2, 4}))){2, 5} )); - BOOST_REQUIRE( &mar.stenciled({2, 4})[2][0] == &mar[2][0] ); - BOOST_REQUIRE( &mar.stenciled({2, 4}, {1, 3})[2][1] == &mar[2][1] ); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): test + int arr[][4] = { + { 0, 10, 20, 30}, + { 50, 60, 70, 80}, + {100, 110, 120, 130}, + {150, 160, 170, 180}, + }; - // BOOST_REQUIRE( &mar[0][0] == mar.origin() ); // origin changed meaning in on 2020/Dec/16 - // BOOST_REQUIRE( mar.base() == mar.origin() ); + multi::array_ref const map{arr}; // multi::array_ref const map(&arr[0][0], {4, 4}); - // BOOST_REQUIRE( mar.stenciled({2, 4}).origin() == mar.origin() ); // origin changed meaning in on 2020/Dec/16 - BOOST_REQUIRE( mar.stenciled({2, 4}).base() != mar.base() ); + auto const& diag = map.diagonal(); - BOOST_REQUIRE( &mar.stenciled({2, 4})[2][0] == mar.stenciled({2, 4}).base() ); + BOOST_TEST( diag.begin() != diag.end() ); +// BOOST_TEST( std::accumulate(diag.begin(), diag.end(), 0) == 0 + 6 + 12 + 18 ); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } +#endif + // BOOST_AUTO_TEST_CASE(array_ref_test_no_ub) { - multi::array arrB = { - {"a", "b", "c", "d", "e"}, // std::string NOLINT(fuchsia-default-arguments-calls) - {"f", "g", "h", "f", "g"}, // std::string NOLINT(fuchsia-default-arguments-calls) - {"h", "i", "j", "k", "l"}, // std::string NOLINT(fuchsia-default-arguments-calls) + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): test + int arr[5][4] = { + { 00, 10, 20, 30}, + { 50, 60, 70, 80}, + {100, 110, 120, 130}, + {150, 160, 170, 180}, }; - arrB.reindex(2); - BOOST_REQUIRE( size(arrB) == 3 ); - BOOST_REQUIRE( arrB[2][0] == "a" ); + + multi::array_ref const map(&arr[0][0], {4, 4}); + + auto const& diag = map.diagonal(); + BOOST_TEST( diag.begin() != diag.end() ); + BOOST_TEST( std::accumulate(diag.begin(), diag.end(), 0) == 0 + 60 + 120 + 180 ); + } + + /*use iterator as pointer*/ + { + multi::array const arr = {1, 2, 3, 4, 5, 6}; + multi::array_ref::const_iterator> const arr2({2, 3}, arr.begin()); + BOOST_TEST(( arr2 == multi::array{ + {1, 2, 3}, + {4, 5, 6} + })); } + + /*use iterator as pointer*/ { - multi::array arrB = { - {"a", "b", "c", "d", "e"}, // std::string NOLINT(fuchsia-default-arguments-calls) - {"f", "g", "h", "f", "g"}, // std::string NOLINT(fuchsia-default-arguments-calls) - {"h", "i", "j", "k", "l"}, // std::string NOLINT(fuchsia-default-arguments-calls) + // multi::array const arr = { {1, 2}, {3, 4}, {5, 6}, {7, 8}}; + // multi::array_ref::const_iterator> const arr2({2, 2}, arr.begin()); + // BOOST_TEST(( arr2 == multi::array{ + // {1, 2, 3}, + // {4, 5, 6} + // })); + } + + // BOOST_AUTO_TEST_CASE(array_ref_test_no_ub2) + { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): test + int arr[][4] = { + {}, + {00, 10, 20, 30}, + {50, 60, 70, 80}, + {100, 110, 120, 130}, + {150, 160, 170, 180}, + {}, }; - arrB.reindex(2, 1); - BOOST_REQUIRE( size(arrB) == 3 ); - BOOST_REQUIRE( arrB[2][1] == "a" ); + + multi::array_ref const map(&arr[1][0], {4, 4}); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + auto const& diag = map.diagonal(); + BOOST_TEST( diag.begin() != diag.end() ); + BOOST_TEST( std::accumulate(diag.begin(), diag.end(), 0) == 0 + 60 + 120 + 180 ); } + + // BOOST_AUTO_TEST_CASE(array_ref_test_allocated_ub_unique_ptr) + { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) for illustration + std::unique_ptr const arrp(new int const[4UL * 4UL]{0, 10, 20, 30, 50, 60, 70, 80, 100, 110, 120, 130, 150, 160, 170, 180}); + + BOOST_TEST( arrp[3] == 30 ); + { + multi::array_ref const map(arrp.get(), {4, 4}); + + auto const& diag = map.diagonal(); + + BOOST_TEST( diag.begin() != diag.end() ); + BOOST_TEST( std::accumulate(diag.begin(), diag.end(), 00) == 00 + 60 + 120 + 180 ); // is this UB? + } + } + + // BOOST_AUTO_TEST_CASE(array_ref_1D_reindexed) { using namespace std::string_literals; // NOLINT(build/namespaces) for literal "string"s - multi::array arrB = (multi::array{ - {"a"s, "b"s, "c"s, "d"s, "e"s}, - {"f"s, "g"s, "h"s, "f"s, "g"s}, - {"h"s, "i"s, "j"s, "k"s, "l"s} - }); // .reindex(2, 1); // std::string NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arrB.reindex(2).extension() == multi::iextension(2, 5) ); - auto exts = arrB.reindexed(2).extensions(); + // clang-format off + std::array stdarr = {{"a"s, "b"s, "c"s, "d"s, "e"s}}; // NOLINT(misc-include-cleaner) bug in clang-tidy 18.1.3 + // clang-format on + + multi::array_ref mar = *multi::array_ptr(&stdarr); + + BOOST_TEST( &mar[1] == &stdarr[1] ); + BOOST_TEST( sizes(mar.reindexed(1)) == sizes(mar) ); - multi::array const arrC(exts); - BOOST_REQUIRE( size(arrC) == 3 ); - BOOST_REQUIRE( size(arrC) == size(arrB) ); + auto diff = &(mar.reindexed(1)[1]) - &mar[0]; + BOOST_TEST( diff == 0 ); - BOOST_REQUIRE( arrC.extension().start() == 2 ); - BOOST_REQUIRE( arrC.extension().finish() == 5 ); + BOOST_TEST( &mar.blocked(2, 4)[2] == &mar[2] ); + for(auto idx : extension(mar.stenciled({2, 4}))) { // NOLINT(altera-unroll-loops) + BOOST_TEST( &mar.stenciled({2, 4})[idx] == &mar[idx] ); + } + + // clang-format off + multi::array arr({{2, 7}}, std::string{"xx"}); // NOLINT(fuchsia-default-arguments-calls) std::string + // clang-format on + + BOOST_TEST( size(arr) == 5 ); + BOOST_TEST( extension(arr) == multi::iextension(2, 7) ); + arr[2] = "a"; + arr[3] = "b"; + arr[4] = "c"; + arr[5] = "d"; + arr[6] = "e"; + BOOST_TEST( std::equal(arr.begin(), arr.end(), mar.begin(), mar.end()) ); // NOLINT(modernize-use-ranges) for C++20 } -} -BOOST_AUTO_TEST_CASE(array_ref_with_stencil) { + // BOOST_AUTO_TEST_CASE(array_ref_of_nested_std_array_reindexed) + { + // clang-format off std::array, 4> arr = {{ - {{ 0.0, 1.0, 2.0, 3.0, 4.0}}, - {{ 5.0, 6.0, 7.0, 8.0, 9.0}}, - {{10.0, 11.0, 12.0, 13.0, 14.0}}, - {{15.0, 16.0, 17.0, 18.0, 19.0}} + { { 0.0, 1.0, 2.0, 3.0, 4.0 } }, + { { 5.0, 6.0, 7.0, 8.0, 9.0 } }, + { { 10.0, 11.0, 12.0, 13.0, 14.0 } }, + { { 15.0, 16.0, 17.0, 18.0, 19.0 } } }}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test type - auto const& mar = *multi::array_ptr(&arr); - BOOST_REQUIRE( mar.size() == 4 ); - - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test type - multi::array ss = { - { 0.0, +1.0, 0.0}, - {+1.0, -4.0, +1.0}, - { 0.0, +1.0, 0.0} - }; - auto const& stencil = ss.reindexed(-1, -1); - - BOOST_REQUIRE( stencil.size() == 3 ); - BOOST_REQUIRE( &stencil[-1][-1] == stencil.base() ); -} + // clang-format on -BOOST_AUTO_TEST_CASE(array_ref_1D_from_vector) { - std::vector vec = {1.0, 2.0, 3.0}; // std::vector NOLINT(fuchsia-default-arguments-calls) - multi::array_ref aref({{1, 3}}, vec.data()); - BOOST_REQUIRE( aref.extension() == multi::iextension(1, 3) ); - BOOST_REQUIRE( &aref[1] == vec.data() ); -} + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): test type + multi::array_ref mar = *multi::array_ptr(&arr); + BOOST_TEST( &mar[1][1] == &arr[1][1] ); + } -BOOST_AUTO_TEST_CASE(array_ref_2D_from_vector) { - std::vector vec = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; // std::string NOLINT(fuchsia-default-arguments-calls) - multi::array_ref aref({2, 3}, vec.data()); - BOOST_REQUIRE( &aref[1][0] == &vec[3] ); -} + // BOOST_AUTO_TEST_CASE(array_ref_reindexed) + { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif -BOOST_AUTO_TEST_CASE(array_ref_2D_from_vector_with_offset) { - std::vector vec = { // NOLINT(fuchsia-default-arguments-calls) - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0 - }; - multi::array_ref aref({multi::iextension(1, 3), multi::iextension(1, 4)}, vec.data()); + // NOLINTNEXTLINE(hicpp-avoid-c-arrays, modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): test + double arr[4][5] = { + { 0.0, 1.0, 2.0, 3.0, 4.0}, + { 5.0, 6.0, 7.0, 8.0, 9.0}, + {10.0, 11.0, 12.0, 13.0, 14.0}, + {15.0, 16.0, 17.0, 18.0, 19.0}, + }; - { - auto exts = aref.extensions(); - BOOST_REQUIRE( std::get<0>(exts) == multi::iextension(1, 3) ); - BOOST_REQUIRE( std::get<1>(exts).start() == 1 ); - BOOST_REQUIRE( std::get<1>(exts).finish() == 4 ); - BOOST_REQUIRE( std::get<1>(exts) == multi::iextension(1, 4) ); - BOOST_REQUIRE( exts == decltype(exts)(multi::iextension(1, 3), multi::iextension(1, 4)) ); + // NOLINTNEXTLINE(hicpp-avoid-c-arrays, modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): special type + multi::array_ref mar = *multi::array_ptr(&arr); + + BOOST_TEST( &mar[1][1] == &arr[1][1] ); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + BOOST_TEST( size(mar .reindexed(1)) == size(mar) ); + BOOST_TEST( size(mar[0].reindexed(1)) == size(mar[0]) ); + + BOOST_TEST( sizes(mar.reindexed(1)) == sizes(mar) ); + + BOOST_TEST( &mar.reindexed(1)[1][0] == &mar[0][0] ); + + BOOST_TEST( mar[0].reindexed(1).sizes() == mar[0].sizes() ); + BOOST_TEST( mar[0].reindexed(1).extension().first() == mar[0].extension().first () + 1 ); + BOOST_TEST( mar[0].reindexed(1).extension().last() == mar[0].extension().last() + 1 ); + + auto diff = &mar[0].reindexed(1)[1] - &mar[0][0]; + BOOST_TEST( diff == 0 ); + + // BOOST_TEST( &(((mar<<1).reindexed(2)>>1).reindexed(1))[1][2] == &mar[0][0] ); + BOOST_TEST( &mar.reindexed(1, 2)[1][2] == &mar[0][0] ); + + BOOST_TEST( &mar.reindexed(1)({1, 5})[1][0] == &mar[0][0] ); + + BOOST_TEST(( sizes(mar.stenciled({2, 4})) == decltype(sizes(mar.stenciled({2, 4}))){2, 5} )); + BOOST_TEST( &mar.stenciled({2, 4})[2][0] == &mar[2][0] ); + BOOST_TEST( &mar.stenciled({2, 4}, {1, 3})[2][1] == &mar[2][1] ); + + // BOOST_TEST( &mar[0][0] == mar.origin() ); // origin changed meaning in on 2020/Dec/16 + // BOOST_TEST( mar.base() == mar.origin() ); + + // BOOST_TEST( mar.stenciled({2, 4}).origin() == mar.origin() ); // origin changed meaning in on 2020/Dec/16 + BOOST_TEST( mar.stenciled({2, 4}).base() != mar.base() ); + + BOOST_TEST( &mar.stenciled({2, 4})[2][0] == mar.stenciled({2, 4}).base() ); + + { + // NOLINTBEGIN(fuchsia-default-arguments-calls) std::string ctor + // multi::array arrB = { + // {"a", "b", "c", "d", "e"}, + // {"f", "g", "h", "f", "g"}, + // {"h", "i", "j", "k", "l"}, + // }; + // NOLINTEND(fuchsia-default-arguments-calls) std::string ctor + // arrB.reindex(2); + // BOOST_TEST( size(arrB) == 3 ); + // BOOST_TEST( arrB[2][0] == "a" ); + } + { + // NOLINTBEGIN(fuchsia-default-arguments-calls) std::string ctor + // multi::array arrB = { + // {"a", "b", "c", "d", "e"}, + // {"f", "g", "h", "f", "g"}, + // {"h", "i", "j", "k", "l"}, + // }; + // NOLINTEND(fuchsia-default-arguments-calls) std::string ctor + // arrB.reindex(2, 1); + // BOOST_TEST( size(arrB) == 3 ); + // BOOST_TEST( arrB[2][1] == "a" ); + } + { + // using namespace std::string_literals; // NOLINT(build/namespaces) for literal "string"s + // multi::array arrB = (multi::array{ + // {"a"s, "b"s, "c"s, "d"s, "e"s}, + // {"f"s, "g"s, "h"s, "f"s, "g"s}, + // {"h"s, "i"s, "j"s, "k"s, "l"s}, + // }); // .reindex(2, 1); // std::string NOLINT(fuchsia-default-arguments-calls) + + // BOOST_TEST( arrB.reindex(2).extension() == multi::iextension(2, 5) ); + // auto exts = arrB.reindexed(2).extensions(); + + // multi::array const arrC(exts); + // BOOST_TEST( size(arrC) == 3 ); + // BOOST_TEST( size(arrC) == size(arrB) ); + + // BOOST_TEST( arrC.extension().first() == 2 ); + // BOOST_TEST( arrC.extension().last() == 5 ); + } } + + // BOOST_AUTO_TEST_CASE(array_ref_with_stencil) { - auto const exts = aref.extensions(); - BOOST_REQUIRE( std::get<0>(exts) == multi::iextension(1, 3) ); - BOOST_REQUIRE( std::get<1>(exts).start() == 1 ); - BOOST_REQUIRE( std::get<1>(exts).finish() == 4 ); - BOOST_REQUIRE( std::get<1>(exts) == multi::iextension(1, 4) ); - BOOST_REQUIRE( exts == decltype(exts)(multi::iextension(1, 3), multi::iextension(1, 4)) ); + std::array, 4> arr = { + {{{0.0, 1.0, 2.0, 3.0, 4.0}}, + {{5.0, 6.0, 7.0, 8.0, 9.0}}, + {{10.0, 11.0, 12.0, 13.0, 14.0}}, + {{15.0, 16.0, 17.0, 18.0, 19.0}}}, + }; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test type + auto const& mar = *multi::array_ptr(&arr); + BOOST_TEST( mar.size() == 4 ); + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test type + multi::array ss = { + { 0.0, +1.0, 0.0}, + {+1.0, -4.0, +1.0}, + { 0.0, +1.0, 0.0}, + }; + auto const& stencil = ss.reindexed(-1, -1); + + BOOST_TEST( stencil.size() == 3 ); + BOOST_TEST( &stencil[-1][-1] == stencil.base() ); } + + // BOOST_AUTO_TEST_CASE(array_ref_1D_from_vector) { - BOOST_REQUIRE( std::get<0>(aref.extensions()) == multi::iextension(1, 3) ); - BOOST_REQUIRE( std::get<1>(aref.extensions()).start() == 1 ); - BOOST_REQUIRE( std::get<1>(aref.extensions()).finish() == 4 ); - BOOST_REQUIRE( std::get<1>(aref.extensions()) == multi::iextension(1, 4) ); - BOOST_REQUIRE( aref.extensions() == decltype(aref.extensions())(multi::iextension(1, 3), multi::iextension(1, 4)) ); + std::vector vec = {1.0, 2.0, 3.0}; // std::vector NOLINT(fuchsia-default-arguments-calls) + // clang-format off + multi::array_ref aref({{1, 3}}, vec.data()); + // clang-format on + BOOST_TEST( aref.extension() == multi::iextension(1, 3) ); + BOOST_TEST( &aref[1] == vec.data() ); } + + // BOOST_AUTO_TEST_CASE(array_ref_2D_from_vector) { - auto ss = aref.sizes(); - BOOST_REQUIRE( std::get<0>(ss) == 2 ); - BOOST_REQUIRE( std::get<1>(ss) == 3 ); - BOOST_REQUIRE( ss == decltype(ss)(2, 3) ); + std::vector vec = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; // std::string NOLINT(fuchsia-default-arguments-calls) + + multi::array_ref aref({2, 3}, vec.data()); + + BOOST_TEST( &aref[1][0] == &vec[3] ); } + + // BOOST_AUTO_TEST_CASE(array_ref_from_subarray) { - auto [nn, mm] = aref.sizes(); - BOOST_REQUIRE( nn == 2 ); - BOOST_REQUIRE( mm == 3 ); + std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + + multi::array_ref aref({4, 4}, vec.data()); + + multi::array barr = { + { 1, 2, 3, 4}, + { 5, 6, 7, 8}, + { 9, 10, 11, 12}, + {13, 14, 15, 16} + }; + + aref = barr(); } + + // BOOST_AUTO_TEST_CASE(array_ref_2D_from_vector_with_offset) { - auto const ss = aref.sizes(); - BOOST_REQUIRE( std::get<0>(ss) == 2 ); - BOOST_REQUIRE( std::get<1>(ss) == 3 ); - BOOST_REQUIRE( ss == decltype(ss)(2, 3) ); + // NOLINTNEXTLINE(fuchsia-default-arguments-calls) + std::vector vec = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + + multi::array_ref aref({multi::iextension(1, 3), multi::iextension(1, 4)}, vec.data()); + + { + auto exts = aref.extensions(); + auto const [exts0, exts1] = exts; + BOOST_TEST( exts0 == multi::iextension(1, 3) ); + + BOOST_TEST( exts1.first() == 1 ); + BOOST_TEST( exts1.last () == 4 ); + + BOOST_TEST( exts1 == multi::iextension(1, 4) ); + + BOOST_TEST( exts == decltype(exts)(multi::iextension(1, 3), multi::iextension(1, 4)) ); + } + { + auto exts = aref.extensions(); + + using std::get; + + BOOST_TEST( get<0>(exts) == multi::iextension(1, 3) ); + BOOST_TEST( get<1>(exts).first() == 1 ); + BOOST_TEST( get<1>(exts).last () == 4 ); + BOOST_TEST( get<1>(exts) == multi::iextension(1, 4) ); + BOOST_TEST( exts == decltype(exts)(multi::iextension(1, 3), multi::iextension(1, 4)) ); + } + { + auto const exts = aref.extensions(); + + using std::get; + + BOOST_TEST( get<0>(exts) == multi::iextension(1, 3) ); + BOOST_TEST( get<1>(exts).first() == 1 ); + BOOST_TEST( get<1>(exts).last () == 4 ); + BOOST_TEST( get<1>(exts) == multi::iextension(1, 4) ); + + BOOST_TEST( exts == decltype(exts)(multi::iextension(1, 3), multi::iextension(1, 4)) ); + } + { + auto const exts = aref.extensions(); + BOOST_TEST( exts.get<0>() == multi::iextension(1, 3) ); + BOOST_TEST( exts.get<1>().first() == 1 ); + BOOST_TEST( exts.get<1>().last () == 4 ); + BOOST_TEST( exts.get<1>() == multi::iextension(1, 4) ); + BOOST_TEST(( exts == decltype(exts){multi::iextension(1, 3), multi::iextension(1, 4)} )); + } + { + auto const exts = aref.extensions(); + + using std::get; + + BOOST_TEST( get<0>(exts) == multi::iextension(1, 3) ); + BOOST_TEST( get<1>(exts).first() == 1 ); + BOOST_TEST( get<1>(exts).last () == 4 ); + BOOST_TEST( get<1>(exts) == multi::iextension(1, 4) ); + + BOOST_TEST( exts == decltype(exts)(multi::iextension(1, 3), multi::iextension(1, 4)) ); + } + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + BOOST_TEST( get<0>(aref.extensions()) == multi::iextension(1, 3) ); + + BOOST_TEST( get<1>(aref.extensions()).first() == 1 ); + BOOST_TEST( get<1>(aref.extensions()).last () == 4 ); + + BOOST_TEST( get<1>(aref.extensions()) == multi::iextension(1, 4) ); + BOOST_TEST( aref.extensions() == decltype(aref.extensions())(multi::iextension(1, 3), multi::iextension(1, 4)) ); + } + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + auto ss = aref.sizes(); + BOOST_TEST( get<0>(ss) == 2 ); + BOOST_TEST( get<1>(ss) == 3 ); + BOOST_TEST( ss == decltype(ss)(2, 3) ); + } + { + auto [nn, mm] = aref.sizes(); + BOOST_TEST( nn == 2 ); + BOOST_TEST( mm == 3 ); + } + { + using std::get; + + auto const ss = aref.sizes(); + BOOST_TEST( get<0>(ss) == 2 ); + BOOST_TEST( get<1>(ss) == 3 ); + BOOST_TEST( ss == decltype(ss)(2, 3) ); + } + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + BOOST_TEST( get<0>(aref.sizes()) == 2 ); + BOOST_TEST( get<1>(aref.sizes()) == 3 ); + BOOST_TEST( aref.sizes() == decltype(aref.sizes())(2, 3) ); + } + { + auto const ss = aref.sizes(); + + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + BOOST_TEST( get<0>(ss) == 2 ); + BOOST_TEST( get<1>(ss) == 3 ); + BOOST_TEST( ss == decltype(ss)(2, 3) ); + } + { + using std::get; + BOOST_TEST( get<0>(aref.sizes()) == 2 ); + BOOST_TEST( get<1>(aref.sizes()) == 3 ); + BOOST_TEST( aref.sizes() == decltype(aref.sizes())(2, 3) ); + } + + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + auto const ss = aref.sizes(); + BOOST_TEST( get<0>(ss) == 2 ); + BOOST_TEST( get<1>(ss) == 3 ); + BOOST_TEST( ss == decltype(ss)(2, 3) ); + } + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + BOOST_TEST( get<0>(aref.sizes()) == 2 ); + BOOST_TEST( get<1>(aref.sizes()) == 3 ); + BOOST_TEST( aref.sizes() == decltype(aref.sizes())(2, 3) ); + } + + BOOST_TEST( &aref[1][1] == vec.data() ); } + + // BOOST_AUTO_TEST_CASE(array_2D_with_offset) { - BOOST_REQUIRE( std::get<0>(aref.sizes()) == 2 ); - BOOST_REQUIRE( std::get<1>(aref.sizes()) == 3 ); - BOOST_REQUIRE( aref.sizes() == decltype(aref.sizes())(2, 3) ); + multi::array const arr({multi::iextension(1, 3), multi::iextension(2, 5)}, 1.2); + + BOOST_TEST( arr.extension().first() == 1 ); + BOOST_TEST( arr.extension().last () == 3 ); } + + // BOOST_AUTO_TEST_CASE(array_ref_1D) { - auto const ss = aref.sizes(); - using std::get; - BOOST_REQUIRE( get<0>(ss) == 2 ); - BOOST_REQUIRE( get<1>(ss) == 3 ); - BOOST_REQUIRE( ss == decltype(ss)(2, 3) ); + // clang-format off + // NOLINTNEXTLINE(fuchsia-default-arguments-calls) + std::array arr = {{"a", "b", "c", "d", "e"}}; + // clang-format on + multi::array_ref&& mar = *multi::array_ptr{&arr}; + // multi::Array mar = *multi::Array(&a); + + BOOST_TEST( extension(mar).first() == 0 ); + BOOST_TEST( extension(mar).last() == 5 ); + + auto&& mar1 = mar.reindexed(1); + + BOOST_TEST( extension(mar1).size() == extension(mar).size() ); + + BOOST_TEST( mar1.extension() == extension(mar1) ); + BOOST_TEST( extension(mar1).first() == 1 ); + BOOST_TEST( mar1.extension().first() == 1 ); + BOOST_TEST( mar1.extension().last() == 6 ); + BOOST_TEST( *extension(mar1).begin() == 1 ); + + BOOST_TEST( size(mar1) == size(mar) ); + BOOST_TEST( mar1.layout().extension().first() == 1 ); + BOOST_TEST( extension(mar1).first() == 1 ); + BOOST_TEST( &mar1[1] == &arr[0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( mar1.base() == &arr[0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( mar1.base() == arr.data() ); } + + // BOOST_AUTO_TEST_CASE(array_ref_original_tests_carray) { - using std::get; - BOOST_REQUIRE( get<0>(aref.sizes()) == 2 ); - BOOST_REQUIRE( get<1>(aref.sizes()) == 3 ); - BOOST_REQUIRE( aref.sizes() == decltype(aref.sizes())(2, 3) ); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + int darr[4][5] = { + {1, 2}, + {2, 3}, + }; + multi::array_ref ref(&darr[0][0], {4, 5}); + multi::array_ref cref(&darr[0][0], {4, 5}); + multi::array_ref crefc(&darr[0][0], {4, 5}); + multi::array_cref ref2(&darr[0][0], {4, 5}); + + BOOST_TEST( &ref[1][2] == &cref [1][2] ); + BOOST_TEST( &ref[1][2] == &crefc[1][2] ); + BOOST_TEST( &ref[1][2] == & ref2[1][2] ); + + ref[1][1] = 20; + BOOST_TEST( ref[1][1] == 20 ); + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + int darr2[4][5] = { + {10, 00}, + {20, 30}, + }; + + darr2[1][0] = 20; + BOOST_TEST( darr2[1][0] == 20 ); + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + auto const& dd = std::as_const(darr2); + + BOOST_TEST( &(dd[1][2]) == &(darr2[1][2]) ); + BOOST_TEST(( & ref[1].static_array_cast()[1] == &ref[1][1] )); + BOOST_TEST(( &multi::static_array_cast(ref[1])[1] == &ref[1][1] )); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } - #if __cplusplus >= 202002L // GCC: use of function template name with no prior declaration in function call with explicit template arguments is a C++20 extension + + // BOOST_AUTO_TEST_CASE(array_ref_cast_carray) { - auto const ss = aref.sizes(); - BOOST_REQUIRE( get<0>(ss) == 2 ); - BOOST_REQUIRE( get<1>(ss) == 3 ); - BOOST_REQUIRE( ss == decltype(ss)(2, 3) ); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + double darr[2][2] = { + {1.0, 2.0}, + {2.0, 3.0}, + }; + multi::array_ref ref(&darr[0][0], {2, 2}); + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + auto&& other_darr = static_cast(ref); + + // NOLINTNEXTLINE(hicpp-use-auto,modernize-use-auto,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + double(&other_darr2)[2][2] = static_cast(ref); + double(&other_darr3)[2][2](ref); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + + BOOST_TEST( &ref [1][0] == &darr[1][0] ); + BOOST_TEST( &other_darr [1][0] == &darr[1][0] ); + BOOST_TEST( &other_darr2[1][0] == &darr[1][0] ); + BOOST_TEST( &other_darr3[1][0] == &darr[1][0] ); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + // TODO(correaa) adapt this test to Boost Test Lightweight + // Homebrew GCC-13 terminates rather than having the expected exception caught. + // MSVC 17.10 fails to compile + // #if !(defined(__GNUC__) && __GNUC__ >= 5 && defined(__APPLE__)) && !defined(_MSC_VER) + // BOOST_REQUIRE_THROW( + // ([&] { + // double(&other_darr4)[3][3](ref); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + // other_darr4[1][1] += 1.0; + // }()), + // std::bad_cast + // ); + // #endif } + + // BOOST_AUTO_TEST_CASE(array_ref_original_tests_const_carray) { - BOOST_REQUIRE( get<0>(aref.sizes()) == 2 ); - BOOST_REQUIRE( get<1>(aref.sizes()) == 3 ); - BOOST_REQUIRE( aref.sizes() == decltype(aref.sizes())(2, 3) ); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + double const d2D[4][5] = { + {1.0, 2.0}, + {2.0, 3.0}, + }; + multi::array_ref d2Rce(&d2D[0][0], {4, 5}); + + BOOST_TEST( &d2Rce[2][3] == &d2D[2][3] ); + BOOST_TEST( d2Rce.size() == 4 ); + BOOST_TEST( num_elements(d2Rce) == 20 ); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } - #endif - BOOST_REQUIRE( &aref[1][1] == vec.data() ); -} + // BOOST_AUTO_TEST_CASE(array_ref_original_tests_const_carray_string) + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + // NOLINTBEGIN(fuchsia-default-arguments-calls) std::string ctor + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + std::string const dc3D[4][2][3] = { + {{"A0a", "A0b", "A0c"}, {"A1a", "A1b", "A1c"}}, + {{"B0a", "B0b", "B0c"}, {"B1a", "B1b", "B1c"}}, + {{"C0a", "C0b", "C0c"}, {"C1a", "C1b", "C1c"}}, + {{"D0a", "D0b", "D0c"}, {"D1a", "D1b", "D1c"}}, + }; + // NOLINTEND(fuchsia-default-arguments-calls) std::string ctor -BOOST_AUTO_TEST_CASE(array_2D_with_offset) { - multi::array const arr({multi::iextension(1, 3), multi::iextension(2, 5)}, 1.2); + multi::array_cref cref(&dc3D[0][0][0], {4, 2, 3}); + BOOST_TEST( num_elements(cref) == 24 && cref[2][1][1] == "C1b" ); - BOOST_REQUIRE( arr.extension().start() == 1 ); - BOOST_REQUIRE( arr.extension().finish() == 3 ); -} + auto const& A2 = cref.sliced(0, 3).rotated()[1].sliced(0, 2).unrotated(); + BOOST_TEST( multi::rank>{} == 2 && num_elements(A2) == 6 ); -BOOST_AUTO_TEST_CASE(array_ref_1D) { - std::array arr = {{"a", "b", "c", "d", "e"}}; // std::string NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( get<0>(sizes(A2)) == 3 && get<1>(sizes(A2)) == 2 ); - multi::array_ref&& mar = *multi::array_ptr{&arr}; -// multi::Array mar = *multi::Array(&a); + auto const& A3 = cref({0, 3}, 1, {0, 2}); + BOOST_TEST( multi::rank>{} == 2 && num_elements(A3) == 6 ); - BOOST_REQUIRE( extension(mar).first() == 0 ); - BOOST_REQUIRE( extension(mar).last() == 5 ); + BOOST_TEST( A2.layout()[2][1] == &A2[2][1] - A2.base() ); + BOOST_TEST( A2.rotated().layout()[1][2] == &A2.rotated()[1][2] - A2.rotated().base() ); + } - auto&& mar1 = mar.reindexed(1); + // BOOST_AUTO_TEST_CASE(array_ref_sizes_assingment) + { + multi::array_cref const cref(nullptr, {4, 2, 3}); + { + auto [sizes1, sizes2, sizes3] = cref.sizes(); + BOOST_TEST( sizes1 == 4 ); + BOOST_TEST( sizes2 == 2 ); + BOOST_TEST( sizes3 == 3 ); + } + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + auto sizes1 = get<0>(cref.sizes()); + auto sizes2 = get<1>(cref.sizes()); + auto sizes3 = get<2>(cref.sizes()); + BOOST_TEST( sizes1 == 4 ); + BOOST_TEST( sizes2 == 2 ); + BOOST_TEST( sizes3 == 3 ); + } + // { + // multi::size_t sizes1; // NOLINT(cppcoreguidelines-init-variables) + // multi::size_t sizes2; // NOLINT(cppcoreguidelines-init-variables) + // multi::size_t sizes3; // NOLINT(cppcoreguidelines-init-variables) + // std::tie(sizes1, sizes2, sizes3) = cref.sizes(); + + // BOOST_TEST( sizes1 == 4 ); + // BOOST_TEST( sizes2 == 2 ); + // BOOST_TEST( sizes3 == 3 ); + // } + { + multi::size_t sizes1; // NOLINT(cppcoreguidelines-init-variables) + multi::size_t sizes2; // NOLINT(cppcoreguidelines-init-variables) + multi::size_t sizes3; // NOLINT(cppcoreguidelines-init-variables) + multi::tie(sizes1, sizes2, sizes3) = cref.sizes(); + + BOOST_TEST( sizes1 == 4 ); + BOOST_TEST( sizes2 == 2 ); + BOOST_TEST( sizes3 == 3 ); + } + { + auto const [sizes1, sizes2, sizes3] = cref.sizes(); + + BOOST_TEST( sizes1 == 4 ); + BOOST_TEST( sizes2 == 2 ); + BOOST_TEST( sizes3 == 3 ); + } + // { + // // NOLINTNEXTLINE(runtime/int) + // long sizes1; // NOLINT(google-runtime-int,cppcoreguidelines-init-variables) test bad idiom + // // NOLINTNEXTLINE(runtime/int) + // long sizes2; // NOLINT(google-runtime-int,cppcoreguidelines-init-variables) test bad idiom + // // NOLINTNEXTLINE(runtime/int) + // long sizes3; // NOLINT(google-runtime-int,cppcoreguidelines-init-variables) test bad idiom + + // multi::tie(sizes1, sizes2, sizes3) = static_cast>(cref.sizes()); + + // BOOST_TEST( sizes1 == 4L ); + // BOOST_TEST( sizes2 == 2L ); + // BOOST_TEST( sizes3 == 3L ); + // } + { + // NOLINTNEXTLINE(runtime/int) + long long sizes1; // NOLINT(google-runtime-int,cppcoreguidelines-init-variables) test bad idiom + // NOLINTNEXTLINE(runtime/int) + long long sizes2; // NOLINT(google-runtime-int,cppcoreguidelines-init-variables) test bad idiom + // NOLINTNEXTLINE(runtime/int) + long long sizes3; // NOLINT(google-runtime-int,cppcoreguidelines-init-variables) test bad idiom + // std::tie(sizes1, sizes2, sizes3) = cref.sizes(); + multi::tie(sizes1, sizes2, sizes3) = cref.sizes(); + + BOOST_TEST( sizes1 == 4 ); + BOOST_TEST( sizes2 == 2 ); + BOOST_TEST( sizes3 == 3 ); + } + { + int64_t sizes1; // NOLINT(cppcoreguidelines-init-variables) + int64_t sizes2; // NOLINT(cppcoreguidelines-init-variables) + int64_t sizes3; // NOLINT(cppcoreguidelines-init-variables) + // std::tie(sizes1, sizes2, sizes3) = cref.sizes(); + multi::tie(sizes1, sizes2, sizes3) = cref.sizes(); + + BOOST_TEST( sizes1 == 4 ); + BOOST_TEST( sizes2 == 2 ); + BOOST_TEST( sizes3 == 3 ); + } + } - BOOST_REQUIRE( extension(mar1).size() == extension(mar).size() ); + // BOOST_AUTO_TEST_CASE(array_ref_rebuild_2D) { + // // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + // int d2D[4][5] = { + // {10, 20}, + // {20, 30}, + // }; + // multi::array_ref d2R(&d2D[0][0], {4, 5}); - BOOST_REQUIRE( mar1.extension() == extension(mar1) ); - BOOST_REQUIRE( extension(mar1).first() == 1 ); - BOOST_REQUIRE( mar1.extension().first() == 1 ); - BOOST_REQUIRE( mar1.extension().last() == 6 ); - BOOST_REQUIRE( *extension(mar1).begin() == 1 ); + // auto&& d2B = d2R(); + // auto&& d2B_ref = multi::ref(d2B.begin(), d2B.end()); - BOOST_REQUIRE( size(mar1) == size(mar) ); - BOOST_REQUIRE( mar1.layout().extension().start() == 1 ); - BOOST_REQUIRE( extension(mar1).start() == 1 ); - BOOST_REQUIRE( &mar1[1] == &arr[0] ); // NOLINT(readability-container-data-pointer) test access - BOOST_REQUIRE( mar1.base() == &arr[0] ); // NOLINT(readability-container-data-pointer) test access - BOOST_REQUIRE( mar1.base() == arr.data() ); -} + // BOOST_TEST( d2B[0][0] == d2B_ref[0][0] ); + // BOOST_TEST( &d2B[0][0] == &d2B_ref[0][0] ); -BOOST_AUTO_TEST_CASE(array_ref_original_tests_carray) { - double darr[4][5] = {{1.0, 2.0}, {2.0, 3.0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - multi::array_ref ref (&darr[0][0], {4, 5}); - multi::array_ref cref (&darr[0][0], {4, 5}); - multi::array_ref crefc(&darr[0][0], {4, 5}); - multi::array_cref ref2(&darr[0][0], {4, 5}); + // BOOST_TEST( d2B.base() == d2B_ref.base() ); + // BOOST_TEST( d2B.layout() == d2B_ref.layout() ); - BOOST_REQUIRE( &ref[1][2] == &cref [1][2] ); - BOOST_REQUIRE( &ref[1][2] == &crefc[1][2] ); - BOOST_REQUIRE( &ref[1][2] == & ref2[1][2] ); + // BOOST_TEST( &d2R() == &multi::ref(d2B.begin(), d2B.end()) ); + // } - ref[1][1] = 2.0; + // BOOST_AUTO_TEST_CASE(array_ref_rebuild_1D) { + // double d1D[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - double darr2[4][5] = {{1.0, 0.0}, {2.0, 3.0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - darr2[1][0] = 2.0; + // multi::array_ref d1R(&d1D[0], {5}); - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - auto const& dd = std::as_const(darr2); + // auto&& d1B = d1R(); + // auto&& d1B_ref = multi::ref(d1B.begin(), d1B.end()); - BOOST_REQUIRE( &(dd[1][2]) == &(darr2[1][2]) ); - BOOST_REQUIRE(( & ref[1].static_array_cast()[1] == &ref[1][1] )); - BOOST_REQUIRE(( &multi::static_array_cast(ref[1])[1] == &ref[1][1] )); -} + // BOOST_TEST( d1B.base() == d1B_ref.base() ); + // BOOST_TEST( d1B.layout() == d1B_ref.layout() ); + // BOOST_TEST( &d1R() == &multi::ref(d1B.begin(), d1B.end()) ); + //} -BOOST_AUTO_TEST_CASE(array_ref_cast_carray) { - double darr[2][2] = {{1.0, 2.0}, {2.0, 3.0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - multi::array_ref ref(&darr[0][0], {2, 2}); + // BOOST_AUTO_TEST_CASE(array_ref_move_assigment_2D) + { + { + multi::array arr({5, 4}); + std::iota(arr.elements().begin(), arr.elements().end(), 0.); - auto&& other_darr = static_cast(ref); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + multi::array arr2({5, 4}); + std::iota(arr2.elements().begin(), arr2.elements().end(), 10.); - double(& other_darr2)[2][2] = static_cast(ref); // NOLINT(hicpp-use-auto,modernize-use-auto,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - double(& other_darr3)[2][2](ref); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + auto&& Aref = multi::array_ref({5, 4}, arr.data_elements()); + auto&& Bref = multi::array_ref({5, 4}, arr2.data_elements()); - BOOST_REQUIRE( &ref [1][0] == &darr[1][0] ); - BOOST_REQUIRE( &other_darr [1][0] == &darr[1][0] ); - BOOST_REQUIRE( &other_darr2[1][0] == &darr[1][0] ); - BOOST_REQUIRE( &other_darr3[1][0] == &darr[1][0] ); + Bref = Aref; - try { - double(& other_darr4)[3][3](ref); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + BOOST_TEST( arr2 == arr ); + } + { + multi::array arr({5, 4}); + std::iota(arr.elements().begin(), arr.elements().end(), 0.0); - BOOST_REQUIRE( &other_darr4[1][0] == &darr[1][0] ); - } catch(...) {} -} + multi::array arr2({5, 4}); + std::iota(arr2.elements().begin(), arr2.elements().end(), 10.0); -BOOST_AUTO_TEST_CASE(array_ref_original_tests_const_carray) { - double const d2D[4][5] = {{1.0, 2.0}, {2.0, 3.0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - multi::array_ref d2Rce(&d2D[0][0], {4, 5}); + auto&& ref2 = multi::array_ref({5, 4}, arr2.data_elements()); - BOOST_REQUIRE( &d2Rce[2][3] == &d2D[2][3] ); - BOOST_REQUIRE( d2Rce.size() == 4 ); - BOOST_REQUIRE( num_elements(d2Rce) == 20 ); -} + ref2 = multi::array_ref({5, 4}, arr.data_elements()); -BOOST_AUTO_TEST_CASE(array_ref_original_tests_const_carray_string) { - std::string const dc3D[4][2][3] = { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - {{"A0a", "A0b", "A0c"}, {"A1a", "A1b", "A1c"}}, // std::string NOLINT(fuchsia-default-arguments-calls) - {{"B0a", "B0b", "B0c"}, {"B1a", "B1b", "B1c"}}, // std::string NOLINT(fuchsia-default-arguments-calls) - {{"C0a", "C0b", "C0c"}, {"C1a", "C1b", "C1c"}}, // std::string NOLINT(fuchsia-default-arguments-calls) - {{"D0a", "D0b", "D0c"}, {"D1a", "D1b", "D1c"}}, // std::string NOLINT(fuchsia-default-arguments-calls) - }; - multi::array_cref cref(&dc3D[0][0][0], {4, 2, 3}); - BOOST_REQUIRE( num_elements(cref) == 24 and cref[2][1][1] == "C1b" ); - auto const& A2 = cref.sliced(0, 3).rotated()[1].sliced(0, 2).unrotated(); - BOOST_REQUIRE( multi::rank>{} == 2 and num_elements(A2) == 6 ); - - BOOST_REQUIRE( std::get<0>(sizes(A2)) == 3 and std::get<1>(sizes(A2)) == 2 ); - - auto const& A3 = cref({0, 3}, 1, {0, 2}); - BOOST_REQUIRE( multi::rank>{} == 2 and num_elements(A3) == 6 ); - - BOOST_REQUIRE( A2.layout()[2][1] == &A2[2][1] - A2.base() ); - BOOST_REQUIRE( A2.rotated().layout()[1][2] == &A2.rotated()[1][2] - A2.rotated().base() ); -} + BOOST_TEST( arr2 == arr ); + } + { + multi::array arr({5, 4}); + std::iota(arr.elements().begin(), arr.elements().end(), 0.0); -BOOST_AUTO_TEST_CASE(array_ref_sizes_assingment) { - multi::array_cref const cref(nullptr, {4, 2, 3}); - { - auto [sizes1, sizes2, sizes3] = cref.sizes(); - BOOST_REQUIRE( sizes1 == 4 ); - BOOST_REQUIRE( sizes2 == 2 ); - BOOST_REQUIRE( sizes3 == 3 ); - } - { - auto sizes1 = std::get<0>(cref.sizes()); - auto sizes2 = std::get<1>(cref.sizes()); - auto sizes3 = std::get<2>(cref.sizes()); - BOOST_REQUIRE( sizes1 == 4 ); - BOOST_REQUIRE( sizes2 == 2 ); - BOOST_REQUIRE( sizes3 == 3 ); - } - { - multi::size_t sizes1, sizes2, sizes3; // NOLINT(readability-isolate-declaration,cppcoreguidelines-init-variables) test a bad idiom - multi::tie(sizes1, sizes2, sizes3) = cref.sizes(); + multi::array arr2({5, 4}); + std::iota(arr2.elements().begin(), arr2.elements().end(), 10.0); - BOOST_REQUIRE( sizes1 == 4 ); - BOOST_REQUIRE( sizes2 == 2 ); - BOOST_REQUIRE( sizes3 == 3 ); - } - { - auto const [sizes1, sizes2, sizes3] = cref.sizes(); + auto&& ref = multi::array_ref({5, 4}, arr.data_elements()); + auto&& ref2 = multi::array_ref({5, 4}, arr2.data_elements()); - BOOST_REQUIRE( sizes1 == 4 ); - BOOST_REQUIRE( sizes2 == 2 ); - BOOST_REQUIRE( sizes3 == 3 ); + ref2 = std::move(ref); + + BOOST_TEST( arr2 == arr ); + } } + + // BOOST_AUTO_TEST_CASE(array_ref_conversion_1D) { - // NOLINTNEXTLINE(runtime/int) - long sizes1, sizes2, sizes3; // NOLINT(google-runtime-int,readability-isolate-declaration,cppcoreguidelines-init-variables) test bad idiom - multi::tie(sizes1, sizes2, sizes3) = cref.sizes(); + multi::array arr({5}, int{}); + BOOST_TEST( arr.size() == 5 ); + std::iota(arr.elements().begin(), arr.elements().end(), 0); + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif - BOOST_REQUIRE( sizes1 == 4 ); - BOOST_REQUIRE( sizes2 == 2 ); - BOOST_REQUIRE( sizes3 == 3 ); + { + auto& carr = static_cast(arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + BOOST_TEST( &carr[3] == &arr[3] ); + + BOOST_TEST(f1d5(static_cast(arr)) == 1 ); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + } + { + int(&carr)[5](arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + BOOST_TEST( &carr[3] == &arr[3] ); + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } + + // BOOST_AUTO_TEST_CASE(array_ref_conversion_2D) { - // NOLINTNEXTLINE(runtime/int) - long long sizes1, sizes2, sizes3; // NOLINT(google-runtime-int,readability-isolate-declaration,cppcoreguidelines-init-variables) test bad idiom - multi::tie(sizes1, sizes2, sizes3) = cref.sizes(); + multi::array arr({5, 4}); + std::iota(arr.elements().begin(), arr.elements().end(), 0); + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + { + auto& carr = static_cast(arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + BOOST_TEST( &carr[3][2] == &arr[3][2] ); - BOOST_REQUIRE( sizes1 == 4 ); - BOOST_REQUIRE( sizes2 == 2 ); - BOOST_REQUIRE( sizes3 == 3 ); + f2d54(static_cast(arr)); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + } + { + int(&carr)[5][4](arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + BOOST_TEST( &carr[3][2] == &arr[3][2] ); + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } + +#ifndef _MSC_VER + // BOOST_AUTO_TEST_CASE(as_span) { - int64_t sizes1, sizes2, sizes3; // NOLINT(readability-isolate-declaration,cppcoreguidelines-init-variables) test bad idiom - multi::tie(sizes1, sizes2, sizes3) = cref.sizes(); +#ifdef BOOST_MULTI_HAS_SPAN + auto print_me0 = [](std::span rng) { + std::cout << "rng.size(): " << rng.size() << '\n'; // (4) + std::for_each(rng.begin(), rng.end(), [](auto const& elem) { std::cout << elem << ' '; }); // NOLINT(modernize-use-ranges) + std::cout << "\n\n"; + }; +#endif - BOOST_REQUIRE( sizes1 == 4 ); - BOOST_REQUIRE( sizes2 == 2 ); - BOOST_REQUIRE( sizes3 == 3 ); - } -} + auto print_me1 = [](multi::array_ref const& rng) -> void { + std::cout << "rng.size(): " << rng.size() << '\n'; // (4) + std::for_each(rng.begin(), rng.end(), [](auto const& elem) -> void { std::cout << elem << ' '; }); // NOLINT(modernize-use-ranges) + std::cout << "\n\n"; + }; -BOOST_AUTO_TEST_CASE(array_ref_rebuild_2D) { - double d2D[4][5] = {{1.0, 2.0}, {2.0, 3.0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - multi::array_ref d2R(&d2D[0][0], {4, 5}); - auto&& d2B = d2R(); - auto&& d2B_ref = multi::ref(d2B.begin(), d2B.end()); + auto print_me2 = [](multi::array_ptr const& ptr) { + std::cout << "ptr->size(): " << ptr->size() << '\n'; // (4) + std::for_each(ptr->begin(), ptr->end(), [](auto const& elem) { std::cout << elem << ' '; }); + std::cout << "\n\n"; + }; - BOOST_REQUIRE( d2B[0][0] == d2B_ref[0][0] ); - BOOST_REQUIRE( &d2B[0][0] == &d2B_ref[0][0] ); +#ifdef BOOST_MULTI_HAS_SPAN + { + int arr[] = {1, 2, 3, 4}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy arrays + print_me0(arr); - BOOST_REQUIRE( d2B.base() == d2B_ref.base() ); - BOOST_REQUIRE( d2B.layout() == d2B_ref.layout() ); + // vvv this fails in certain versions of clang (14?ss) + // std::vector vec = {1, 2, 3, 4, 5}; // NOLINT(fuchsia-default-arguments-calls) + // print_me0(vec); - BOOST_REQUIRE( &d2R() == &multi::ref(d2B.begin(), d2B.end()) ); -} + // clang-format off + std::array arr2 = {{1, 2, 3, 4, 5, 6}}; + // clang-format on -BOOST_AUTO_TEST_CASE(array_ref_rebuild_1D) { - double d1D[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - multi::array_ref d1R(&d1D[0], {5}); - auto&& d1B = d1R(); - auto&& d1B_ref = multi::ref(d1B.begin(), d1B.end()); + print_me0(arr2); + } +#endif + { + int arr[] = {1, 2, 3, 4}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test c-arrays - BOOST_REQUIRE( d1B.base() == d1B_ref.base() ); - BOOST_REQUIRE( d1B.layout() == d1B_ref.layout() ); - BOOST_REQUIRE( &d1R() == &multi::ref(d1B.begin(), d1B.end()) ); -} + print_me1(multi::array_ref{arr}); + print_me1(arr); -BOOST_AUTO_TEST_CASE(array_ref_move_assigment_2D) { - { - multi::array arr({5, 4}); - std::iota(arr.elements().begin(), arr.elements().end(), 0.); + std::vector vec = {1, 2, 3, 4, 5}; // NOLINT(fuchsia-default-arguments-calls) - multi::array arr2({5, 4}); - std::iota(arr2.elements().begin(), arr2.elements().end(), 10.); + print_me1(*multi::array_ptr{vec.data(), 5}); - multi::array_ref&& Aref{{5, 4}, arr .data_elements()}; - multi::array_ref&& Bref{{5, 4}, arr2.data_elements()}; + // clang-format off + std::array arr2 = {{1, 2, 3, 4, 5, 6}}; + // clang-format on - Bref = Aref; + print_me1(arr2); + print_me1(*multi::array_ptr{arr2.data(), {6}}); - BOOST_REQUIRE( arr2 == arr ); - } - { - multi::array arr({5, 4}); - std::iota(arr.elements().begin(), arr.elements().end(), 0.0); + multi::static_array marr( + // #ifdef _MSC_VER // problems with MSVC 14.3 c++17 + multi::extensions_t<1> + // #endif + {10}, + 99 + ); + + print_me1(*multi::array_ptr(marr.data_elements(), 10)); + + // #ifndef _MSC_VER + auto& alias = marr; - multi::array arr2({5, 4}); - std::iota(arr2.elements().begin(), arr2.elements().end(), 10.0); + marr = alias; + BOOST_TEST(marr[5] == 99); - multi::array_ref&& ref2{{5, 4}, arr2.data_elements()}; + marr = alias(); + BOOST_TEST(marr[5] == 99); + // #endif + } + { + int arr[] = {1, 2, 3, 4}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test c-arrays + print_me2(multi::array_ptr{&arr}); + print_me2(&arr); - ref2 = multi::array_ref({5, 4}, arr.data_elements()); + std::vector vec = {1, 2, 3, 4, 5}; // NOLINT(fuchsia-default-arguments-calls) + print_me2({vec.data(), 5}); - BOOST_REQUIRE( arr2 == arr ); + // clang-format off + std::array arr2 = {{1, 2, 3, 4, 5, 6}}; + // clang-format on + + // print_me2(&arr2); // this crashes clang-tidy + print_me2({arr2.data(), {6}}); + + // multi::static_array marr({10}, 99); + // print_me2(&marr); // TODO(correaa) make this work + } } +#endif + + // BOOST_AUTO_TEST_CASE(diagonal) { - multi::array arr({5, 4}); - std::iota(arr.elements().begin(), arr.elements().end(), 0.0); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif - multi::array arr2({5, 4}); - std::iota(arr2.elements().begin(), arr2.elements().end(), 10.0); + // NOLINTNEXTLINE(hicpp-avoid-c-arrays, modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): test + int arr[4][3] = { + { 0, 10, 20}, + { 50, 10, 70}, + {100, 110, 20}, + {990, 990, 999}, + }; - auto&& ref = multi::array_ref({5, 4}, arr.data_elements()); - auto&& ref2 = multi::array_ref({5, 4}, arr2.data_elements()); + // NOLINTNEXTLINE(hicpp-avoid-c-arrays, modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): special type + multi::array_ref mar = *multi::array_ptr(&arr); - ref2 = std::move(ref); + BOOST_TEST( &mar({0, 3}, {0, 3}).diagonal()[0] == &arr[0][0] ); + BOOST_TEST( &mar({0, 3}, {0, 3}).diagonal()[1] == &arr[1][1] ); + BOOST_TEST( &mar({0, 3}, {0, 3}).diagonal()[2] == &arr[2][2] ); - BOOST_REQUIRE( arr2 == arr ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + auto sum = 0; + + // NOLINTNEXTLINE(altera-unroll-loops) test for-range loop + for(auto const& aii : mar.diagonal()) { + sum += aii; + } + BOOST_TEST( sum == mar[0][0] + mar[1][1] + mar[2][2]); } -} -void f1d5(double(&carr)[5]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) -void f1d5(double(&carr)[5]) { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - BOOST_REQUIRE(carr[1] == 1.0); -} + // BOOST_AUTO_TEST_CASE(function_passing) + { + multi::array arr({3, 3}); + multi::array_ref& arrR = arr; -void f2d54(double(&carr)[5][4]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) -void f2d54(double(&carr)[5][4]) { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - BOOST_REQUIRE(carr[0][1] == 1.0); -} + arrR[0][0] = 21; -BOOST_AUTO_TEST_CASE(array_ref_conversion_1D) { - multi::array arr(5); - BOOST_REQUIRE( arr.size() == 5 ); - std::iota(arr.elements().begin(), arr.elements().end(), 0.0); + arr.reextent({5, 5}); + BOOST_TEST( arr [0][0] == 21 ); + BOOST_TEST(&arrR[0][0] == &arr[0][0]); + } + + // BOOST_AUTO_TEST_CASE(function_passing_2) { - auto& carr = static_cast(arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - BOOST_REQUIRE( &carr[3] == &arr[3] ); + multi::Array arr({3, 3}); + [[maybe_unused]] multi::Array arrR = arr; + + arrR[0][0] = 51; + + [[maybe_unused]] multi::Array arrCR = arr; - f1d5(static_cast(arr)); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + BOOST_TEST(&arrCR[0][0] == &arrR[0][0]); + + [[maybe_unused]] multi::Array arrCR2 = arrCR; + + arr.reextent({5, 5}); + BOOST_TEST( arrR[0][0] == 51 ); + BOOST_TEST(&arrR[0][0] == &arr[0][0]); } + + // BOOST_AUTO_TEST_CASE(function_passing_3) { - double(&carr)[5](arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - BOOST_REQUIRE( &carr[3] == &arr[3] ); - // f1d5((double(&)[5])(arr)); // this will warn with -Wold-style-cast NOLINT(google-readability-casting,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - } -} + multi::array const arr({3, 3}, 10); -BOOST_AUTO_TEST_CASE(array_ref_conversion_2D) { - multi::array arr({5, 4}); - std::iota(arr.elements().begin(), arr.elements().end(), 0.0); + BOOST_TEST( trace_array_deduce (arr) == 30 ); + BOOST_TEST( trace_array_deduce(arr) == 30 ); - { - auto& carr = static_cast(arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - BOOST_REQUIRE( &carr[3][2] == &arr[3][2] ); + multi::array const arr_paren_copy{arr()}; + BOOST_TEST( arr_paren_copy.size() == 3 ); - f2d54(static_cast(arr)); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + // BOOST_TEST( trace_generic (arr) == 30 ); + // BOOST_TEST(( trace_generic >(arr) == 30 )); + // BOOST_TEST(( trace_generic&>(arr) == 3 )); // can't generate element_type + + // BOOST_TEST( trace_generic (arr()) == 30 ); + // BOOST_TEST(( trace_generic >(+arr()) == 30 )); // this will make a copy + // BOOST_TEST(( trace_generic&>(arr()) == 3 )); // can't generate element_type + + // BOOST_TEST(( trace_generic >(arr) == 30 )); + // BOOST_TEST(( trace_generic&>(arr) == 3 )); // can't generate element_type + // BOOST_TEST(( trace_generic >(arr) == 30 )); + // BOOST_TEST(( trace_generic&>(arr) == 3 )); // can't generate element_type + + // BOOST_TEST(( trace_generic >(arr({0, 3}, {0, 3})) == 3 )); + // BOOST_TEST(( trace_generic&>(arr()) == 3 )); // can't generate element_type + + BOOST_TEST(( trace_separate_ref (arr) == 30 )); + BOOST_TEST(( trace_separate_sub (arr) == 30 )); + + // BOOST_TEST(( trace_separate_ref2 (arr) == 3 )); // not allowed + // BOOST_TEST(( trace_separate_ref3 (arr) == 3 )); // not allowed + + // BOOST_TEST(( trace_separate_ref4 (arr) == 3 )); // not allowed + // BOOST_TEST(( trace_separate_sub4 (arr) == 3 )); // not allowed } + +#if __cplusplus > 202002L || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) + // BOOST_AUTO_TEST_CASE(function_passing_3_lambdas) { - double(&carr)[5][4](arr); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - BOOST_REQUIRE( &carr[3][2] == &arr[3][2] ); - // f2d54((double(&)[5][4])(arr)); // this will warn with -Wold-style-cast NOLINT(google-readability-casting,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - } -} + auto buffer = std::make_unique(9); + std::fill_n(buffer.get(), 9, 1); + + multi::array const arr({3, 3}, 1); + multi::array_ref const aref(buffer.get(), {3, 3}); + + auto const& asub = arr({0, 3}, {0, 3}); + + auto deduce_array = [](Arr const& a) { + return std::accumulate(a.diagonal().begin(), a.diagonal().end(), typename Arr::element_type{0}); + }; // NOLINT(readability/braces) bug in cpplint 1.6.1 + + BOOST_TEST( deduce_array(arr ) == 3 ); + BOOST_TEST( deduce_array(aref) == 3 ); + BOOST_TEST( deduce_array(asub) == 3 ); -BOOST_AUTO_TEST_CASE(as_span) { - #if defined(__cpp_lib_span) and (__cpp_lib_span >= 202002L) - auto printMe0 = [](std::span rng) { - std::cout << "rng.size(): " << rng.size() << '\n'; // (4) - std::for_each(rng.begin(), rng.end(), [](auto const& elem) {std::cout << elem << ' ';}); - std::cout << "\n\n"; - }; - #endif + auto deduce_element = [](multi::array const& a) { + return std::accumulate(a.diagonal().begin(), a.diagonal().end(), T{0}); + }; // NOLINT(readability/braces) bug in cpplint 1.6.1 - auto printMe1 = [](multi::array_ref rng) { - std::cout << "rng.size(): " << rng.size() << '\n'; // (4) - std::for_each(rng.begin(), rng.end(), [](auto const& elem) {std::cout << elem << ' ';}); - std::cout << "\n\n"; - }; + BOOST_TEST( deduce_element(arr) == 3 ); + // BOOST_TEST( deduce_element(aref) == 30 ); + // BOOST_TEST( deduce_element(asub) == 30 ); - auto printMe2 = [](multi::array_ptr ptr) { - std::cout << "ptr->size(): " << ptr->size() << '\n'; // (4) - std::for_each(ptr->begin(), ptr->end(), [](auto const& elem) {std::cout << elem << ' ';}); - std::cout << "\n\n"; - }; + auto deduce_element_ref = [](multi::array_ref const& a) { + return std::accumulate(a.diagonal().begin(), a.diagonal().end(), T{0}); + }; // NOLINT(readability/braces) cpplint 1.6.1 gets confused - #if defined(__cpp_lib_span) and (__cpp_lib_span >= 202002L) - { - int arr[] = {1, 2, 3, 4}; - printMe0(arr); + BOOST_TEST( deduce_element_ref(arr) == 3 ); + BOOST_TEST( deduce_element_ref(aref) == 3 ); + // BOOST_TEST( deduce_element_ref(asub) == 3 ); - std::vector vec = {1, 2, 3, 4, 5}; - printMe0(vec); + auto deduce_element_sub = [](multi::const_subarray const& a) { + return std::accumulate(a.diagonal().begin(), a.diagonal().end(), T{0}); + }; // NOLINT(readability/braces) cpplint 1.6.1 gets confused - std::array arr2 = {{1, 2, 3, 4, 5, 6}}; - printMe0(arr2); - } - #endif - { - int arr[] = {1, 2, 3, 4}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test c-arrays + BOOST_TEST( deduce_element_sub(arr) == 3 ); + BOOST_TEST( deduce_element_sub(aref) == 3 ); + BOOST_TEST( deduce_element_sub(asub) == 3 ); + } +#endif - printMe1(multi::array_ref{arr}); - printMe1( arr ); + // BOOST_AUTO_TEST_CASE(function_passing_4) { + // multi::array arr({3, 3}, 10); - std::vector vec = {1, 2, 3, 4, 5}; // NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( mut_trace_array_deduce (arr) == 30 ); + // BOOST_TEST( mut_trace_array_deduce(arr) == 30 ); - printMe1( *multi::array_ptr{vec.data(), 5} ); + // BOOST_TEST( mut_trace_generic (arr) == 30 ); + // BOOST_TEST(( mut_trace_generic >(arr) == 30 )); + // } - std::array arr2 = {{1, 2, 3, 4, 5, 6}}; + // BOOST_AUTO_TEST_CASE(array_fill_constructor) + { + multi::array arr(3, multi::array{10, 20, 30, 40}); - printMe1(arr2); - printMe1(*multi::array_ptr{ arr2.data(), {6} }); + BOOST_TEST( arr[0][1] == 20 ); + BOOST_TEST( arr[1][1] == 20 ); + } + // BOOST_AUTO_TEST_CASE(array_fill_constructor_1D) + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] - multi::static_array marr({10}, 99); - printMe1(*multi::array_ptr{marr.data_elements(), 10}); + multi::array arr(3, 10); - // printMe2(&marr); - } - { - int arr[] = {1, 2, 3, 4}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test c-arrays - printMe2(multi::array_ptr{&arr}); - printMe2( &arr ); + BOOST_TEST( arr[0] == 10 ); + BOOST_TEST( arr[1] == 10 ); - std::vector vec = {1, 2, 3, 4, 5}; // NOLINT(fuchsia-default-arguments-calls) - printMe2( {vec.data(), 5} ); + BOOST_TEST( get<0>(arr().sizes()) == 3 ); + BOOST_TEST( get<0>(sizes(arr())) == 3 ); - std::array arr2 = {{1, 2, 3, 4, 5, 6}}; - // printMe2(&arr2); // this crashes clang-tidy - printMe2({arr2.data(), {6} }); + // BOOST_TEST( get<1>(sizes(arr())) == 10 ); - // multi::static_array marr({10}, 99); - // printMe2(&marr); // TODO(correaa) make this work - } -} + // BOOST_TEST( get<0>(sizes(arr)) == 3 ); + // BOOST_TEST( get<1>(sizes(arr)) == 10 ); + } -BOOST_AUTO_TEST_CASE(diagonal) { - double (&&arr)[4][3] = { // NOLINT(hicpp-avoid-c-arrays, modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): test - { 0.0, 1.0, 2.0}, - { 5.0, 1.0, 7.0}, - {10.0, 11.0, 2.0}, - {99.0, 99.0, 99.9} - }; + // BOOST_AUTO_TEST_CASE(array_fill_constructor_2D) + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] - // NOLINTNEXTLINE(hicpp-avoid-c-arrays, modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays): special type - multi::array_ref mar = *multi::array_ptr(&arr); + multi::array arr({3, 4}, 10); - BOOST_REQUIRE( &mar({0, 3}, {0, 3}).diagonal()[0] == &arr[0][0] ); - BOOST_REQUIRE( &mar({0, 3}, {0, 3}).diagonal()[1] == &arr[1][1] ); - BOOST_REQUIRE( &mar({0, 3}, {0, 3}).diagonal()[2] == &arr[2][2] ); + BOOST_TEST( get<0>(arr().sizes()) == 3 ); + BOOST_TEST( get<0>(sizes(arr())) == 3 ); - auto sum = 0.0; - for(auto const& aii : mar.diagonal() ) {sum += aii;} // NOLINT(altera-unroll-loops) test for-range loop - BOOST_REQUIRE( sum == mar[0][0] + mar[1][1] + mar[2][2]); -} + BOOST_TEST( get<1>(arr().sizes()) == 4 ); + BOOST_TEST( get<1>(sizes(arr())) == 4 ); + + // BOOST_TEST( get<1>(sizes(arr())) == 10 ); + + // BOOST_TEST( get<0>(sizes(arr)) == 3 ); + // BOOST_TEST( get<1>(sizes(arr)) == 10 ); + } + + return boost::report_errors(); +} // NOLINT(readability/fn_size) diff --git a/test/array_vector_substitutability.cpp b/test/array_vector_substitutability.cpp deleted file mode 100644 index 6b30d7a2c..000000000 --- a/test/array_vector_substitutability.cpp +++ /dev/null @@ -1,234 +0,0 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi legacy adaptor example" // NOLINT(cppcoreguidelines-macro-usage) title -#include - -#include - -#include - -namespace multi = boost::multi; - -template // e.g. std::vector or multi::array -void resize_copy_1(std::vector const& source, DynamicArray& darr) { - darr = DynamicArray(source); -} - -template // e.g. std::vector or multi::array -void resize_copy_2(std::vector const& source, DynamicArray& darr) { - darr = DynamicArray(source.begin(), source.end()); // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) -} - -template // e.g. std::vector or multi::array -void resize_copy_3(std::vector const& source, DynamicArray& darr) { - darr = std::decay_t(source.begin(), source.end()); // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) -} - -template // e.g. std::vector or multi::array -void resize_copy_4(It first, It last, DynamicArray& darr) { - darr = DynamicArray(first, last); // or std::decay_t(source.begin(), source.end()) // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) -} - -template // e.g. std::vector or multi::array -void resize_copy_5(It first, It last, DynamicArray& darr) { - darr.assign(first, last); // or std::decay_t(source.begin(), source.end()) -} - -// void resize_copy_6 ----> see below test_resize_copy_6 - -BOOST_AUTO_TEST_CASE(test_resize_copy_1) { - std::vector const source = {0.0, 1.0, 2.0, 3.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - - std::vector dest_v = {99.0, 99.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - multi::array dest_a = {88.0, 88.0}; - - BOOST_REQUIRE( dest_v.size() == 2 ); - BOOST_REQUIRE( dest_a.size() == 2 ); - - resize_copy_1(source, dest_v); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3.0 ); - - resize_copy_1(source, dest_a); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3.0 ); -} - -BOOST_AUTO_TEST_CASE(test_resize_copy_2) { - std::vector const source = {0.0, 1.0, 2.0, 3.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - - std::vector dest_v = {99.0, 99.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - multi::array dest_a = {88.0, 88.0}; - - BOOST_REQUIRE( dest_v.size() == 2 ); - BOOST_REQUIRE( dest_a.size() == 2 ); - - resize_copy_2(source, dest_v); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3. ); - - resize_copy_2(source, dest_a); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3. ); -} - -BOOST_AUTO_TEST_CASE(test_resize_copy_3) { - std::vector const source = {0.0, 1.0, 2.0, 3.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - - std::vector dest_v = {99.0, 99.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - multi::array dest_a = {88.0, 88.0}; - - BOOST_REQUIRE( dest_v.size() == 2 ); - BOOST_REQUIRE( dest_a.size() == 2 ); - - resize_copy_3(source, dest_v); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3. ); - - resize_copy_3(source, dest_a); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3. ); -} - -BOOST_AUTO_TEST_CASE(test_resize_copy_4) { - std::vector const source = {0.0, 1.0, 2.0, 3.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - - std::vector dest_v = {99.0, 99.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - multi::array dest_a = {88.0, 88.0}; - - BOOST_REQUIRE( dest_v.size() == 2 ); - BOOST_REQUIRE( dest_a.size() == 2 ); - - resize_copy_4(source.begin(), source.end(), dest_v); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3.0 ); - - resize_copy_4(source.begin(), source.end(), dest_a); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3.0 ); -} - -BOOST_AUTO_TEST_CASE(test_resize_copy_5) { - std::vector const source = {0.0, 1.0, 2.0, 3.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - - std::vector dest_v = {99.0, 99.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - multi::array dest_a = {88.0, 88.0}; - - BOOST_REQUIRE( dest_v.size() == 2 ); - BOOST_REQUIRE( dest_a.size() == 2 ); - - resize_copy_5(source.begin(), source.end(), dest_v); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3.0 ); - - resize_copy_5(source.begin(), source.end(), dest_a); - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3.0 ); -} - -BOOST_AUTO_TEST_CASE(test_resize_copy_6) { - std::vector const source = {0.0, 1.0, 2.0, 3.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - - std::vector dest_v = {99.0, 99.0}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) - multi::array dest_a = {88.0, 88.0}; - - BOOST_REQUIRE( dest_v.size() == 2 ); - BOOST_REQUIRE( dest_a.size() == 2 ); - - { // look same code as below - dest_v = decltype(dest_v)(source); - } - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3.0 ); - - { // look same code as above - dest_a = decltype(dest_a)(source); - } - - BOOST_REQUIRE( dest_v.size() == 4 ); - BOOST_REQUIRE( dest_v[3] == 3.0 ); -} - -BOOST_AUTO_TEST_CASE(assign_equality) { - { - multi::array const AA = {1.0, 2.0, 3.0}; - std::vector const aa = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) - - BOOST_REQUIRE( std::equal(AA.begin(), AA.end(), aa.begin() ) ); - } - { - multi::array const AA = {1.0, 2.0, 3.0}; - std::vector const aa(AA.begin(), AA.end()); // NOLINT(fuchsia-default-arguments-calls) - - BOOST_REQUIRE( std::equal(AA.begin(), AA.end(), aa.begin() ) ); - } - { - multi::array const AA = {1.0, 2.0, 3.0}; - auto const aa(AA().operator std::vector()); - - BOOST_REQUIRE( std::equal(AA.begin(), AA.end(), aa.begin() ) ); - } - // { - // multi::array const AA = {1.0, 2.0, 3.0}; - // std::vector const aa(AA); - - // BOOST_REQUIRE( std::equal(AA.begin(), AA.end(), aa.begin() ) ); - // } - { - std::vector const aa = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) - multi::array const AA(aa.begin(), aa.end()); - - BOOST_REQUIRE( std::equal(AA.begin(), AA.end(), aa.begin() ) ); - } - { - std::vector const aa = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) - multi::array const AA(aa); - - BOOST_REQUIRE( std::equal(AA.begin(), AA.end(), aa.begin() ) ); - } -} - -BOOST_AUTO_TEST_CASE(construct_from_vector_2D) { - { - multi::array const AA = {{1, 2}, {3, 4}}; - BOOST_REQUIRE( AA.num_elements() == 4 ); - - std::vector> const aa(AA.begin(), AA.end()); // NOLINT(fuchsia-default-arguments-calls) - } - { - multi::array const AA = {{1, 2}, {3, 4}}; - BOOST_REQUIRE( AA.num_elements() == 4 ); - - auto const aa(AA().operator std::vector>()); - } - // { - // multi::array AA = {{1, 2}, {3, 4}}; - // BOOST_REQUIRE( AA.num_elements() == 4 ); - - // std::vector> const aa(AA); // TODO(correaa) circle - // } - // { - // multi::array const AA = {{1, 2}, {3, 4}}; - // BOOST_REQUIRE( AA.num_elements() == 4 ); - - // std::vector> const aa(AA); // TODO(correaa) circle - // } - // { - // multi::array AA = {{1, 2}, {3, 4}}; - // BOOST_REQUIRE( AA.num_elements() == 4 ); - - // multi::array, 1> const aa(AA); // TODO(correaa) - // } -} diff --git a/test/assignments.cpp b/test/assignments.cpp index 4dcd1c367..e82766668 100644 --- a/test/assignments.cpp +++ b/test/assignments.cpp @@ -1,154 +1,249 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi assignments" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#include // for array, apply, operator==, layout_t -#include +#include -#include +#include // for fill +// #include // for complex +#include // for size_t +#include // for size +#include // for std::allocator // IWYU pragma: keep +// IWYU pragma: no_include // for decay_t +#include // for move +#include // for vector, allocator namespace multi = boost::multi; namespace { -inline constexpr auto make_ref(double* ptr) -> multi::array_ref { - return multi::array_ref(ptr, {5, 7}); -} -} // namespace - -BOOST_AUTO_TEST_CASE(equality_1D) { - multi::array arr = {1.0, 2.0, 3.0}; - multi::array arr2 = {1.0, 2.0, 3.0}; - BOOST_REQUIRE( arr == arr2 ); - BOOST_REQUIRE( not (arr != arr2) ); - BOOST_REQUIRE( arr() == arr2() ); - BOOST_REQUIRE( not (arr() != arr2()) ); +constexpr auto make_ref(int* ptr) { + return multi::array_ref(ptr, {5, 7}); } -BOOST_AUTO_TEST_CASE(equality_2D) { - multi::array arr = { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - }; - multi::array arr2 = { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - }; - BOOST_REQUIRE( arr == arr2 ); - BOOST_REQUIRE( not (arr != arr2) ); - - BOOST_REQUIRE( arr() == arr2() ); - BOOST_REQUIRE( not (arr() != arr2()) ); - - BOOST_REQUIRE( arr[0] == arr2[0] ); - BOOST_REQUIRE( not (arr[0] != arr2[0]) ); +template +auto eye(multi::extensions_t<2> exts, Allocator const& alloc) { + multi::array ret(exts, 0, alloc); + std::fill(ret.diagonal().begin(), ret.diagonal().end(), 1); + return ret; } -BOOST_AUTO_TEST_CASE(multi_copy_move) { - multi::array arr({3, 3}, 0.0); - multi::array arr2 = arr; - BOOST_REQUIRE( arr == arr2 ); +template +auto eye(multi::extensions_t<2> exts) { return eye(exts, std::allocator{}); } - auto* arr_data = arr.data_elements(); +} // end unnamed namespace - multi::array arr3 = std::move(arr); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(equality_1D) + { + multi::array arr = {10, 20, 30}; + multi::array arr2 = {10, 20, 30}; - BOOST_REQUIRE( arr3.data_elements() == arr_data ); + BOOST_TEST( arr == arr2 ); + BOOST_TEST( !(arr != arr2) ); - multi::array const arr4(std::move(arr2)); - BOOST_REQUIRE( size(arr4) == 3 ); -} + BOOST_TEST( arr() == arr2() ); + BOOST_TEST( !(arr() != arr2()) ); + } -#if 1 -BOOST_AUTO_TEST_CASE(range_assignment) { + // BOOST_AUTO_TEST_CASE(equality_2D) { - auto ext = multi::make_extension_t(10L); - multi::array vec(ext.begin(), ext.end()); - BOOST_REQUIRE( ext.size() == vec.size() ); - BOOST_REQUIRE( vec[1] = 10 ); + multi::array arr = { + {10, 20, 30}, + {40, 50, 60}, + }; + multi::array arr2 = { + {10, 20, 30}, + {40, 50, 60}, + }; + + BOOST_TEST( arr == arr2 ); + BOOST_TEST( !(arr != arr2) ); + + BOOST_TEST( arr() == arr2() ); + BOOST_TEST( !(arr() != arr2()) ); + + BOOST_TEST( arr[0] == arr2[0] ); + BOOST_TEST( !(arr[0] != arr2[0]) ); } + + // BOOST_AUTO_TEST_CASE(multi_copy_move) { - multi::array vec(multi::extensions_t<1>{multi::iextension{10}}); - auto ext = extension(vec); - vec.assign(ext.begin(), ext.end()); - BOOST_REQUIRE( vec[1] == 1 ); + multi::array arr({3, 3}, 0.0); + multi::array arr2 = arr; + BOOST_TEST( arr == arr2 ); // cppcheck-suppress knownConditionTrueFalse ; + + auto* arr_data = arr.data_elements(); + + multi::array arr3 = std::move(arr); + + BOOST_TEST( arr3.data_elements() == arr_data ); + + multi::array const arr4(std::move(arr2)); + BOOST_TEST( size(arr4) == 3 ); } -} -BOOST_AUTO_TEST_CASE(rearranged_assignment) { - multi::array tmp({14, 14, 7, 4}); - multi::array src({ 2, 14, 14, 7, 2}); - src[0][1][2][3][1] = 99.0; + // BOOST_AUTO_TEST_CASE(range_assignment) + { + { + auto const ext = multi::make_extension_t(10L); - BOOST_REQUIRE( extensions(tmp.unrotated().partitioned(2).transposed().rotated()) == extensions(src) ); -} + multi::array vec(ext.begin(), ext.end()); -BOOST_AUTO_TEST_CASE(rearranged_assignment_resize) { - multi::array const arrA({4, 5}); - multi::array arrB({2, 3}); + BOOST_TEST( ext.size() == vec.size() ); + BOOST_TEST( vec[1] == 1L ); + } + { + multi::array vec(multi::extensions_t<1>{multi::iextension{10}}); - arrB = arrA; - BOOST_REQUIRE( arrB.size() == 4 ); -} + auto const ext = extension(vec); + + vec.assign(ext.begin(), ext.end()); + BOOST_TEST( vec[1] == 1 ); + } + } + + // BOOST_AUTO_TEST_CASE(rearranged_assignment) + { + auto const ext5 = multi::extensions_t<5>{2, 14, 14, 7, 2}; -BOOST_AUTO_TEST_CASE(rvalue_assignments) { - using complex = std::complex; + [[maybe_unused]] auto const ext52 = ext5; - std::vector const vec1(200, 99.0); // NOLINT(fuchsia-default-arguments-calls) - std::vector vec2(200); // NOLINT(fuchsia-default-arguments-calls) + [[maybe_unused]] multi::array const src_test(ext5); - auto linear1 = [&] { return multi::array_cptr(vec1.data(), 200); }; - auto linear2 = [&] { return multi::array_ptr(vec2.data(), 200); }; + multi::array src({2, 14, 14, 7, 2}); - *linear2() = *linear1(); -} + src[0][1][2][3][1] = 99; -BOOST_AUTO_TEST_CASE(assignments) { + BOOST_TEST( src[0][1][2][3][1] == 99 ); // cppcheck-suppress knownConditionTrueFalse ; for test + + // BOOST_TEST( tmp.unrotated().partitioned(2).transposed().rotated().extensions() == src.extensions() ); + // BOOST_TEST( extensions(tmp.unrotated().partitioned(2).transposed().rotated()) == extensions(src) ); + } + + // BOOST_AUTO_TEST_CASE(rearranged_assignment_resize) { - std::vector vec(static_cast(5 * 7), 99.0); // NOLINT(fuchsia-default-arguments-calls) - constexpr double val = 33.0; - multi::array const arr({5, 7}, val); - multi::array_ref(vec.data(), {5, 7}) = arr; - BOOST_REQUIRE( vec[9] == val ); - BOOST_REQUIRE( not vec.empty() ); - BOOST_REQUIRE( not is_empty(arr) ); + multi::array const arrA({4, 5}); + multi::array arrB({2, 3}); + + arrB = arrA; + BOOST_TEST( arrB.size() == 4 ); } + +#ifndef _MSC_VER // TODO(correaa) fix + // seems to produce a deterministic divide by zero + // Assertion failed: stride_ != 0, file D:\a\boost-multi\boost-root\boost/multi/detail/layout.hpp, line 767 + // D:\a\boost-multi\boost-root\boost\multi\detail\layout.hpp(770) : error C2220: the following warning is treated as an error + // D:\a\boost-multi\boost-root\boost\multi\detail\layout.hpp(770) : warning C4723: potential divide by 0 + // D:\a\boost-multi\boost-root\boost\multi\detail\layout.hpp(770) : warning C4723: potential divide by 0 + + // BOOST_AUTO_TEST_CASE(rvalue_assignments) { + // using complex = std::complex; + + // std::vector const vec1(200, 99.0); // NOLINT(fuchsia-default-arguments-calls) + // std::vector vec2(200); // NOLINT(fuchsia-default-arguments-calls) + + // auto linear1 = [&] { + // return multi::array_cptr(vec1.data(), 200); + // }; + // auto linear2 = [&] { + // return multi::array_ptr(vec2.data(), 200); + // }; + + // *linear2() = *linear1(); + // } +#endif + + // BOOST_AUTO_TEST_CASE(assignments) { - std::vector vec(5 * 7L, 99.0); // NOLINT(fuchsia-default-arguments-calls) - std::vector wec(5 * 7L, 33.0); // NOLINT(fuchsia-default-arguments-calls) + { + std::vector vec(static_cast(5 * 7), 99); // NOLINT(fuchsia-default-arguments-calls) + + constexpr int val = 33; + + multi::array const arr({5, 7}, val); + multi::array_ref(vec.data(), {5, 7}) = arr(); // arr() is a subarray + + BOOST_TEST( vec[9] == val ); // cppcheck-suppress containerOutOfBounds ; + BOOST_TEST( !vec.empty() ); + BOOST_TEST( !is_empty(arr) ); + } + { + std::vector vec(5 * 7L, 99); // NOLINT(fuchsia-default-arguments-calls) + std::vector wec(5 * 7L, 33); // NOLINT(fuchsia-default-arguments-calls) + + multi::array_ptr const Bp(wec.data(), {5, 7}); + make_ref(vec.data()) = *Bp; - multi::array_ptr const Bp(wec.data(), {5, 7}); - make_ref(vec.data()) = *Bp; - make_ref(vec.data()) = Bp->sliced(0, 5); + auto&& mref = make_ref(vec.data()); + // mref = (*Bp).sliced(0, 5); + mref = Bp->sliced(0, 5); - BOOST_REQUIRE( vec[9] == 33.0 ); + // make_ref(vec.data()) = (*Bp).sliced(0, 5); + make_ref(vec.data()) = Bp->sliced(0, 5); + + BOOST_TEST( vec[9] == 33 ); + } + { + std::vector vec(5 * 7L, 99); // NOLINT(fuchsia-default-arguments-calls) + std::vector wec(5 * 7L, 33); // NOLINT(fuchsia-default-arguments-calls) + + make_ref(vec.data()) = make_ref(wec.data()); + + BOOST_TEST( vec[9] == 33 ); + } } + + // BOOST_AUTO_TEST_CASE(assigment_temporary) + { + multi::array Id = eye(multi::extensions_t<2>({3, 3})); + BOOST_TEST( Id == eye({3, 3}) ); + BOOST_TEST( Id[1][1] == 1 ); + BOOST_TEST( Id[1][0] == 0 ); + } + + // BOOST_AUTO_TEST_CASE(self_assignment) { - std::vector vec(5 * 7L, 99.0); // NOLINT(fuchsia-default-arguments-calls) - std::vector wec(5 * 7L, 33.0); // NOLINT(fuchsia-default-arguments-calls) + // NOLINTBEGIN(fuchsia-default-arguments-calls) + multi::static_array, 2> arr = { + {std::vector(10, 1), std::vector(20, 2)}, + {std::vector(30, 3), std::vector(40, 4)}, + }; + // NOLINTEND(fuchsia-default-arguments-calls) - make_ref(vec.data()) = make_ref(wec.data()); + BOOST_TEST( arr[1][1] == std::vector(40, 4) ); // NOLINT(fuchsia-default-arguments-calls) + auto* loc = &arr[1][1][5]; - BOOST_REQUIRE( vec[9] == 33.0 ); + auto const* arr_ptr = std::addressof(arr); + + arr = *arr_ptr; + + BOOST_TEST( arr[1][1] == std::vector(40, 4) ); // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( &arr[1][1][5] == loc ); } -} -template -auto eye(multi::extensions_t<2> exts, Allocator alloc) { - multi::array ret(exts, 0.0, alloc); - ret.diagonal().fill(1.0); - return ret; -} + // BOOST_AUTO_TEST_CASE(static_array_move) + { + // NOLINTBEGIN(fuchsia-default-arguments-calls) + multi::static_array, 2> arr = { + {std::vector(10, 1), std::vector(20, 2)}, + {std::vector(30, 3), std::vector(40, 4)}, + }; + // NOLINTEND(fuchsia-default-arguments-calls) -template -auto eye(multi::extensions_t<2> exts) { return eye(exts, std::allocator{}); } + BOOST_TEST( arr[1][1] == std::vector(40, 4) ); // NOLINT(fuchsia-default-arguments-calls) -BOOST_AUTO_TEST_CASE(assigment_temporary) { - multi::array Id = eye(multi::extensions_t<2>({3, 3})); - BOOST_REQUIRE( Id == eye({3, 3}) ); - BOOST_REQUIRE( Id[1][1] == 1.0 ); - BOOST_REQUIRE( Id[1][0] == 0.0 ); -} + multi::static_array, 2> arr2(std::move(arr)); + BOOST_TEST( arr2[1][1] == std::vector(40, 4) ); // NOLINT(fuchsia-default-arguments-calls) -#endif + // cppcheck-suppress accessMoved ; + BOOST_TEST( arr[0][0].empty() ); // NOLINT(clang-analyzer-cplusplus.Move,fuchsia-default-arguments-calls,bugprone-use-after-move,hicpp-invalid-access-moved) + BOOST_TEST( arr[1][1].empty() ); // NOLINT(clang-analyzer-cplusplus.Move,fuchsia-default-arguments-calls,bugprone-use-after-move,hicpp-invalid-access-moved) + } + + return boost::report_errors(); +} diff --git a/test/boost_array_concept.cpp b/test/boost_array_concept.cpp new file mode 100644 index 000000000..a9bc25b80 --- /dev/null +++ b/test/boost_array_concept.cpp @@ -0,0 +1,297 @@ +// Copyright 2024-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// NOLINTBEGIN +// #include // for BOOST_CONCEPT_ASSERT +// #include // for Assignable, CopyCons... +// NOLINTEND + +#include + +// Test explicitly calls deprecated function +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // assignment operator was implicitly defined as deleted +#endif + +#include // for operator!=, implicit... + +// IWYU pragma: no_include // for const_sub_array, sub_array +// IWYU pragma: no_include // for fill_n + +// #include // for multi_array +// #include // for multi_array // IWYU pragma: keep +// #include // for ConstMultiArrayConcept + +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#include // for ptrdiff_t +#include // for vector + +auto main() -> int { // NOLINT(bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(concepts_boost_array) + { + // using BMA [[maybe_unused]] = boost::multi_array; // maybe_unused for bug in nvcc 11.8 + + // BOOST_CONCEPT_ASSERT((boost::multi_array_concepts::ConstMultiArrayConcept)); + // BOOST_CONCEPT_ASSERT((boost::multi_array_concepts::MutableMultiArrayConcept)); + } + + // BOOST_AUTO_TEST_CASE(concepts_boost_array_1D) + { + // using BMA = boost::multi_array; + + // BOOST_CONCEPT_ASSERT((boost::multi_array_concepts::ConstMultiArrayConcept)); + // BOOST_CONCEPT_ASSERT((boost::multi_array_concepts::MutableMultiArrayConcept)); + } + + namespace multi = boost::multi; + + // BOOST_AUTO_TEST_CASE(backwards) + { + multi::array const MA({2, 2}); + (void)MA; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + // BOOST_REQUIRE(A.index_bases()[0] == 0); // dangles? + // BOOST_REQUIRE(A.index_bases()[1] == 0); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + { +#ifdef __NVCC__ +#pragma nv_diagnostic push +#pragma nv_diag_suppress = deprecated_entity_with_custom_message // nvcc #? +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + // auto ib = MA.index_bases(); (void)ib; + // BOOST_REQUIRE(ib[0] == 0); // dangles? + // BOOST_REQUIRE(ib[1] == 0); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __NVCC__ +#pragma nv_diagnostic pop +#endif + } + // { + // #pragma GCC diagnostic push + // #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // std::array ib(MA.index_bases()); (void)ib; + // BOOST_REQUIRE(ib[0] == 0); + // BOOST_REQUIRE(ib[1] == 0); + // #pragma GCC diagnostic pop + // } + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + // BOOST_REQUIRE(static_cast(MA.index_bases())[0] == 0); // dangles + // BOOST_REQUIRE(static_cast(MA.index_bases())[1] == 0); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + // BOOST_REQUIRE(MA.index_bases()[0] == 0); // dangles + // BOOST_REQUIRE(MA.index_bases()[1] == 0); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + // std::ptrdiff_t const* ib = MA.index_bases(); + // BOOST_REQUIRE(ib); + // BOOST_REQUIRE(ib[0] == 0); // dangles + // BOOST_REQUIRE(ib[1] == 0); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + std::vector const ib(2); + (void)ib; // NOLINT(fuchsia-default-arguments-calls) + // std::copy_n(static_cast(MA.index_bases()), 2, ib.begin()); + // BOOST_REQUIRE(ib[0] == 0); // dangles + // BOOST_REQUIRE(ib[1] == 0); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + // { + // #pragma GCC diagnostic push + // #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // std::vector ib(2); // NOLINT(fuchsia-default-arguments-calls) + // std::copy_n(MA.index_bases().to_array().data(), 2, ib.begin()); + // BOOST_REQUIRE(ib[0] == 0); + // BOOST_REQUIRE(ib[1] == 0); + // #pragma GCC diagnostic pop + // } + } + + // BOOST_AUTO_TEST_CASE(concepts_array) + { + // using MA = multi::array; + + // BOOST_CONCEPT_ASSERT((boost::multi_array_concepts::ConstMultiArrayConcept)); + // BOOST_CONCEPT_ASSERT((boost::multi_array_concepts::MutableMultiArrayConcept)); + + // BOOST_CONCEPT_ASSERT((boost::Assignable)); + // BOOST_CONCEPT_ASSERT((boost::SGIAssignable)); + // BOOST_CONCEPT_ASSERT((boost::DefaultConstructible)); + // BOOST_CONCEPT_ASSERT((boost::CopyConstructible)); + // BOOST_CONCEPT_ASSERT((boost::EqualityComparable)); + // BOOST_CONCEPT_ASSERT((boost::LessThanComparable)); + // BOOST_CONCEPT_ASSERT((boost::Comparable)); + + // Function Object Concept Checking Classes + // BOOST_CONCEPT_ASSERT((boost::Generator>)); + // BOOST_CONCEPT_ASSERT((boost::UnaryFunction)); + // BOOST_CONCEPT_ASSERT((boost::BinaryFunction)); + + // vvv needs result_type TODO(correaa) add to array?, should result_type be array? or subarray? + // BOOST_CONCEPT_ASSERT((boost::AdaptableGenerator>)); + // BOOST_CONCEPT_ASSERT((boost::AdaptableUnaryFunction)); + // BOOST_CONCEPT_ASSERT((boost::AdaptableBinaryFunction)); + + // Container Concept Checking Classes + // BOOST_CONCEPT_ASSERT((boost::Container)); + // BOOST_CONCEPT_ASSERT((boost::Mutable_Container)); + // BOOST_CONCEPT_ASSERT((boost::ForwardContainer)); + // BOOST_CONCEPT_ASSERT((boost::Mutable_ForwardContainer)); + // BOOST_CONCEPT_ASSERT((boost::ReversibleContainer)); // TODO(correaa) make it reversible, `const_reverse_iterator _i = cc.rbegin();` + // BOOST_CONCEPT_ASSERT((boost::Mutable_ReversibleContainer)); + // BOOST_CONCEPT_ASSERT((boost::RandomAccessContainer)); + // BOOST_CONCEPT_ASSERT((boost::Mutable_RandomAccessContainer)); + // BOOST_CONCEPT_ASSERT((boost::Sequence)); // TODO(correaa) needs insert and erase, which will not be provided + // BOOST_CONCEPT_ASSERT((boost::Collection)); + } + + // BOOST_AUTO_TEST_CASE(concepts_array_1D) + { + // using MA = multi::array; + + // BOOST_CONCEPT_ASSERT((boost::multi_array_concepts::ConstMultiArrayConcept)); + // // BOOST_CONCEPT_ASSERT((boost::multi_array_concepts::MutableMultiArrayConcept)); + + // BOOST_CONCEPT_ASSERT((boost::Assignable)); + // BOOST_CONCEPT_ASSERT((boost::SGIAssignable)); + // BOOST_CONCEPT_ASSERT((boost::DefaultConstructible)); + // BOOST_CONCEPT_ASSERT((boost::CopyConstructible)); + // BOOST_CONCEPT_ASSERT((boost::EqualityComparable)); + // BOOST_CONCEPT_ASSERT((boost::LessThanComparable)); + // // BOOST_CONCEPT_ASSERT((boost::Comparable)); + + // // Function Object Concept Checking Classes + // BOOST_CONCEPT_ASSERT((boost::Generator>)); + // BOOST_CONCEPT_ASSERT((boost::UnaryFunction)); + // // BOOST_CONCEPT_ASSERT((boost::BinaryFunction)); + + // // vvv--- needs result_type TODO(correaa) add to array?, should result_type be array? or subarray? + // // BOOST_CONCEPT_ASSERT((boost::AdaptableGenerator>)); + // // BOOST_CONCEPT_ASSERT((boost::AdaptableUnaryFunction)); + // // BOOST_CONCEPT_ASSERT((boost::AdaptableBinaryFunction)); + + // // Container Concept Checking Classes + // BOOST_CONCEPT_ASSERT((boost::Container)); + // BOOST_CONCEPT_ASSERT((boost::Mutable_Container)); + // BOOST_CONCEPT_ASSERT((boost::ForwardContainer)); + // BOOST_CONCEPT_ASSERT((boost::Mutable_ForwardContainer)); + // // BOOST_CONCEPT_ASSERT((boost::ReversibleContainer)); // TODO(correaa) make it reversible, `const_reverse_iterator _i = cc.rbegin();` + // // BOOST_CONCEPT_ASSERT((boost::Mutable_ReversibleContainer)); + // // BOOST_CONCEPT_ASSERT((boost::RandomAccessContainer)); + // // BOOST_CONCEPT_ASSERT((boost::Mutable_RandomAccessContainer)); + // // BOOST_CONCEPT_ASSERT((boost::Sequence)); // TODO(correaa) needs insert and erase, which will not be provided + // BOOST_CONCEPT_ASSERT((boost::Collection)); + } + + // BOOST_AUTO_TEST_CASE(concepts_iterator) + { + // using MAIt = multi::array::iterator; + + // BOOST_CONCEPT_ASSERT((boost::Assignable)); + // BOOST_CONCEPT_ASSERT((boost::SGIAssignable)); + // BOOST_CONCEPT_ASSERT((boost::DefaultConstructible)); + // BOOST_CONCEPT_ASSERT((boost::CopyConstructible)); + // BOOST_CONCEPT_ASSERT((boost::EqualityComparable)); + // BOOST_CONCEPT_ASSERT((boost::LessThanComparable)); + + // BOOST_CONCEPT_ASSERT((boost::InputIterator)); + // BOOST_CONCEPT_ASSERT((boost::OutputIterator)); + // BOOST_CONCEPT_ASSERT((boost::OutputIterator)); + + // // Iterator Concept Checking Classes + // BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); + // BOOST_CONCEPT_ASSERT((boost::Mutable_ForwardIterator)); + // BOOST_CONCEPT_ASSERT((boost::BidirectionalIterator)); + // BOOST_CONCEPT_ASSERT((boost::Mutable_BidirectionalIterator)); + // BOOST_CONCEPT_ASSERT((boost::RandomAccessIterator)); + // BOOST_CONCEPT_ASSERT((boost::Mutable_RandomAccessIterator)); + } + + // BOOST_AUTO_TEST_CASE(concepts_const_iterator) + { + // using MAIt [[maybe_unused]] = multi::array::const_iterator; // maybe_unused for bug in nvcc 11.8 + + // BOOST_CONCEPT_ASSERT((boost::Assignable)); + // BOOST_CONCEPT_ASSERT((boost::SGIAssignable)); + // BOOST_CONCEPT_ASSERT((boost::DefaultConstructible)); + // BOOST_CONCEPT_ASSERT((boost::CopyConstructible)); + // BOOST_CONCEPT_ASSERT((boost::EqualityComparable)); + // BOOST_CONCEPT_ASSERT((boost::LessThanComparable)); + + // BOOST_CONCEPT_ASSERT((boost::InputIterator)); + // // BOOST_CONCEPT_ASSERT((boost::OutputIterator)); + // // BOOST_CONCEPT_ASSERT((boost::OutputIterator)); + + // BOOST_CONCEPT_ASSERT((boost::ForwardIterator)); + // // BOOST_CONCEPT_ASSERT((boost::Mutable_ForwardIterator)); + // BOOST_CONCEPT_ASSERT((boost::BidirectionalIterator)); + // // BOOST_CONCEPT_ASSERT((boost::Mutable_BidirectionalIterator)); + // BOOST_CONCEPT_ASSERT((boost::RandomAccessIterator)); + // // BOOST_CONCEPT_ASSERT((boost::Mutable_RandomAccessIterator)); + } + + return boost::report_errors(); +} diff --git a/test/broadcast.cpp b/test/broadcast.cpp new file mode 100644 index 000000000..70d403c0d --- /dev/null +++ b/test/broadcast.cpp @@ -0,0 +1,51 @@ +// Copyright 2023-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include + +#include // for std::ranges::fold_left +#include // IWYU pragma: keep for std::abs +// IWYU pragma: no_include // for abs +// IWYU pragma: no_include // for abs + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(broadcast_as_fill) + { + multi::array bb = {10, 11}; + + multi::array BB({10, 2}); + + // std::fill (BB.begin(), BB.end(), bb); // canonical way + std::fill_n(BB.begin(), BB.size(), bb); // canonical way + + // std::copy_n(bb.broadcasted().begin(), BB.size(), BB.begin()); // doesn't work because faulty implementation of copy_n + // thrust::copy_n(bb.broadcasted().begin(), BB.size(), BB.begin()); // equivalent, using broadcast + + // std::copy_n(bb.broadcasted().begin(), bb.broadcasted().size(), BB.begin()); // incorrect, undefined behavior, no useful size() + // std::copy (bb.broadcasted().begin(), bb.broadcasted().end(), BB.begin()); // incorrect, undefined behavior, non-terminating loop (end is not reacheable) + // BB = bb.broadcasted(); + + BOOST_TEST( BB[0] == bb ); + BOOST_TEST( BB[1] == bb ); + + // NOLINTNEXTLINE(modernize-use-ranges) + BOOST_TEST( std::all_of(BB.begin(), BB.end(), [&bb](auto const& row) { return row == bb; }) ); + + multi::array const one{1.0}; + + BOOST_TEST( one == 1.0 ); + + auto const& ones = one.broadcasted(); + BOOST_TEST( std::abs( *ones.begin() - 1.0 ) < 1.0e-8 ); + + // BOOST_TEST( ones.layout().nelems() == (std::numeric_limits::max)() ); + } + + return boost::report_errors(); +} diff --git a/test/coelements.cpp b/test/coelements.cpp new file mode 100644 index 000000000..432da35b7 --- /dev/null +++ b/test/coelements.cpp @@ -0,0 +1,82 @@ +// Copyright 2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include + +#if defined(__cpp_lib_generator) && (__cpp_lib_generator >= 202207L) +#include +#endif + +namespace multi = boost::multi; + +// https://godbolt.org/z/7MqxhWvz3 + +#if defined(__cpp_lib_generator) && (__cpp_lib_generator >= 202207L) +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wnull-dereference" +#endif + +#include + +template +std::generator +co_extensions_elements(Arr2D const& arr2d) { + auto const [is, js] = arr2d.extensions(); + for(auto const i : is) { + for(auto const j : js) { + co_yield typename Arr2D::indexes{i, j}; + } + } +} + +template +std::generator +co_celements(Arr2D const& arr2d) { + auto const [is, js] = arr2d.extensions(); + for(auto const i : is) { + for(auto const j : js) { + co_yield arr2d[i][j]; + } + } +} + +#endif + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + multi::array const arr = { + {0, 1, 2}, + {3, 4, 5} + }; + + BOOST_TEST( arr.extension()[1] == 1 ); + { + auto const [i, j] = arr.extensions()[1][2]; + BOOST_TEST( i == 1 ); + BOOST_TEST( j == 2 ); + } + +#if defined(__cpp_lib_generator) && (__cpp_lib_generator >= 202207L) + for(auto const& [i, j] : co_extensions_elements(arr)) { + std::cout << i << ' ' << j << '\n'; + } + { + auto const [i, j] = *co_extensions_elements(arr).begin(); + BOOST_TEST( i == 0 ); + BOOST_TEST( j == 0 ); + } + { + auto const [i, j] = *(++co_extensions_elements(arr).begin()); + BOOST_TEST( i == 0 ); + BOOST_TEST( j == 1 ); + } + { + BOOST_TEST( *(++co_celements(arr).begin()) == 1 ); + } +#endif + + return boost::report_errors(); +} diff --git a/test/comparisons.cpp b/test/comparisons.cpp index 7b92d220b..d3baf07f4 100644 --- a/test/comparisons.cpp +++ b/test/comparisons.cpp @@ -1,118 +1,141 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2022 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi comparisons" // test title NOLINT(cppcoreguidelines-macro-usage) title -#include +#include // for array, apply, operator!=, operat... -#include +#include -#include +#include // for equal +#include // for complex, operator== +#include // for begin, end, cbegin, cend, size +#include // for swap // IWYU pragma: keep // NOLINT(misc-include-cleaner) namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(comparison_complex) { - using complex = std::complex; +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(comparison_complex) { - multi::array arr = { 1.0, 2.0, 3.0 }; - multi::array arr2 = {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}}; - - BOOST_REQUIRE( arr[1] == arr2[1] ); - BOOST_REQUIRE( arr == arr2 ); - BOOST_REQUIRE( not (arr != arr2) ); - BOOST_REQUIRE( arr2 == arr ); - BOOST_REQUIRE( not (arr2 != arr) ); + using complex = std::complex; + { + multi::array arr = {1.0, 2.0, 3.0}; + multi::array arr2 = { + {1.0, 0.0}, + {2.0, 0.0}, + {3.0, 0.0}, + }; + + BOOST_TEST( arr[1] == arr2[1] ); + BOOST_TEST( arr == arr2 ); + BOOST_TEST( !(arr != arr2) ); + BOOST_TEST( arr2 == arr ); + BOOST_TEST( !(arr2 != arr) ); + } + { + multi::array const arr = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + }; + + multi::array const arr2 = { + {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}}, + {{4.0, 0.0}, {5.0, 0.0}, {6.0, 0.0}}, + }; + + BOOST_TEST( arr[1][1] == arr2[1][1] ); + BOOST_TEST( arr == arr2 ); + BOOST_TEST( !(arr != arr2) ); + BOOST_TEST( arr2 == arr ); + BOOST_TEST( !(arr2 != arr) ); + BOOST_TEST( std::equal(arr[1].begin(), arr[1].end(), begin(arr2[1]), end(arr2[1])) ); + } } + + // BOOST_AUTO_TEST_CASE(multi_comparisons_swap) { - multi::array const arr = { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - }; - multi::array const arr2 = { - {{1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}}, - {{4.0, 0.0}, {5.0, 0.0}, {6.0, 0.0}}, + multi::array arr = { + { {1.2, 1.1}, {2.4, 1.0}}, + {{11.2, 3.0}, {34.4, 4.0}}, + { {1.2, 1.1}, {2.4, 1.0}}, }; + BOOST_TEST( arr[0] < arr[1] ); - BOOST_REQUIRE( arr[1][1] == arr2[1][1] ); - BOOST_REQUIRE( arr == arr2 ); - BOOST_REQUIRE( not (arr != arr2) ); - BOOST_REQUIRE( arr2 == arr ); - BOOST_REQUIRE( not (arr2 != arr) ); - BOOST_REQUIRE( std::equal(arr[1].begin(), arr[1].end(), begin(arr2[1]), end(arr2[1])) ); + swap(arr[0], arr[1]); + BOOST_TEST( arr[1] < arr[0] ); + + swap(arr[0], arr[1]); + BOOST_TEST( arr[0] < arr[1] ); } -} -BOOST_AUTO_TEST_CASE(multi_comparisons_swap) { - multi::array arr = { - { {1.2, 1.1}, {2.4, 1.}}, - {{11.2, 3.0}, {34.4, 4.}}, - { {1.2, 1.1}, {2.4, 1.}}, - }; - BOOST_REQUIRE( arr[0] < arr[1] ); + // BOOST_AUTO_TEST_CASE(comparisons_equality) + { + multi::array arr = { + { {1.2, 1.1}, {2.4, 1.0}}, + {{11.2, 3.0}, {34.4, 4.0}}, + { {1.2, 1.1}, {2.4, 1.0}}, + }; - swap(arr[0], arr[1]); - BOOST_REQUIRE( arr[1] < arr[0] ); + multi::array_ref ref(arr.data_elements(), extensions(arr)); + multi::array_cref cref(data_elements(arr), extensions(arr)); - swap(arr[0], arr[1]); - BOOST_REQUIRE( arr[0] < arr[1] ); -} + BOOST_TEST( arr == arr ); + BOOST_TEST( !(arr != arr) ); + BOOST_TEST( ref == arr ); + BOOST_TEST( !(ref != arr) ); + BOOST_TEST( ref == cref ); + BOOST_TEST( !(ref != cref) ); -BOOST_AUTO_TEST_CASE(comparisons_equality) { - multi::array arr = { - { {1.2, 1.1}, {2.4, 1.0}}, - {{11.2, 3.0}, {34.4, 4.0}}, - { {1.2, 1.1}, {2.4, 1.0}}, - }; + BOOST_TEST( arr[0] == arr[2] ); + BOOST_TEST( ref[0] == arr[2] ); + BOOST_TEST( ref[0] == cref[2] ); - multi::array_ref ref(arr.data_elements(), extensions(arr)); - multi::array_cref cref(data_elements(arr), extensions(arr)); + BOOST_TEST( !( arr[0] != arr[2]) ); + BOOST_TEST( !( ref[0] != ref[2]) ); - BOOST_REQUIRE( arr == arr ); - BOOST_REQUIRE( not (arr != arr) ); - BOOST_REQUIRE( ref == arr ); - BOOST_REQUIRE( not (ref != arr) ); - BOOST_REQUIRE( ref == cref ); - BOOST_REQUIRE( not (ref != cref) ); + BOOST_TEST( !( arr[0] != arr[2]) ); + BOOST_TEST( !( ref[0] != ref[2]) ); + } + + // BOOST_AUTO_TEST_CASE(comparisons_ordering) + { + multi::array arr = { + { {12, 11}, {24, 10}}, + {{112, 30}, {344, 40}}, + { {12, 11}, {24, 10}}, + }; - BOOST_REQUIRE( arr[0] == arr[2] ); - BOOST_REQUIRE( ref[0] == arr[2] ); - BOOST_REQUIRE( ref[0] == cref[2] ); + multi::array_ref ref(arr.data_elements(), extensions(arr)); - BOOST_REQUIRE( not ( arr[0] != arr[2]) ); - BOOST_REQUIRE( not ( ref[0] != ref[2]) ); + multi::array_cref cref(data_elements(arr), extensions(arr)); - BOOST_REQUIRE( not ( arr[0] != arr[2]) ); - BOOST_REQUIRE( not ( ref[0] != ref[2]) ); -} + BOOST_TEST( arr[0] <= arr[1] ); + BOOST_TEST( ref[0] <= arr[1] ); -BOOST_AUTO_TEST_CASE(comparisons_ordering) { - multi::array arr = { - { {1.2, 1.1}, {2.4, 1.}}, - {{11.2, 3.0}, {34.4, 4.}}, - { {1.2, 1.1}, {2.4, 1.}}, - }; +#if !defined(_MSC_VER) // not working on msvc + cuda + BOOST_TEST( cref[0] <= cref[1] ); +#endif - multi::array_ref ref(arr.data_elements(), extensions(arr)); - multi::array_cref cref(data_elements(arr), extensions(arr)); + BOOST_TEST( arr[0][0] <= arr[0][1] ); + BOOST_TEST( ref[0][0] <= arr[0][1] ); - BOOST_REQUIRE( arr[0] <= arr[1] ); - BOOST_REQUIRE( ref[0] <= arr[1] ); - BOOST_REQUIRE( cref[0] <= cref[1] ); + BOOST_TEST( arr[1][0][0] == 112 ); + BOOST_TEST( ref[1][0][0] == 112 ); + BOOST_TEST( cref[1][0][0] == 112 ); - BOOST_REQUIRE( arr[0][0] <= arr[0][1] ); - BOOST_REQUIRE( ref[0][0] <= arr[0][1] ); + BOOST_TEST( arr[0][0][0] == 12 ); + BOOST_TEST( ref[0][0][0] == 12 ); + BOOST_TEST( cref[0][0][0] == 12 ); - BOOST_REQUIRE( arr[1][0][0] == 11.2 ); - BOOST_REQUIRE( ref[1][0][0] == 11.2 ); - BOOST_REQUIRE( cref[1][0][0] == 11.2 ); + swap(ref[0], ref[1]); - BOOST_REQUIRE( arr[0][0][0] == 1.2 ); - BOOST_REQUIRE( ref[0][0][0] == 1.2 ); - BOOST_REQUIRE( cref[0][0][0] == 1.2 ); + BOOST_TEST( begin(arr) < end(arr) ); + BOOST_TEST( !(begin(arr) < begin(arr)) ); - swap(ref[0], ref[1]); + BOOST_TEST( cbegin(arr) < cend(arr) ); - BOOST_REQUIRE( begin(arr) < end(arr) ); - BOOST_REQUIRE( cbegin(arr) < cend(arr) ); + BOOST_TEST( end(arr) - begin(arr) == size(arr) ); + } - BOOST_REQUIRE( end(arr) - begin(arr) == size(arr) ); + return boost::report_errors(); } diff --git a/test/concepts.cpp b/test/concepts.cpp index 9a2654312..78641826e 100644 --- a/test/concepts.cpp +++ b/test/concepts.cpp @@ -1,36 +1,113 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022 Alfredo A. Correa +// Copyright 2022-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi concepts" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wsign-conversion" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif -#include +// NOLINTBEGIN(misc-include-cleaner) +// #include // for BOOST_CONCEPT_ASSERT // IWYU pragma: keep +// #include // for Assignable, CopyCons... // IWYU pragma: keep +// #include // for operator- // IWYU pragma: keep +// #include // for multi_array // IWYU pragma: keep +// #include // for ConstMultiArrayConcept // IWYU pragma: keep +// NOLINTEND(misc-include-cleaner) -#include +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#include // for operator!=, implicit... + +#include // for is_same_v, is_convertib... namespace multi = boost::multi; -using NDArrays = std::tuple< - multi::array, - multi::array, - multi::array ->; +#include + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(convertibles_1D) + { + using NDArray = multi::array; + + NDArray const nda; + (void)nda; + + static_assert(std::is_same_v::value_type>); + static_assert(std::is_same_v::reference>); + + using NDRef = typename NDArray::ref; + + static_assert(std::is_convertible_v); + +#ifndef __NVCC__ + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); +#else + static_assert(std::is_convertible::value); + static_assert(std::is_convertible::value); +#endif + + static_assert(std::is_same_v::value_type>); + static_assert(std::is_same_v::reference>); + } + + // BOOST_AUTO_TEST_CASE(convertibles_2D) + { + using NDArray = multi::array; + + NDArray const nda; + (void)nda; + + static_assert(std::is_same_v::value_type>); + static_assert(std::is_same_v::reference>); + + using NDRef = typename NDArray::ref; + + static_assert(std::is_convertible_v); + + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); + + static_assert(std::is_same_v::value_type>); + static_assert(std::is_same_v::reference>); + } + + // BOOST_AUTO_TEST_CASE(convertibles_3D) + { + using NDArray = multi::array; + + NDArray const nda; + (void)nda; + + static_assert(std::is_same_v::value_type>); + static_assert(std::is_same_v::reference>); -BOOST_AUTO_TEST_CASE_TEMPLATE(convertibles, NDArray, NDArrays) -{ - static_assert( std::is_convertible_v ); - static_assert( std::is_convertible_v ); + using NDRef = typename NDArray::ref; - static_assert( std::is_same_v::value_type>); - static_assert( std::is_same_v::reference >); + static_assert(std::is_convertible_v); - using NDRef = typename NDArray::ref; + // multi::what(); - static_assert( std::is_convertible_v ); + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); - static_assert( std::is_convertible_v ); - static_assert( std::is_convertible_v ); + static_assert(std::is_same_v::value_type>); + static_assert(std::is_same_v::reference>); + } - static_assert( std::is_same_v::value_type> ); - static_assert( std::is_same_v::reference > ); + return boost::report_errors(); } diff --git a/test/conor.cpp b/test/conor.cpp new file mode 100644 index 000000000..7dff3672f --- /dev/null +++ b/test/conor.cpp @@ -0,0 +1,110 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// vvv this has no effect, needs to be passed directly from compilation line "-Wno-psabi" +// #ifdef __GNUC__ +// #pragma GCC diagnostic ignored "-Wpsabi" // for ranges backwards compatibility message +// #endif + +#include // IWYU pragma: keep + +#if __cplusplus >= 202302L + +#include // IWYU pragma: keep // for std::equal +#include // for std::abs +#include // IWYU pragma: keep +#include // for std::get // NOLINT(misc-include-cleaner) + +#if defined(__cplusplus) && (__cplusplus >= 202002L) +#include // for constructible_from // NOLINT(misc-include-cleaner) // IWYU pragma: keep +#include // for std::plus // NOLINT(misc-include-cleaner) // IWYU pragma: keep +#include // for std::cout // NOLINT(misc-include-cleaner) +#include // for std::numeric_limits // NOLINT(misc-include-cleaner) // IWYU pragma: keep +#include // IWYU pragma: keep +#endif + +#include + +// #define FMT_HEADER_ONLY +// #define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +// #include +// #include // from https://gitlab.com/correaa/boost-multi + +namespace stdr = std::ranges; +namespace stdv = std::views; + +auto printR2(auto const& lbl, auto const& arr2D) { + // return fmt::print("{} = \n[{}]\n\n", lbl, fmt::join(arr2D, ",\n ")); + std::cout << lbl << " = \n"; + for(auto const& row : arr2D) { + for(auto const& elem : row) + std::cout << elem << ", "; + std::cout << '\n'; + } + std::cout << '\n'; +} + +constexpr auto maxR1 = []>(R const& row, V low = std::numeric_limits::lowest()) { + return stdr::fold_left(row, low, stdr::max); +}; + +constexpr auto sumR1 = []>(R const& rng, V zero = {}) { + return stdr::fold_left(rng, zero, std::plus<>{}); +}; + +#define FWD(var) std::forward(var) + +auto softmax(auto&& matrix) noexcept { + return // + FWD(matrix) // + | + stdv::transform([](auto&& row) { + auto max = maxR1(row); + return // + FWD(row) // + | + stdv::transform([=](auto ele) noexcept { return std::exp(ele - max); }); + }) // + | + stdv::transform([](auto&& nums) { + auto den = sumR1(nums); + return // + FWD(nums) // + | + stdv::transform([=](auto num) noexcept { return num / den; }); + }); +} + +namespace multi = boost::multi; + +int main() { + auto const matrix = + ([](auto ii) noexcept { return static_cast(ii); } ^ + multi::extensions_t(6)) + .partitioned(2); + + printR2("matrix", matrix); + + printR2("softmax", softmax(matrix)); + + auto const alloc_matrix = multi::array{ + {0.0F, 1.0F, 2.0F}, + {3.0F, 4.0F, 5.0F} + }; + + printR2("softmax", softmax(alloc_matrix)); + + auto const sofmax_copy = multi::array(softmax(alloc_matrix)); + + BOOST_TEST( std::abs(sumR1(sofmax_copy[1]) - 1.0F) < 1e-12F ); + + // auto softmax_copy = +softmax(alloc_matrix); + + return boost::report_errors(); +} +#else +auto main() -> int { + return boost::report_errors(); +} +#endif diff --git a/test/constructors.cpp b/test/constructors.cpp index 5849b2218..dbf5d0107 100644 --- a/test/constructors.cpp +++ b/test/constructors.cpp @@ -1,129 +1,232 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#include +#include -#include +#include -#include +// IWYU pragma: no_include +#include // for complex // IWYU pragma: keep // bug in iwyu 18.1.7 +#include // for size_t +#include // for size +#include // for string +#include // for is_copy_assignable_v, is_copy_co... +#include // for move +#include // for vector namespace multi = boost::multi; -struct multiplies_bind1st{ +struct multiplies_bind1st { using complex = std::complex; explicit multiplies_bind1st(multi::array&& marr) : m_(std::move(marr)) {} // this produces a bug in nvcc11.0 private: multi::array m_; }; -BOOST_AUTO_TEST_CASE(multi_construct_1d) { - multi::static_array arr(multi::extensions_t<1>{multi::iextension{10}}, 1.0); -// multi::static_array arr(multi::array::extensions_type{10}, 1.); - BOOST_REQUIRE( size(arr) == 10 ); - BOOST_REQUIRE( arr[1] == 1.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_constructors_inqnvcc_bug) { - using complex = std::complex; - - multi::array marr({10, 10}); - multiplies_bind1st(std::move(marr)); -} - -BOOST_AUTO_TEST_CASE(multi_constructors_1d) { +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_construct_1d) { - multi::array const arr(multi::extensions_t<1>{multi::iextension{10}}); - BOOST_REQUIRE( size(arr)==10 ); + multi::static_array arr(multi::extensions_t<1>{multi::iextension{10}}, 10); + // multi::static_array arr(multi::array::extensions_type{10}, 10); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[1] == 10 ); } { - multi::array arr(multi::extensions_t<1>{multi::iextension{10}}, double{}); - BOOST_REQUIRE( size(arr)==10 ); - BOOST_REQUIRE( arr[5]== double{} ); + multi::array const arr2(multi::extensions_t<2>{3, 4}); + BOOST_TEST( arr2.size() == 3 ); } + // TODO(correaa) should this work + // { + // multi::array arr2{multi::extensions_t<2>{3, 4}}; + // BOOST_TEST( arr2.size() == 3 ); + // BOOST_TEST( (~arr2).size() == 4 ); + // } + // TODO(correaa) should this work + // { + // std::vector> vecs(3, std::vector(4)); + // multi::array arr2{vecs.begin(), vecs.end()}; + // BOOST_TEST( arr2.size() == 3 ); + // BOOST_TEST( (~arr2).size() == 4 ); + // } + + // BOOST_AUTO_TEST_CASE(multi_constructors_inqnvcc_bug) { - multi::array arr(multi::extensions_t<1>{multi::iextension{10}}, double{}); - BOOST_REQUIRE( size(arr)==10 ); - BOOST_REQUIRE( arr[5]== double{} ); + using complex = std::complex; + + multi::array marr({10, 10}); + multiplies_bind1st(std::move(marr)); } - #if defined(__cpp_deduction_guides) and not defined(__NVCC__) + + // BOOST_AUTO_TEST_CASE(multi_constructors_1d) { - multi::array arr(multi::extensions_t<1>{{0, 10}}, double{}); - BOOST_REQUIRE( size(arr)==10 ); - BOOST_REQUIRE( arr[5]== double{} ); + { + multi::array const arr(multi::extensions_t<1>{multi::iextension{10}}); + BOOST_TEST( size(arr) == 10 ); + } + { + multi::array arr(multi::extensions_t<1>{multi::iextension{10}}, int{}); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[5] == int{} ); + } + { + multi::array arr(multi::extensions_t<1>{multi::iextension{10}}, int{}); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[5] == int{} ); + } +#if defined(__cpp_deduction_guides) && !defined(__NVCC__) + { + multi::array arr(multi::extensions_t<1>({0, 10}), int{}); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[5] == int{} ); + } + { + // clang-format off + multi::array arr({{0, 10}}, int{}); + // clang-format on + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[5] == int{} ); + } + { + multi::array arr({10}, int{}); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[5] == int{} ); + } + { + multi::array arr(10, int{}); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[5] == int{} ); + } +#endif } + + // BOOST_AUTO_TEST_CASE(multi_constructors_2d_ctad) { - multi::array arr({{0, 10}}, double{}); - BOOST_REQUIRE( size(arr)==10 ); - BOOST_REQUIRE( arr[5]== double{} ); +#if defined(__cpp_deduction_guides) && !defined(__NVCC__) + multi::array arr({10, 20}, int{}); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[5][6] == int{} ); +#endif } + + // BOOST_AUTO_TEST_CASE(multi_constructors) { - multi::array arr({10}, double{}); - BOOST_REQUIRE( size(arr)==10 ); - BOOST_REQUIRE( arr[5]== double{} ); + { + // multi::array arr({10}); assert(size(A)==1); // warning in clang + } + { + // multi::array arr({10}, double{}); assert(size(arr)==10); // warning in clang + } + { + // multi::array arr({10}, double{}); assert(size(arr)==10); // warning in clang + } + { + // multi::array arr({10}, 0.); assert(size(arr)==10); // warning in clang + } + { + // multi::array arr({10}, {}); assert(size(arr)==10); // error ambiguous + } + { + multi::array arr = {10}; + + BOOST_TEST( size(arr) == 1 ); + BOOST_TEST( arr[0] == 10 ); + } + + { + multi::array arr = {10}; + BOOST_TEST( size(arr) == 1 ); + BOOST_TEST( arr[0] == 10 ); + } + { + multi::array arr = {10}; + BOOST_TEST( size(arr) == 1 ); + BOOST_TEST( arr[0] == 10 ); + } + { + multi::array arr({10}); + BOOST_TEST( size(arr) == 1 ); + BOOST_TEST( arr[0] == 10 ); + } + { + multi::array arr({10}); + BOOST_TEST( size(arr) == 1 ); + BOOST_TEST( arr[0] == 10 ); + } + { + multi::array arr({10}); + BOOST_TEST( size(arr) == 1 ); + BOOST_TEST( arr[0] == 10 ); + //}{ multi::array arr({{10}}) ; assert( size(arr)==1 and arr[0]==10 ); // clang warns about double bracked + //}{ multi::array arr({{10}}) ; assert( size(arr)==1 and arr[0]==10 ); // clang warns about double bracked + //}{ multi::array arr({{10}}) ; assert( size(arr)==1 and arr[0]==10 ); // clang warns about double bracked + } + { + multi::array const arr({0, 10}); + BOOST_TEST( size(arr) == 2 ); + } + { + multi::array const arr({0, 10}); + BOOST_TEST( size(arr) == 2 ); + } + { + multi::array const arr({0, 10}); + BOOST_TEST( size(arr) == 2 ); + } + { + using T = multi::array; + + static_assert(std::is_nothrow_destructible_v); + static_assert(std::is_default_constructible_v); + static_assert(std::is_nothrow_default_constructible_v); + + static_assert(std::is_copy_constructible_v); + static_assert(std::is_copy_assignable_v); + + // static_assert( std::is_nothrow_copy_constructible_v ); + // static_assert( std::is_nothrow_copy_assignable_v ); + + static_assert(std::is_move_constructible_v); + static_assert(std::is_move_assignable_v); + + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); + } } + + // BOOST_AUTO_TEST_CASE(views_are_not_allocable) { - multi::array arr(10, double{}); - BOOST_REQUIRE( size(arr)==10 ); - BOOST_REQUIRE( arr[5]== double{} ); + // multi::array const AA = {{1.0, 2.0}, {3.0, 4.0}}; + // [[maybe_unused]] decltype(AA[0])* pp = new decltype(AA[0]){AA[0]}; + // delete pp; } - #endif -} - -BOOST_AUTO_TEST_CASE(multi_constructors_2d_ctad) { -#if defined(__cpp_deduction_guides) and not defined(__NVCC__) - multi::array arr({10, 20}, double{}); - BOOST_REQUIRE( size(arr)==10 ); - BOOST_REQUIRE( arr[5][6] == double{} ); -#endif -} - -BOOST_AUTO_TEST_CASE(multi_constructors) { -{ -// multi::array arr({10}); assert(size(A)==1); // warning in clang -}{ -// multi::array arr({10}, double{}); assert(size(arr)==10); // warning in clang -}{ -// multi::array arr({10}, double{}); assert(size(arr)==10); // warning in clang -}{ -// multi::array arr({10}, 0.); assert(size(arr)==10); // warning in clang -}{ -// multi::array arr({10}, {}); assert(size(arr)==10); // error ambiguous -}{ multi::array arr = {10} ; BOOST_REQUIRE( size(arr)==1 and arr[0]==10 ); -}{ multi::array arr = {10} ; BOOST_REQUIRE( size(arr)==1 and arr[0]==10 ); -}{ multi::array arr = {10} ; BOOST_REQUIRE( size(arr)==1 and arr[0]==10 ); -}{ multi::array arr({10}) ; BOOST_REQUIRE( size(arr)==1 and arr[0]==10 ); -}{ multi::array arr({10}) ; BOOST_REQUIRE( size(arr)==1 and arr[0]==10 ); -}{ multi::array arr({10}) ; BOOST_REQUIRE( size(arr)==1 and arr[0]==10 ); -//}{ multi::array arr({{10}}) ; assert( size(arr)==1 and arr[0]==10 ); // clang warns about double bracked -//}{ multi::array arr({{10}}) ; assert( size(arr)==1 and arr[0]==10 ); // clang warns about double bracked -//}{ multi::array arr({{10}}) ; assert( size(arr)==1 and arr[0]==10 ); // clang warns about double bracked -} -{ - multi::array const arr({0, 10}) ; BOOST_REQUIRE( size(arr)==2 ); -} -{ - multi::array const arr({0, 10}) ; BOOST_REQUIRE( size(arr)==2 ); -} -{ - multi::array const arr({0, 10}) ; BOOST_REQUIRE( size(arr)==2 ); -} -{ - using T = multi::array; - - static_assert( std::is_nothrow_destructible_v ); - static_assert( std::is_default_constructible_v ); - static_assert( std::is_nothrow_default_constructible_v ); - static_assert( std::is_copy_constructible_v ); - static_assert( std::is_copy_assignable_v ); + // BOOST_AUTO_TEST_CASE(views_are_not_placeable) + { + // multi::array const AA = {{1.0, 2.0}, {3.0, 4.0}}; + // auto&& A0 = AA[0]; + // new(std::addressof(A0)) decltype(AA[0]){AA[1]}; + } - // static_assert( std::is_nothrow_copy_constructible_v ); - // static_assert( std::is_nothrow_copy_assignable_v ); + // BOOST_AUTO_TEST_CASE(views_cannot_be_elements) + { + multi::array const AA = { + {1.0, 2.0}, + {3.0, 4.0}, + }; + std::vector vv; + vv.emplace_back(AA[0]); + vv.push_back(AA[0]); + // auto&& A0 = AA[0]; + // vv.push_back(A0); + } - static_assert( std::is_move_constructible_v ); - static_assert( std::is_move_assignable_v ); + // BOOST_AUTO_TEST_CASE(views_cannot_be_elements2) + { + // multi::array const AA = {{1.0, 2.0}, {3.0, 4.0}}; + // std::vector vv(3, AA[0]); + } - static_assert( std::is_nothrow_move_constructible_v ); - static_assert( std::is_nothrow_move_assignable_v ); -} + return boost::report_errors(); } diff --git a/test/conversions.cpp b/test/conversions.cpp index a5ecc691e..bd3926254 100644 --- a/test/conversions.cpp +++ b/test/conversions.cpp @@ -1,120 +1,193 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#include +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244) // allow conversion from double to float in uninitialized_construct algorithms +#endif -#include +#include -#include +#include +// #include // for abs // IWYU pragma: keep +#include // for abs +#include // IWYU pragma: keep +#include namespace multi = boost::multi; -// NOLINTBEGIN(fuchsia-default-arguments-calls) // this is a defect in std::complex, not in the library -BOOST_AUTO_TEST_CASE(complex_conversion_float_to_double) { - std::complex const cee{1.0, 2.0}; +namespace { +void fun(multi::array, 2> arr); +void fun(multi::array, 2> arr) { arr.clear(); } - std::complex const zee = cee; +void gun(multi::array, 2> const& /*unused*/); +void gun(multi::array, 2> const& /*unused*/) { + /* no-op */ +} +} // end unnamed namespace - static_assert( multi::is_explicitly_convertible_v, std::complex> ); - static_assert( multi::is_implicitly_convertible_v, std::complex> ); +#include - BOOST_TEST(cee.real() == zee.real()); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // NOLINTBEGIN(fuchsia-default-arguments-calls) // std::complex has a constructor with a default argument, not in the library + // BOOST_AUTO_TEST_CASE(complex_conversion_float_to_double) + { + std::complex const cee{1.0, 2.0}; - multi::static_array, 1> const CEE1(10, std::complex{}); // NOLINT(fuchsia-default-arguments-calls) - multi::static_array, 1> const ZEE1 = CEE1; -} + std::complex const zee = cee; -BOOST_AUTO_TEST_CASE(complex_conversion_double_to_float) { - std::complex const zee{1.0, 2.0}; + static_assert(multi::detail::is_explicitly_convertible_v, std::complex>); + static_assert(multi::detail::is_implicitly_convertible_v, std::complex>); - static_assert( multi::is_explicitly_convertible_v, std::complex>); - static_assert(not multi::is_implicitly_convertible_v, std::complex>); + BOOST_TEST( std::abs( cee.real() - static_cast(zee.real())) < 1E-6F ); - std::complex const cee{zee}; + multi::static_array, 1> const CEE1(10, std::complex{}); // NOLINT(fuchsia-default-arguments-calls) + multi::static_array, 1> const ZEE1 = CEE1; - BOOST_TEST(cee.real() == zee.real()); + // BOOST_TEST( zee == cee ); // fails to compile, ok. since types are not comparable ... + // BOOST_TEST( ZEE1 == CEE1 ); // fails to compile, ok. ...then arrays are not comparable either, but a transformation is comparable... - multi::static_array, 1> const ZEE1(10, std::complex{}); - multi::static_array, 1> const CEE1{ZEE1}; -} + BOOST_TEST( ZEE1 == CEE1.element_transformed([](auto const& ce) noexcept { return std::complex(ce); })); + } -BOOST_AUTO_TEST_CASE(double_to_complex_conversion_documentation) { - // conversions from real to complex is implicit ... - double const dee = 5.0; - std::complex const zee = dee; + // BOOST_AUTO_TEST_CASE(complex_conversion_double_to_float) + { + std::complex const zee{1.0, 2.0}; - BOOST_REQUIRE( zee.real() == 5.0 ); - BOOST_REQUIRE( zee.imag() == 0.0 ); + static_assert(multi::detail::is_explicitly_convertible_v, std::complex>); + static_assert(!multi::detail::is_implicitly_convertible_v, std::complex>); - // ... therefore from array of reals to arrays of complex is also - multi::array DEE({10, 10}, dee); - multi::array, 2> ZEE = DEE; + std::complex const cee{zee}; - BOOST_REQUIRE( ZEE[3][4].real() == 5.0 ); - BOOST_REQUIRE( ZEE[3][4].imag() == 0.0 ); + BOOST_TEST( std::abs( cee.real() - static_cast(zee.real()) ) < 1E-6F ); - multi::array, 2> ZEE2{DEE}; + multi::static_array, 1> const ZEE1(10, std::complex{}); + multi::static_array, 1> const CEE1{ZEE1}; - BOOST_REQUIRE( ZEE2[3][4].real() == 5.0 ); - BOOST_REQUIRE( ZEE2[3][4].imag() == 0.0 ); + BOOST_TEST( ZEE1 == CEE1.element_transformed([](auto const& ce) noexcept { return std::complex(ce); })); + } - // multi::array DEE2{ZEE}; // compilation error -} + // BOOST_AUTO_TEST_CASE(double_to_complex_conversion_documentation) + { + // conversions from real to complex is implicit ... + double const dee = 5.0; + std::complex const zee = dee; -void fun(multi::array, 2> /*unused*/); -void fun(multi::array, 2> /*unused*/) { }; // NOLINT(performance-unnecessary-value-param) + BOOST_TEST( std::abs( zee.real() - 5.0 ) < 1E-6 ); + BOOST_TEST( std::abs( zee.imag() - 0.0 ) < 1E-6 ); -void gun(multi::array, 2> const& /*unused*/); -void gun(multi::array, 2> const& /*unused*/) { }; + // ... therefore from array of reals to arrays of complex is also + multi::array DEE({10, 10}, dee); + multi::array, 2> ZEE = DEE; -BOOST_AUTO_TEST_CASE(conversion_in_function_call) { - multi::array, 2> ZEE({10, 10}); - fun( multi::array, 2>{ZEE} ); - gun( multi::array, 2>{ZEE} ); -} + BOOST_TEST( std::abs( ZEE[3][4].real() - 5.0 ) < 1E-6); + BOOST_TEST( std::abs( ZEE[3][4].imag() - 0.0 ) < 1E-6); -BOOST_AUTO_TEST_CASE(double_to_float) { - double const dee = 5.0; - // float const eff{dee}; // -Wc++11-narrowing // NOLINT(bugprone-narrowing-conversions) - // float const eff = dee; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - // float const eff(dee); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - auto const eff = static_cast(dee); + multi::array, 2> ZEE2{DEE}; - // BOOST_REQUIRE( eff == 5.0 ); // -Wdouble-promotion - BOOST_REQUIRE( eff == 5.0F ); + BOOST_TEST( std::abs( ZEE2[3][4].real() - 5.0 ) < 1E-6); + BOOST_TEST( std::abs( ZEE2[3][4].imag() - 0.0 ) < 1E-6); - multi::array const DEE({10, 10}, dee); - // multi::array const EFF(DEE); - auto const EFF = static_cast>(DEE); // TODO(correaa) investigate producing intermediate types accessible through interminediate types + // multi::array DEE2{ZEE}; // compilation error, good + } - BOOST_REQUIRE( EFF[3][4] == 5.0F ); + // BOOST_AUTO_TEST_CASE(conversion_in_function_call) + { + multi::array, 2> ZEE({10, 10}); + fun(multi::array, 2>{ZEE}); + gun(multi::array, 2>{ZEE}); + } - // multi::array const EFF = DEE; -} + // BOOST_AUTO_TEST_CASE(float_to_double) + { + float const dee = 5.0F; + // float const eff{dee}; // -Wc++11-narrowing // NOLINT(bugprone-narrowing-conversions) + // float const eff = dee; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + // float const eff(dee); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + auto const eff = static_cast(dee); + + BOOST_TEST( std::abs( eff - 5.0) < 1E-6 ); + + multi::array const DEE({10, 10}, dee); + // multi::array const EFF(DEE); + auto const EFF = static_cast>(DEE); // TODO(correaa) investigate producing intermediate types accessible through interminediate types + + BOOST_TEST( std::abs( EFF[3][4] - 5.0 ) < 1E-6 ); + + // multi::array const EFF = DEE; + } -BOOST_AUTO_TEST_CASE(complex_to_complex_conversion) { - std::complex const cee{1.0, 2.0}; - std::complex const zee = cee; + // BOOST_AUTO_TEST_CASE(double_to_float) + { + double const dee = 5.0; + // float const eff{dee}; // -Wc++11-narrowing // NOLINT(bugprone-narrowing-conversions) + // float const eff = dee; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + // float const eff(dee); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + auto const eff = static_cast(dee); - BOOST_REQUIRE( zee.real() == 1.0 ); - BOOST_REQUIRE( zee.imag() == 2.0 ); + BOOST_TEST( std::abs( eff - 5.0F) < 1E-6F ); - // std::complex cee2 = zee; // implicit conversion, compilation error - std::complex const cee2{zee}; + multi::array const DEE({10, 10}, dee); - BOOST_REQUIRE( cee2.real() == 1.0F ); - BOOST_REQUIRE( cee2.imag() == 2.0F ); + // multi::array const EFF(DEE); + auto const EFF = static_cast>(DEE); // TODO(correaa) investigate producing intermediate types accessible through interminediate types - multi::array, 2> const CEE({10, 10}, cee); - multi::array, 2> const ZEE = CEE; + BOOST_TEST( std::abs( EFF[3][4] - 5.0F ) < 1E-6F ); - BOOST_REQUIRE( ZEE[3][4].real() == 1.0 ); - BOOST_REQUIRE( ZEE[3][4].imag() == 2.0 ); + // multi::array const EFF = DEE; + } - // multi::array, 2> const CEE2 = ZEE; // implicit conversion, compilation error - multi::array, 2> const CEE2{ZEE}; + // BOOST_AUTO_TEST_CASE(complex_to_complex_conversion) + { + std::complex const cee{1.0, 2.0}; + std::complex const zee = cee; - BOOST_REQUIRE( CEE2[3][4].real() == 1.0F ); - BOOST_REQUIRE( CEE2[3][4].imag() == 2.0F ); + BOOST_TEST( std::abs( zee.real() - 1.0) < 1E-6); + BOOST_TEST( std::abs( zee.imag() - 2.0) < 1E-6); + + // std::complex cee2 = zee; // implicit conversion, compilation error + std::complex const cee2{zee}; + + BOOST_TEST( std::abs( cee2.real() - 1.0F ) < 1E-6F ); + BOOST_TEST( std::abs( cee2.imag() - 2.0F ) < 1E-6F ); + + multi::array, 2> const CEE({10, 10}, cee); + multi::array, 2> const ZEE = CEE; + + BOOST_TEST( std::abs( ZEE[3][4].real() - 1.0 ) < 1E-6 ); + BOOST_TEST( std::abs( ZEE[3][4].imag() - 2.0 ) < 1E-6 ); + + // multi::array, 2> const CEE2 = ZEE; // implicit conversion, compilation error + multi::array, 2> const CEE2{ZEE}; + + BOOST_TEST( std::abs( CEE2[3][4].real() - 1.0F ) < 1E-6F ); + BOOST_TEST( std::abs( CEE2[3][4].imag() - 2.0F ) < 1E-6F ); + } + // NOLINTEND(fuchsia-default-arguments-calls) + + { + multi::array const arr1D = {1, 2, 3}; + auto const vec1D = arr1D.to(); + BOOST_TEST( vec1D.size() == 3 ); + + multi::array const arr2D = { + {1, 2}, + {3, 4} + }; + auto const vec2D = arr2D.to(); + BOOST_TEST( vec2D.size() == 2 ); + BOOST_TEST( vec2D[0].size() == 2 ); + + auto const wec2D = arr2D.to<>(); + BOOST_TEST( wec2D.size() == 2 ); + BOOST_TEST( wec2D[0].size() == 2 ); + } + + return boost::report_errors(); } -// NOLINTEND(fuchsia-default-arguments-calls) + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/test/cursor.cpp b/test/cursor.cpp new file mode 100644 index 000000000..ce2c05bed --- /dev/null +++ b/test/cursor.cpp @@ -0,0 +1,306 @@ +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array, implicit_cast, explicit_cast + +#include + +#include // for is_same_v, is_same + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + { + static_assert(std::is_trivially_default_constructible_v::cursor>); + static_assert(std::is_trivially_copy_constructible_v::cursor>); + static_assert(std::is_trivially_copy_assignable_v::cursor>); + } + + // BOOST_AUTO_TEST_CASE(iterator_1d) + { + static_assert(std::is_trivially_default_constructible_v::cursor>); + static_assert(std::is_trivially_copy_constructible_v::cursor>); + static_assert(std::is_trivially_copy_assignable_v::cursor>); + } + // BOOST_AUTO_TEST_CASE(iterator_2d) + { + static_assert(std::is_trivially_default_constructible_v::cursor>); + static_assert(std::is_trivially_copy_constructible_v::cursor>); + static_assert(std::is_trivially_copy_assignable_v::cursor>); + + // { + // multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, 99.0); + // BOOST_TEST( arr.size() == 100 ); + // BOOST_TEST( arr.begin() < arr.end() ); + // BOOST_TEST( arr.end() - arr.begin() == arr.size() ); + + // multi::array::const_iterator const cbarr = arr.cbegin(); + // multi::array::iterator barr = arr.begin(); + + // [[maybe_unused]] multi::array::const_iterator const cbarr3{barr}; + + // BOOST_TEST( barr == cbarr ); // problem in C++20 + // BOOST_TEST( cbarr == barr ); // problem in C++20 + + // barr += 1; + // barr -= 1; + // BOOST_TEST( cbarr == barr ); + + // multi::array::const_iterator const cbarr2 = begin(arr); + // BOOST_TEST( cbarr2 == cbarr ); + // } + // { + // multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, 99.0); + // BOOST_TEST( arr.size() == 100 ); + // BOOST_TEST( arr.begin() < arr.end() ); + + // auto arr2 = arr.begin(); + // multi::array::const_iterator const cbb = arr2; + // BOOST_TEST( cbb == arr2 ); + // // BOOST_TEST( arr2 == cbb ); // TODO(correaa) problem in C++20 + // } + // { + // multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, 99.0); + // BOOST_TEST( arr.size() == 100 ); + // BOOST_TEST( arr.begin() < arr.end() ); + + // auto const arrend = arr.end(); + // auto const arrlast = arrend - 1; + + // BOOST_TEST( arrlast + 1 == arrend ); + // } + } + + // BOOST_AUTO_TEST_CASE(iterator_2d) { + // BOOST_TEST((std::is_trivially_copy_constructible_v >)); + // BOOST_TEST((std::is_trivially_copy_assignable_v >)); + // BOOST_TEST((std::is_trivially_default_constructible_v>)); + + // BOOST_TEST((std::is_trivially_copy_constructible_v >)); + // BOOST_TEST((std::is_trivially_copy_assignable_v >)); + // BOOST_TEST((std::is_trivially_default_constructible_v>)); + + // // BOOST_TEST((std::is_trivially_default_constructible_v::iterator>)); // TODO(correaa) + // BOOST_TEST((std::is_trivially_copy_constructible_v ::iterator>)); + // BOOST_TEST((std::is_trivially_copy_assignable_v ::iterator>)); + + // { + // multi::array const arr({120, 140}, 99.0); + + // BOOST_TEST( arr.size() == 120 ); + // BOOST_TEST( arr.cbegin() < arr.cend() ); + // BOOST_TEST( arr.cend() - arr.cbegin() == arr.size() ); + + // using iter = multi::array::iterator; + // static_assert(std::is_same_v); + // static_assert(std::is_same_v>); + // static_assert(std::is_same_v>); + // static_assert(std::is_same_v); + + // using citer = multi::array::const_iterator; + // static_assert(std::is_same_v); + // static_assert(std::is_same_v>); + + // static_assert(std::is_same_v>); + // static_assert(std::is_same_v); + + // auto const arrend = arr.end(); + // auto const arrlast = arrend - 1; + + // BOOST_TEST( arrlast + 1 == arrend ); + // } + // { + // std::vector vec(10000); // std::vector NOLINT(fuchsia-default-arguments-calls) + // multi::array_ref arr(vec.data(), {100, 100}); + // BOOST_TEST(arr.size() == 100); + // begin(arr)[4][3] = 2.0; + // } + // } + + // BOOST_AUTO_TEST_CASE(iterator_interface) { + // multi::array arr = { + // { {12, 11}, {24, 10}}, + // {{112, 30}, {344, 40}}, + // { {12, 11}, {24, 10}} + // }; + + // BOOST_TEST( size(arr) == 3 ); + // BOOST_TEST( size(arr[0]) == 2 ); + // BOOST_TEST( size(arr[0][0]) == 2 ); + // BOOST_TEST( arr[0][0][1] == 11 ); + + // BOOST_TEST( begin(arr) < end(arr) ); + // BOOST_TEST( cbegin(arr) < cend(arr) ); + // BOOST_TEST( begin(arr[0]) < end(arr[0]) ); + // BOOST_TEST( begin(arr[0]) < end(arr[0]) ); + + // // BOOST_TEST(( multi::array::reverse_iterator {A.begin()} == rend(A) )); + + // // BOOST_TEST( rbegin(A) < rend(A) ); + + // BOOST_TEST( end(arr) - begin(arr) == size(arr) ); + // // BOOST_TEST( rend(A) - rbegin(A) == size(A) ); + + // BOOST_TEST( size(*begin(arr) ) == 2 ); + // BOOST_TEST( size( begin(arr)[1]) == 2 ); + + // BOOST_TEST( &(arr[1][1].begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + // BOOST_TEST( &arr[0][1][0] == &arr[0][1][0] ); + // BOOST_TEST( &((*arr.begin())[1][0]) == &arr[0][1][0] ); + + // BOOST_TEST( &((*arr.begin()).operator[](1)[0]) == &arr[0][1][0] ); + // BOOST_TEST( &( arr.begin()->operator[](1)[0]) == &arr[0][1][0] ); + + // BOOST_TEST( &((*arr.begin()).operator[](1).begin()[0]) == &arr[0][1][0] ); // NOLINT(readability-container-data-pointer) test access + // BOOST_TEST( &( arr.begin()->operator[](1).begin()[0]) == &arr[0][1][0] ); // NOLINT(readability-container-data-pointer) test access + + // BOOST_TEST( &((*(arr.begin()+1)).operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + // BOOST_TEST( &( (arr.begin()+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + + // BOOST_TEST( &((*(begin(arr)+1)).operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + // BOOST_TEST( &(( begin(arr)+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + + // BOOST_TEST( &((*(cbegin(arr)+1)).operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + // BOOST_TEST( &(( cbegin(arr)+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + // } + + // BOOST_AUTO_TEST_CASE(iterator_semantics) { + // multi::array arr = { + // { {1.2, 1.1}, {2.4, 1.0}}, + // {{11.2, 3.0}, {34.4, 4.0}}, + // { {1.2, 1.1}, {2.4, 1.0}} + // }; + + // multi::array::iterator it; + // // BOOST_TEST(( multi::array::iterator{} == it )); // `it` is uninitialized + // // BOOST_TEST(( it == multi::array::iterator{} )); + + // it = begin(arr); + // BOOST_TEST( it == begin(arr) ); + + // it += 1; + // it -= 1; + // BOOST_TEST( it == begin(arr) ); + + // auto const& arrc = arr(); + // BOOST_TEST( &arrc[0][0][0] == &arr[0][0][0] ); + + // auto const& arrc2 = arr(); + + // BOOST_TEST( arrc.addressof() == arrc2.addressof() ); // BOOST_TEST( &arrc == &arrc2 ); + + // multi::array::iterator const it2 = begin(arr); + // BOOST_TEST(it == it2); + + // it = end(arr); + // BOOST_TEST(it != it2); + // BOOST_TEST(it > it2); + + // multi::array::iterator const it3{it}; + // BOOST_TEST( it3 == it ); + + // static_assert(std::is_same::iterator::element_ptr, double*>{}); + + // // cit = it3; + // // BOOST_REQUIRE( cit == it3 ); // TODO(correaa) + // // BOOST_REQUIRE( it3 == cit ); // TODO(correaa) + + // // cit = it3; + // // BOOST_TEST( cit == it3 ); // TODO(correaa) + // // BOOST_TEST( it3 == cit ); // TODO(correaa) + + // BOOST_TEST( &arr[0][2][1] == &begin(arr)[0][2][1] ); + + // [[maybe_unused]] multi::array::const_iterator const cit2 = it3; + + // static_assert(decltype(begin(arr))::rank_v == 3, "!"); + // static_assert(decltype(begin(arr))::rank{} == 3, "!"); + + // // auto&& ref = multi::ref(begin(arr), end(arr)); + + // // BOOST_TEST( arr.base() == ref.base() ); + // // BOOST_TEST( arr[0][2][1] == ref[0][2][1] ); + // // BOOST_TEST( &arr[0][2][1] == &ref[0][2][1] ); + // // BOOST_TEST( arr.layout().stride() == ref.layout().stride()); + // // BOOST_TEST( arr.layout().offset() == ref.layout().offset()); + // // BOOST_TEST( arr.layout().nelems() == ref.layout().nelems()); + + // // BOOST_TEST( arr.num_elements() == ref.num_elements() ); + // // BOOST_TEST( arr.stride() == ref.stride() ); + // // BOOST_TEST( arr.layout() == ref.layout() ); + + // // BOOST_TEST( &multi::ref(begin(arr), end(arr)) == &arr ); + // } + + // BOOST_AUTO_TEST_CASE(iterator_arrow_operator) { + // // NOLINTBEGIN(fuchsia-default-arguments-calls) std::string has a default constructor + // multi::array arr = { + // {"00", "01"}, + // {"10", "11"}, + // {"20", "21"} + // }; + // // NOLINTEND(fuchsia-default-arguments-calls) + + // BOOST_TEST( arr[1][0] == "10" ); + + // BOOST_TEST( std::is_sorted(begin(arr), end(arr)) ); // sorted by rows + // BOOST_TEST( std::is_sorted(begin(arr.rotated()), end(arr.rotated())) ); // sorted by cols + + // BOOST_TEST( (*begin( arr )).size() == arr[0].size() ); + // BOOST_TEST( begin( arr )->size() == arr[0].size() ); + + // BOOST_TEST( (*begin( arr.rotated() )).size() == arr.size() ); + // BOOST_TEST( begin( arr.rotated() )->size() == arr.size() ); + + // BOOST_TEST( &((*begin( arr )).operator[](1)) == &(arr[0][1]) ); + // BOOST_TEST( &( begin( arr )->operator[](1)) == &(arr[0][1]) ); + + // BOOST_TEST( &((*begin( arr.rotated() )).operator[](1)) == &(arr[1][0]) ); + // BOOST_TEST( &( begin( arr.rotated() )->operator[](1)) == &(arr[1][0]) ); + // } + + // BOOST_AUTO_TEST_CASE(index_range_iteration) { + // multi::index_range irng(0, 5); // semiopen interval + // std::ostringstream out; + // std::copy(irng.begin(), irng.end(), std::ostream_iterator{out, ","}); + // BOOST_TEST_EQ(out.str(), std::string{"0,1,2,3,4,"}); // NOLINT(fuchsia-default-arguments-calls) + + // BOOST_TEST( std::accumulate(begin(irng), end(irng), static_cast(0U)) == irng.size()*(irng.size()-1)/2 ); + + // auto const sum_of_cubes = [](auto&& acc, auto const& elem) { + // return std::forward(acc) + elem * elem * elem; + // }; + // BOOST_TEST( std::accumulate(begin(irng), end(irng), multi::index_range::value_type{}, sum_of_cubes) > 0 ); + // } + + // BOOST_AUTO_TEST_CASE(multi_reverse_iterator_1D) { + // multi::array arr(100, 66.0); + // BOOST_TEST( &arr[99] == &*std::make_reverse_iterator(arr.end()) ); + + // auto rbegin = std::make_reverse_iterator(arr.end()); + // rbegin += 100; + // multi::array::iterator const begin{rbegin.base()}; + // BOOST_TEST( begin == arr.begin() ); + // } + + // BOOST_AUTO_TEST_CASE(multi_reverse_iterator_2D) { + // multi::array arr = { + // { 10, 20}, + // { 100, 200}, + // {1000, 2000} + // }; + // BOOST_TEST( (*arr.begin())[1] == 20 ); + // BOOST_TEST( arr.begin()->operator[](1) == 20 ); + + // auto rbegin = std::make_reverse_iterator(arr.end()); + + // BOOST_TEST( (*rbegin)[1] == 2000 ); + + // BOOST_TEST( arr.begin() < arr.begin() + 1 ); + // BOOST_TEST( arr.end() - 1 < arr.end() ); + // } + + return boost::report_errors(); +} diff --git a/test/diagonal.cpp b/test/diagonal.cpp index f24f98c63..2ba891fb1 100644 --- a/test/diagonal.cpp +++ b/test/diagonal.cpp @@ -1,20 +1,24 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2024 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi array diagonal" // test title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array, layout_t, static_array -#include +#include -#include +#include // for transform +#include // for plus // IWYU pragma: keep +#include // for accumulate namespace multi = boost::multi; +namespace { template auto trace_with_indices(Array2D const& arr) { typename Array2D::element_type sum{0}; for(auto i : extension(arr)) { // NOLINT(altera-unroll-loops) testing loops - sum += arr[i][i]; + sum += arr[i][i]; // cppcheck-suppress useStlAlgorithm ; } return sum; } @@ -23,37 +27,112 @@ template auto trace_with_diagonal(Array2D const& arr) { typename Array2D::element_type sum{0}; for(auto aii : arr.diagonal()) { // NOLINT(altera-unroll-loops) testing loops - sum += aii; + sum += aii; // cppcheck-suppress useStlAlgorithm ; } return sum; } template auto trace_with_accumulate(Array2D const& arr) { - return std::accumulate(arr.diagonal().begin(), arr.diagonal().end(), 0); + return std::accumulate(arr.diagonal().begin(), arr.diagonal().end(), static_cast(0)); } +} // end unnamed namespace -// g++ 7 defect -// template -// auto trace_with_reduce(Array2D const& arr) { -// return std::reduce(arr.diagonal().begin(), arr.diagonal().end(), 0); -// } +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // trace_test) + { + using int_element = multi::index; + multi::array arr({5, 5}, 0); -BOOST_AUTO_TEST_CASE(trace_test) { - multi::array arr({5, 5}, 0); + auto [is, js] = extensions(arr); - auto [is, js] = extensions(arr); - for(auto i : is) { // NOLINT(altera-unroll-loops) testing loops - for(auto j : js) { // NOLINT(altera-unroll-loops) testing loops - arr[i][j] = static_cast(10 * i + j); + // NOLINTNEXTLINE(altera-unroll-loops) testing loops + for(auto i : is) { + for(auto j : js) { // NOLINT(altera-unroll-loops) testing loops + arr[i][j] = (10 * i) + j; + } } + + auto tr = trace_with_diagonal(arr); + + BOOST_TEST( tr == 00 + 11 + 22 + 33 + 44 ); + + BOOST_TEST( trace_with_diagonal(arr) == trace_with_indices(arr) ); + BOOST_TEST( trace_with_diagonal(arr) == trace_with_accumulate(arr) ); + } + + // broadcasted + { + multi::array const arr = { + {0, 1, 2}, + {4, 5, 6}, + {8, 9, 10}, + }; + + BOOST_TEST( arr.diagonal().begin() != arr.diagonal().end() ); + BOOST_TEST( arr.diagonal().end() - arr.diagonal().begin() == 3 ); + } + + // broadcasted) + { + multi::array const arr = { + {0, 1, 2, 3}, + {4, 5, 6, 7}, + {8, 9, 10, 11}, + }; + + auto const& a3D = arr.broadcasted(); + + BOOST_TEST( &a3D[0][2][1] == &arr[2][1] ); + BOOST_TEST( &a3D[1][2][1] == &arr[2][1] ); + + { + auto const& arr_instance = a3D[0]; + BOOST_TEST( &arr_instance[2][1] == &arr[2][1] ); + } + { + auto const& arr_instance = a3D[99]; + BOOST_TEST( &arr_instance[2][1] == &arr[2][1] ); + } + { + auto const& arr_instance = a3D[-99]; + BOOST_TEST( &arr_instance[2][1] == &arr[2][1] ); + } + { + auto const& a3D_self = a3D(); + BOOST_TEST( &a3D_self[ 4][2][1] == &arr[2][1] ); + BOOST_TEST( &a3D_self[99][2][1] == &arr[2][1] ); + } + } + + // broadcast_1D + { + multi::array const arr = {0, 1, 2, 3}; + + auto const& a2D = arr.broadcasted(); + + BOOST_TEST( &a2D[0][2] == &arr[2] ); + BOOST_TEST( &a2D[1][2] == &arr[2] ); } - auto tr = trace_with_diagonal(arr); + // broadcast_0D + { + multi::array arr = {0, 1, 2, 3}; + multi::array const vv(2); + + auto const& v1D = vv.broadcasted(); + + BOOST_TEST( &v1D[0] == vv.base() ); + BOOST_TEST( &v1D[1] == vv.base() ); - BOOST_REQUIRE( tr == 00 + 11 + 22 + 33 + 44 ); + multi::array r1D({4}, 0); + std::transform(arr.begin(), arr.end(), v1D.begin(), r1D.begin(), std::plus<>{}); // NOLINT(modernize-use-ranges) + + BOOST_TEST( r1D[3] == arr[3] + 2 ); + + std::transform(arr.begin(), arr.end(), v1D.begin(), arr.begin(), [](auto, auto ve) { return ve; }); // NOLINT(modernize-use-ranges) + BOOST_TEST( arr[3] == 2 ); + } - BOOST_REQUIRE( trace_with_diagonal(arr) == trace_with_indices(arr) ); - BOOST_REQUIRE( trace_with_diagonal(arr) == trace_with_accumulate(arr) ); -// BOOST_REQUIRE( trace_with_diagonal(arr) == trace_with_reduce(arr) ); + return boost::report_errors(); } diff --git a/test/drop.cpp b/test/drop.cpp new file mode 100644 index 000000000..5d34c9b8b --- /dev/null +++ b/test/drop.cpp @@ -0,0 +1,27 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for implicit_cast, explicit_cast + +#include + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + /* drop */ + { + multi::array AA({ + {1, 5}, + {1, 5} + }, + 0); + AA[1][1] = 12; + AA[2][1] = 13; + + BOOST_TEST( AA.dropped(1)[1][1] == 13 ); + } + + return boost::report_errors(); +} diff --git a/test/element_access.cpp b/test/element_access.cpp index 279aa21bb..98a5f7b57 100644 --- a/test/element_access.cpp +++ b/test/element_access.cpp @@ -1,250 +1,392 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array, layout_t, subarray, range + +#include + +#include // for copy +#include // for array, get +#include // for deque, operator== +#include // for size, back_inserter +#include // for iota +#include // for operator""s, allocator, basic_st... +#include // for apply, make_tuple, tuple +#include // for is_assignable_v +#include // for move, forward +#include // for vector, operator== -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi element access" // test title NOLINT(cppcoreguidelines-macro-usage) title -#include +namespace multi = boost::multi; -#include "multi/array.hpp" +namespace { +template +void assign_elements_from_to(Array1D&& arr, std::deque>& dest) { // NOLINT(google-runtime-references) dest is mutated + // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved) + std::copy(std::forward(arr).begin(), std::forward(arr).end(), std::back_inserter(dest)); +} +} // end namespace -#include -#include // for std::iota +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(empty_intersection) + { + multi::array arr({10}); + multi::array arr2; -namespace multi = boost::multi; + auto const is = intersection(arr.extension(), arr2.extension()); + BOOST_TEST( arr(is).is_empty() ); + arr2(is) = arr(is); -namespace test_bee { -struct bee {}; + BOOST_TEST( arr2(is) == arr(is) ); + } -template auto paren(Array&& arr, bee const& /*unused*/) -> decltype(auto) { - return std::forward(arr)(0); -} -} // end namespace test_bee + // BOOST_AUTO_TEST_CASE(multi_tests_element_access_with_tuple) + { + multi::array arr({3, 3}, 'k'); -BOOST_AUTO_TEST_CASE(overload_initlist) { - multi::array arr = {10.0}; - test_bee::bee const zero; - BOOST_REQUIRE( &arr(0) == &arr(zero) ); -} + std::array point = { + {1, 2} + }; -BOOST_AUTO_TEST_CASE(overload_paren) { - multi::array arr({10}); - test_bee::bee const zero; - BOOST_REQUIRE( &arr(0) == &arr(zero) ); -} + BOOST_TEST( arr[point[0]][point[1]] == arr(1, 2) ); + BOOST_TEST( &arr(point[0], point[1]) == &arr[point[0]][point[1]] ); -BOOST_AUTO_TEST_CASE(empty_intersection) { - multi::array arr({10}); - multi::array arr2; + BOOST_TEST( &arr[point[0]][point[1]] == &arr(point[0], point[1]) ); + BOOST_TEST( &arr(point[0], point[1]) == &arr.apply(point) ); - auto const is = intersection(arr.extension(), arr2.extension()); - BOOST_REQUIRE( arr(is).is_empty() ); - arr2(is) = arr(is); + BOOST_TEST( &arr[point[0]][point[1]] == &std::apply(arr, point) ); + BOOST_TEST( &arr[point[0]][point[1]] == & apply(arr, point) ); + } - BOOST_REQUIRE( arr2(is) == arr(is) ); -} + // BOOST_AUTO_TEST_CASE(multi_tests_extension_with_tuple) + { + { + multi::array::extensions_type const ext = {3, 4}; + + multi::array const arr(ext, 44.0); + + BOOST_TEST( size(arr) == 3 ); + } + { + auto const [en, em] = std::make_tuple(3, 4); + multi::array const arr({en, em}, 44.0); + BOOST_TEST( size(arr) == 3 ); + } + { + auto arr = std::apply([](auto const&... szs) { return multi::array({szs...}, 55.0); }, std::make_tuple(3, 4)); + BOOST_TEST( size(arr) == 3 ); + + using std::get; + + BOOST_TEST( get<0>(arr.sizes()) == 3 ); + BOOST_TEST( get<1>(arr.sizes()) == 4 ); + } + } -BOOST_AUTO_TEST_CASE(multi_tests_element_access_with_tuple) { - multi::array arr({3, 3}, 44.0); - std::array point = {{1, 2}}; + // BOOST_AUTO_TEST_CASE(multi_test_constness_reference) + { + multi::array const carr({10, 10}, '9'); - BOOST_REQUIRE( arr[point[0]][point[1]] == arr(1, 2) ); - BOOST_REQUIRE( &arr(point[0], point[1]) == &arr[point[0]][point[1]] ); + BOOST_TEST( size( carr(1, {0, 3}) ) == 3 ); - BOOST_REQUIRE( &arr[point[0]][point[1]] == &arr(point[0], point[1]) ); - BOOST_REQUIRE( &arr(point[0], point[1]) == &arr.apply(point) ); + BOOST_TEST( carr(1, {0, 3})[1] == '9' ); + static_assert(decltype(carr({0, 3}, 1))::rank_v == 1); + BOOST_TEST( size(carr.sliced(0, 3)) == 3 ); - BOOST_REQUIRE( &arr[point[0]][point[1]] == &std::apply(arr, point) ); - BOOST_REQUIRE( &arr[point[0]][point[1]] == & apply(arr, point) ); -} + BOOST_TEST( carr.range({0, 3}).rotated()[1].unrotated().size() == 3 ); + + BOOST_TEST( carr({0, 3}, {0, 3})[1][1] == '9' ); + + static_assert(!std::is_assignable_v); + } -BOOST_AUTO_TEST_CASE(multi_tests_extension_with_tuple) { + // comparison for elements iterator { - multi::array::extensions_type const ext = {3, 4}; - multi::array const arr(ext, 44.0); - BOOST_REQUIRE( size(arr) == 3 ); + multi::array const arr({3, 3}, 99); + auto const& subarr = arr({0, 3}, {0, 3}); + + BOOST_TEST( subarr.elements().begin() == subarr.elements().begin() ); + BOOST_TEST( !(subarr.elements().begin() != subarr.elements().begin()) ); + BOOST_TEST( !(subarr.elements().begin() < subarr.elements().begin()) ); // cppcheck-suppress duplicateExpression ; for testing purposes } + + // BOOST_AUTO_TEST_CASE(multi_test_stencil) { - auto const [en, em] = std::make_tuple(3, 4); - multi::array const arr({en, em}, 44.0); - BOOST_REQUIRE( size(arr) == 3 ); + using namespace std::string_literals; // NOLINT(build/namespaces) ""s + + // NOLINTBEGIN(misc-include-cleaner) bug in clang-tidy 18 + multi::array arr = { + {"a"s, "b"s, "c"s, "d"s, "e"s}, + {"f"s, "g"s, "h"s, "f"s, "g"s}, + {"h"s, "i"s, "j"s, "k"s, "l"s}, + }; + // NOLINTEND(misc-include-cleaner) bug in clang-tidy 18 + + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr.num_elements() == 3*5L ); + BOOST_TEST( arr[1][2] == "h" ); + + BOOST_TEST( size(arr ({1, 3}, {2, 5})) == 2 ); + BOOST_TEST( extension(arr ({1, 3}, {2, 5})).first() == 0 ); + BOOST_TEST( arr ({1, 3}, {2, 5}).num_elements() == 2*3L ); + BOOST_TEST( arr ({1, 3}, {2, 5}).num_elements() == 2*3L ); + BOOST_TEST( arr ({1, 3}, {2, 5})[0][0] == "h" ); + BOOST_TEST( &arr ({1, 3}, {2, 5})[0][0] == &arr[1][2] ); + + BOOST_TEST( size(arr.stenciled({1, 3}, {2, 5})) == 2 ); + // BOOST_TEST( extension(arr.stenciled({1, 3}, {2, 5})).first() == 1 ); + // BOOST_TEST( arr.stenciled({1, 3}, {2, 5}).num_elements() == 2*3L ); + // BOOST_TEST( arr.stenciled({1, 3}, {2, 5}) [1][2] == "h" ); + // BOOST_TEST( &arr.stenciled({1, 3}, {2, 5}) [1][2] == &arr[1][2] ); + + BOOST_TEST( arr().elements().size() == arr.num_elements() ); + + BOOST_TEST( &arr({1, 3}, {2, 5}).elements()[0] == &arr(1, 2) ); + BOOST_TEST( &arr({1, 3}, {2, 5}).elements()[arr({1, 3}, {2, 5}).elements().size() - 1] == &arr(2, 4) ); + + BOOST_TEST( &arr({1, 3}, {2, 5}).elements().front() == &arr(1, 2) ); + BOOST_TEST( &arr({1, 3}, {2, 5}).elements().back() == &arr(2, 4) ); + + auto beg = arr({1, 3}, {2, 5}).elements().begin(); + beg += (arr({1, 3}, {2, 5}).elements().size() - 1); + BOOST_TEST( &*beg == &arr(2, 4) ); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) bug in clang-tidy 14? + + { + auto beg1 = arr({1, 3}, {2, 5}).elements().begin(); + auto end1 = arr({1, 3}, {2, 5}).elements().end(); + auto end2 = arr({1, 3}, {2, 5}).elements().end(); + + for(; end1 != beg1; --end1) { // NOLINT(altera-id-dependent-backward-branch,altera-unroll-loops) + } + BOOST_TEST( end1 == beg1 ); // cppcheck-suppress knownConditionTrueFalse ; for testing purposes + + for(; end1 != end2; ++end1) { // NOLINT(altera-id-dependent-backward-branch,altera-unroll-loops) + } + BOOST_TEST( end1 == end2 ); // cppcheck-suppress knownConditionTrueFalse ; + } + + BOOST_TEST( arr.elements().size() == arr.elements().end() - arr.elements().begin() ); + BOOST_TEST( arr.elements().size() - 1 == arr.elements().end() - (arr.elements().begin() + 1) ); + BOOST_TEST( arr.elements().size() - 1 == (arr.elements().end() - 1) - arr.elements().begin() ); + BOOST_TEST( arr.elements().size() - 2 == (arr.elements().end() - 1) - (arr.elements().begin() + 1) ); } + + // BOOST_AUTO_TEST_CASE(empty_elements) { - auto arr = std::apply([](auto const&... szs) { return multi::array({szs...}, 55.0); }, std::make_tuple(3, 4)); - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( std::get<0>(sizes(arr)) == 3 ); - BOOST_REQUIRE( std::get<1>(sizes(arr)) == 4 ); + multi::array arr1; + multi::array arr2; + + BOOST_TEST( arr1.elements().size() == 0 ); + BOOST_TEST( arr2.elements().size() == 0 ); + BOOST_TEST( arr1.elements() == arr2.elements() ); + BOOST_TEST( !(arr1.elements() != arr2.elements()) ); } -} -BOOST_AUTO_TEST_CASE(multi_test_constness_reference) { - multi::array const carr({10, 10}, 99.0); + // BOOST_AUTO_TEST_CASE(multi_test_elements_1D) + { + multi::array arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + BOOST_TEST( arr.size() == 10 ); - BOOST_REQUIRE( size( carr(1, {0, 3}) ) == 3 ); + BOOST_TEST( arr.elements().size() == 10 ); + BOOST_TEST( &arr.elements()[0] == &arr[0] ); + BOOST_TEST( &arr.elements()[9] == &arr[9] ); - BOOST_REQUIRE( carr(1, {0, 3})[1] == 99. ); - static_assert(decltype(carr({0, 3}, 1))::rank_v == 1); - BOOST_REQUIRE( size(carr.sliced(0, 3)) == 3 ); + BOOST_TEST( arr.elements().begin() < arr.elements().begin() + 1 ); + BOOST_TEST( !(arr.elements().begin() < arr.elements().begin()) ); + BOOST_TEST( !(arr.elements().begin() + 1 < arr.elements().begin() + 1) ); - BOOST_REQUIRE( carr.range({0, 3}).rotated()[1].unrotated().size() == 3 ); + BOOST_TEST( arr.elements().begin() < arr.elements().end() ); + BOOST_TEST( arr.elements().end() > arr.elements().begin() ); + BOOST_TEST( arr.elements().begin() != arr.elements().end() ); + BOOST_TEST( !( arr.elements().begin() == arr.elements().end() ) ); - BOOST_REQUIRE( carr({0, 3}, {0, 3})[1][1] == 99. ); + BOOST_TEST( arr().elements().begin() < arr().elements().end() ); + BOOST_TEST( arr().elements().begin() == arr().elements().begin() ); - static_assert(not std::is_assignable_v); -} + BOOST_TEST( arr().elements().begin() < arr().elements().end() || arr().elements().begin() == arr().elements().end() ); + BOOST_TEST( arr().elements().begin() <= arr().elements().end() ); -BOOST_AUTO_TEST_CASE(multi_test_stencil) { - using namespace std::string_literals; // NOLINT(build/namespaces) ""s + BOOST_TEST( arr().elements().end() > arr().elements().begin() ); + BOOST_TEST( arr().elements().end() >= arr().elements().begin() ); - multi::array arr = { - {"a"s, "b"s, "c"s, "d"s, "e"s}, - {"f"s, "g"s, "h"s, "f"s, "g"s}, - {"h"s, "i"s, "j"s, "k"s, "l"s}, - }; + arr.elements() = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + BOOST_TEST( arr[2] == 7 ); + BOOST_TEST( arr.elements()[2] == 7 ); + BOOST_TEST( arr.elements().end() - arr.elements().begin() == arr.size() ); + BOOST_TEST( arr.elements().begin() + arr.size() == arr.elements().end() ); + BOOST_TEST( &(*(arr.elements().begin() + arr.size())) == &(*arr.elements().end()) ); - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr.num_elements() == 3*5L ); - BOOST_REQUIRE( arr[1][2] == "h" ); + auto beg = arr.elements().begin(); + beg += arr.size(); + BOOST_TEST( &(*beg) == &(*arr.elements().end()) ); + } - BOOST_REQUIRE( size(arr ({1, 3}, {2, 5})) == 2 ); - BOOST_REQUIRE( extension(arr ({1, 3}, {2, 5})).start() == 0 ); - BOOST_REQUIRE( arr ({1, 3}, {2, 5}).num_elements() == 2*3L ); - BOOST_REQUIRE( arr ({1, 3}, {2, 5}).num_elements() == 2*3L ); - BOOST_REQUIRE( arr ({1, 3}, {2, 5})[0][0] == "h" ); - BOOST_REQUIRE( &arr ({1, 3}, {2, 5})[0][0] == &arr[1][2] ); + // BOOST_AUTO_TEST_CASE(multi_test_elements_1D_as_range) + { + multi::array arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + BOOST_TEST( arr.size() == 10 ); - BOOST_REQUIRE( size(arr.stenciled({1, 3}, {2, 5})) == 2 ); - BOOST_REQUIRE( extension(arr.stenciled({1, 3}, {2, 5})).start() == 1 ); - BOOST_REQUIRE( arr.stenciled({1, 3}, {2, 5}).num_elements() == 2*3L ); - BOOST_REQUIRE( arr.stenciled({1, 3}, {2, 5}) [1][2] == "h" ); - BOOST_REQUIRE( &arr.stenciled({1, 3}, {2, 5}) [1][2] == &arr[1][2] ); + arr().elements() = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + BOOST_TEST( arr[2] == 7 ); + BOOST_TEST( arr.elements()[2] == 7 ); - BOOST_REQUIRE( arr().elements().size() == arr.num_elements() ); + arr(2) = 9; + BOOST_TEST( arr[2] == 9 ); + } - BOOST_REQUIRE( &arr({1, 3}, {2, 5}).elements()[0] == &arr(1, 2) ); - BOOST_REQUIRE( &arr({1, 3}, {2, 5}).elements()[arr({1, 3}, {2, 5}).elements().size() - 1] == &arr(2, 4) ); + // BOOST_AUTO_TEST_CASE(elements_from_init_list_2D) + { + multi::array arr({3, 2}); + arr().elements() = {1, 2, 3, 4, 5, 6}; + BOOST_TEST(arr[1][0] == 3); - BOOST_REQUIRE( &arr({1, 3}, {2, 5}).elements().front() == &arr(1, 2) ); - BOOST_REQUIRE( &arr({1, 3}, {2, 5}).elements().back() == &arr(2, 4) ); -} + arr.elements() = {10, 20, 30, 40, 50, 60}; + BOOST_TEST(arr[1][0] == 30); + } -BOOST_AUTO_TEST_CASE(multi_test_elements_1D) { - multi::array arr = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; - BOOST_REQUIRE( arr.size() == 10 ); + // BOOST_AUTO_TEST_CASE(front_back_2D) + { + multi::array arr({3, 4}); + std::iota(arr.elements().begin(), arr.elements().end(), int{}); - BOOST_REQUIRE( arr.elements().size() == 10 ); - BOOST_REQUIRE( &arr.elements()[0] == &arr[0] ); - BOOST_REQUIRE( &arr.elements()[9] == &arr[9] ); + BOOST_TEST( arr.front()[2] == arr[0][2] ); + BOOST_TEST( &arr.front()[2] == &arr[0][2] ); - BOOST_REQUIRE( arr.elements().begin() < arr.elements().end() ); - BOOST_REQUIRE( arr.elements().end() > arr.elements().begin() ); - BOOST_REQUIRE( arr.elements().begin() != arr.elements().end() ); - BOOST_REQUIRE( not( arr.elements().begin() == arr.elements().end() ) ); + BOOST_TEST( (*(arr.begin() + 2)).base() == arr[2].base() ); + BOOST_TEST( (arr.begin() + 2)->base() == arr[2].base() ); - BOOST_REQUIRE( arr().elements().begin() < arr().elements().end() ); - BOOST_REQUIRE( arr().elements().begin() == arr().elements().begin() ); + BOOST_TEST( (*(arr.end() - 1)).base() == arr[2].base() ); + BOOST_TEST( (arr.end() - 1)->base() == arr[2].base() ); - BOOST_REQUIRE( arr().elements().begin() < arr().elements().end() or arr().elements().begin() == arr().elements().end() ); - BOOST_REQUIRE( arr().elements().begin() <= arr().elements().end() ); + // auto const prv = std::prev(arr.end()); + // BOOST_TEST( (*(prv)).base() == arr[2].base() ); // TODO(correaa) investigate why this fails in NVCC - BOOST_REQUIRE( arr().elements().end() > arr().elements().begin() ); - BOOST_REQUIRE( arr().elements().end() >= arr().elements().begin() ); + // BOOST_TEST( (*(std::prev(arr.end()))).base() == arr[2].base() ); // TODO(correaa) investigate why this fails in NVCC + // BOOST_TEST( (*(std::prev(arr.end(), 1))).base() == arr[2].base() ); // TODO(correaa) investigate why this fails in NVCC - arr.elements() = {9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0}; - BOOST_REQUIRE( arr[2] == 7. ); - BOOST_REQUIRE( arr.elements()[2] == 7. ); -} + BOOST_TEST( arr.back ().base() == arr[2].base() ); + BOOST_TEST( arr.back () == arr[2] ); -BOOST_AUTO_TEST_CASE(multi_test_elements_1D_as_range) { - multi::array arr = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; - BOOST_REQUIRE( arr.size() == 10 ); + BOOST_TEST( arr.back ()[2] == arr[2][2] ); + BOOST_TEST( &arr.back ()[2] == &arr[2][2] ); + } - arr().elements() = {9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0}; - BOOST_REQUIRE( arr[2] == 7. ); - BOOST_REQUIRE( arr.elements()[2] == 7.0 ); -} + // BOOST_AUTO_TEST_CASE(front_back_1D) + { + multi::array arr({30}, int{}); + std::iota(arr.elements().begin(), arr.elements().end(), 0); -BOOST_AUTO_TEST_CASE(elements_from_init_list_2D) { - multi::array arr({3, 2}); - arr().elements() = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - BOOST_REQUIRE(arr[1][0] == 3.0); + BOOST_TEST( arr.front() == arr[ 0] ); + BOOST_TEST( &arr.front() == &arr[ 0] ); - arr.elements() = {10.0, 20.0, 30.0, 40.0, 50.0, 60.0}; - BOOST_REQUIRE(arr[1][0] == 30.); -} + BOOST_TEST( arr.back () == arr[29] ); + BOOST_TEST( &arr.back () == &arr[29] ); + } -BOOST_AUTO_TEST_CASE(front_back_2D) { - multi::array arr({3, 4}); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 0.0); + // BOOST_AUTO_TEST_CASE(elements_rvalues) + { + using movable_type = std::vector; + movable_type const movable_value(5, 99); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr.front()[2] == arr[0][2] ); - BOOST_REQUIRE( &arr.front()[2] == &arr[0][2] ); + multi::array arr = {movable_value, movable_value, movable_value}; + BOOST_TEST( arr.size() == 3 ); - BOOST_REQUIRE( arr.back ()[2] == arr[2][2] ); - BOOST_REQUIRE( &arr.back ()[2] == &arr[2][2] ); -} + movable_type const front = std::move(arr)[0]; // cppcheck-suppress accessMoved ; for testing purposes -BOOST_AUTO_TEST_CASE(front_back_1D) { - multi::array arr({30}, double{}); - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 0.0); + BOOST_TEST( front == movable_value ); - BOOST_REQUIRE( arr.front() == arr[ 0] ); - BOOST_REQUIRE( &arr.front() == &arr[ 0] ); + // cppcheck-suppress accessMoved ; for testing purposes + BOOST_TEST( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes - BOOST_REQUIRE( arr.back () == arr[29] ); - BOOST_REQUIRE( &arr.back () == &arr[29] ); -} + BOOST_TEST( arr[1] == movable_value ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes -BOOST_AUTO_TEST_CASE(elements_rvalues) { - using movable_type = std::vector; - movable_type const movable_value(5., 99.0); // NOLINT(fuchsia-default-arguments-calls) + std::move(arr)[1] = movable_value; + } - multi::array arr = {movable_value, movable_value, movable_value}; - BOOST_REQUIRE( arr.size() == 3 ); + // BOOST_AUTO_TEST_CASE(elements_rvalues_nomove) + { + using movable_type = std::vector; + movable_type const movable_value(5, 99.0); // NOLINT(fuchsia-default-arguments-calls) - movable_type const front = std::move(arr)[0]; + multi::array arr = {movable_value, movable_value, movable_value}; + BOOST_TEST( arr.size() == 3 ); - BOOST_REQUIRE( front == movable_value ); - BOOST_REQUIRE( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes - BOOST_REQUIRE( arr[1] == movable_value ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes + std::deque> q1; - std::move(arr)[1] = movable_value; -} + assign_elements_from_to(arr, q1); -template -void assign_elements_from_to(Array1D&& arr, std::deque>& dest) { // NOLINT(google-runtime-references) dest is mutated - std::copy(std::forward(arr).begin(), std::forward(arr).end(), std::back_inserter(dest)); -} + BOOST_TEST( arr[0] == movable_value ); -BOOST_AUTO_TEST_CASE(elements_rvalues_nomove) { - using movable_type = std::vector; - movable_type const movable_value(5., 99.0); // NOLINT(fuchsia-default-arguments-calls) + std::deque> q2; - multi::array arr = {movable_value, movable_value, movable_value}; - BOOST_REQUIRE( arr.size() == 3 ); + assign_elements_from_to(std::move(arr), q2); - std::deque> q1; + // BOOST_TEST( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes - assign_elements_from_to(arr, q1); + BOOST_TEST( q1 == q2 ); + } - BOOST_REQUIRE( arr[0] == movable_value ); + // BOOST_AUTO_TEST_CASE(elements_rvalues_assignment) + { + std::vector vec = {10, 20, 30}; // NOLINT(fuchsia-default-arguments-calls) - std::deque> q2; + std::move(vec) = std::vector{30, 40, 50}; // NOLINT(fuchsia-default-arguments-calls) - assign_elements_from_to(std::move(arr), q2); + // cppcheck-suppress accessMoved ; for testing purposes + std::move(vec)[1] = 990; // it compiles // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes -// BOOST_REQUIRE( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes + // cppcheck-suppress accessMoved ; for testing purposes + BOOST_TEST( vec[1] == 990 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes - BOOST_REQUIRE( q1 == q2 ); -} + multi::array arr1 = {10, 20, 30}; + multi::array const arr2 = {10, 20, 30}; + + std::move(arr1) = arr2; // this compiles TODO(correaa) should it? + } + + // BOOST_AUTO_TEST_CASE(range_2) + { + multi::array arr3({3, 4, 5}, 99); + multi::array const brr3({2, 2, 5}, 88); -BOOST_AUTO_TEST_CASE(elements_rvalues_assignment) { - std::vector vec = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) + // what(arr3, arr3({0, 2}, {0, 2})); + // what(arr3, arr3.range({0, 2}), arr3.paren_aux_({0, 2}), arr3({0, 2}), arr3({0, 2}, {0, 2})); + arr3({0, 2}, {0, 2}) = brr3; - std::move(vec) = std::vector{3.0, 4.0, 5.0}; // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr3[0][0][0] == 88 ); // should not compile + } + + { + multi::array const A2D({3, 3}, 11); + multi::array B2D({2, 2}, 22); + multi::array v1D(3, 33); + + using boost::multi::_; + v1D(_) = A2D(_, 0); // v1D() = A2D( _ , 0); + v1D(_) = A2D(0, _); // v1D() = A2D( 0 ) ; + B2D(_, _) = A2D({0, 2}, {0, 2}); // B2D() = A2D({0, 2}, {0, 2}); + } + { + auto A2D = multi::array{ + {1, 2}, + {3, 4} + }; + BOOST_TEST( A2D[1][1] == 4 ); + + A2D[1][1] = 44; - std::move(vec)[1] = 99.0; // it compiles // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing purposes + BOOST_TEST( A2D[1][1] == 44 ); // cppcheck-suppress knownConditionTrueFalse ; // test syntax - multi::array arr1 = {1.0, 2.0, 3.0}; - multi::array const arr2 = {1.0, 2.0, 3.0}; +#if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + BOOST_TEST(( A2D[1, 1] == 44 )); - std::move(arr1) = arr2; // this compiles TODO(correaa) should it? + A2D[1, 1] = 444; + BOOST_TEST(( A2D[1, 1] == 444 )); + + using boost::multi::_; + BOOST_TEST(( &A2D[_, 1][1] == &A2D[1, 1] )); +#endif + } + return boost::report_errors(); } diff --git a/test/element_moved.cpp b/test/element_moved.cpp new file mode 100644 index 000000000..0e0fd4fa9 --- /dev/null +++ b/test/element_moved.cpp @@ -0,0 +1,74 @@ +// Copyright 2024-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 10. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array, apply, array_types<>::ele... + +#include + +// IWYU pragma: no_include // for remove_reference<>::type +// IWYU pragma: no_include +// IWYU pragma: no_include +// IWYU pragma: no_include +// IWYU pragma: no_include // for fill_n +// IWYU pragma: no_include +// IWYU pragma: no_include +#include // for move, swap // IWYU pragma: keep +#include // for vector, operator==, vector<>::va... + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // explicit_move_subarray_vector_2d_assign + { + multi::array, 2> arrA({10, 10}, std::vector(5, {}, {})); + + BOOST_TEST( arrA[2][2].size() == 5 ); + { + using std::move; + multi::array, 2> arrB(arrA().element_moved()); // (arrA.extensions()); + // arrB = arrA().element_moved(); + + BOOST_TEST( arrA.size() == 10 ); + BOOST_TEST( arrB.size() == 10 ); + BOOST_TEST( arrA[2][2].empty() ); + BOOST_TEST( arrB[2][2].size() == 5 ); + } + } + + // explicit_move_subarray_vector_2d_ctor + // { + // multi::array, 2> arrA({10, 10}, std::vector(5)); + + // BOOST_TEST( arrA[2][2].size() == 5 ); + // { + // using std::move; + // multi::array, 2> arrB{arrA().element_moved()}; + + // BOOST_TEST( arrA.size() == 10 ); + // BOOST_TEST( arrB.size() == 10 ); + // BOOST_TEST( arrA[2][2].size() == 0 ); + // BOOST_TEST( arrB[2][2].size() == 5 ); + // } + // } + + // BOOST_AUTO_TEST_CASE(explicit_move_subarray_vector_1d) + // { + // multi::array, 1> arrA(10, std::vector(5)); + + // BOOST_TEST( arrA[2].size() == 5 ); + // { + // using std::move; + // multi::array, 1> arrB(arrA.extensions()); + // arrB() = arrA().element_moved(); + // // std::copy(arrA().element_moved().begin(), arrA().element_moved().end(), arrB.begin()); + + // BOOST_TEST( arrA.size() == 10 ); + // BOOST_TEST( arrB.size() == 10 ); + // BOOST_TEST( arrA[2].size() == 0 ); + // BOOST_TEST( arrB[2].size() == 5 ); + // } + // } + + return boost::report_errors(); +} diff --git a/test/element_transformed.cpp b/test/element_transformed.cpp index a0dd297dd..0fcce92d1 100644 --- a/test/element_transformed.cpp +++ b/test/element_transformed.cpp @@ -1,242 +1,307 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022-2023 Alfredo A. Correa +// Copyright 2022-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi element transformed" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#ifdef _MSC_VER +#pragma warning(disable : 4244) // warning C4244: 'initializing': conversion from '_Ty' to '_Ty', possible loss of data +#endif -#include +#include // for transform_ptr, array, subarray -#include -#include +#include + +// IWYU pragma: no_include // for copy // for GNU stdlib +// IWYU pragma: no_include // for declval // for GNU stdlib +#include // for complex, operator*, operator+ +#include // for declval, forward +#include // for vector + +template struct Conjd; // NOLINT(readability-identifier-naming) for testing + +struct Conj_t { // NOLINT(readability-identifier-naming) for testing + template constexpr auto operator()(ComplexRef&& zee) const noexcept { return Conjd{std::forward(zee)}; } + template constexpr auto operator()(Conjd const&) const = delete; + template constexpr auto operator()(Conjd&&) const = delete; + template constexpr auto operator()(Conjd&) const = delete; +}; +inline constexpr Conj_t Conj; + +template +struct Conjd { // NOLINT(readability-identifier-naming) for testing + using decay_type = decltype(+std::declval()); + + // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + // explicit constexpr operator decay_type() const {return std::conj(c_); } // NOSONAR(cpp:S1709) + + friend constexpr auto operator==(decay_type const& other, Conjd const& self) -> bool { return std::conj(self.c_) == other; } + friend constexpr auto operator!=(decay_type const& other, Conjd const& self) -> bool { return std::conj(self.c_) != other; } + + friend constexpr auto operator==(Conjd const& self, decay_type const& other) -> bool { return other == std::conj(self.c_); } + friend constexpr auto operator!=(Conjd const& self, decay_type const& other) -> bool { return other != std::conj(self.c_); } + + friend constexpr auto operator==(Conjd const& self, Conjd const& other) -> bool { return other.c_ == self.c_; } + friend constexpr auto operator!=(Conjd const& self, Conjd const& other) -> bool { return other.c_ != self.c_; } + + constexpr auto operator=(decay_type const& other) && -> Conjd& { + c_ = std::conj(other); + return *this; + } + + private: + constexpr explicit Conjd(ComplexRef& cee) : c_{cee} {} + ComplexRef& c_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) can be a reference + friend decltype(Conj); +}; namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_function_reference) { - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_function_reference) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit - multi::array arr = { 1.0 + 2.0*I, 3.0 + 4.0*I}; + multi::array arr = {1.0 + 2.0 * I, 3.0 + 4.0 * I}; - constexpr auto conj = static_cast(std::conj); + // conste xpr + auto const conj = static_cast(std::conj); - auto const& conjd_arr = arr.element_transformed(conj); - BOOST_REQUIRE( conjd_arr[0] == conj(arr[0]) ); - BOOST_REQUIRE( conjd_arr[1] == conj(arr[1]) ); + auto const& conjd_arr = arr.element_transformed(conj); + BOOST_TEST( conjd_arr[0] == conj(arr[0]) ); + BOOST_TEST( conjd_arr[1] == conj(arr[1]) ); -// Ac[0] = 5. + 4.*I; // this doesn't compile, good! - BOOST_REQUIRE( conjd_arr[0] == 1. - 2.*I ); + // Ac[0] = 5. + 4.*I; // this doesn't compile, good! + BOOST_TEST( conjd_arr[0] == 1.0 - 2.0*I ); - BOOST_TEST_REQUIRE( real(std::inner_product(arr.begin(), arr.end(), conjd_arr.begin(), complex{0.0, 0.0})) == std::norm(arr[0]) + std::norm(arr[1]) ); - BOOST_REQUIRE( imag(std::inner_product(arr.begin(), arr.end(), conjd_arr.begin(), complex{0.0, 0.0})) == 0. ); + // BOOST_REQUIRE_CLOSE(real(std::inner_product(arr.begin(), arr.end(), conjd_arr.begin(), complex{ 0.0, 0.0 })), std::norm(arr[0]) + std::norm(arr[1]), 1E-6); + // BOOST_REQUIRE_CLOSE(imag(std::inner_product(arr.begin(), arr.end(), conjd_arr.begin(), complex{ 0.0, 0.0 })), 0.0, 1E-6); - BOOST_TEST_REQUIRE( std::inner_product(arr.begin(), arr.end(), conjd_arr.begin(), complex{0.0, 0.0}) == std::norm(arr[0]) + std::norm(arr[1]) ); -} + // BOOST_TEST_REQUIRE( std::inner_product(arr.begin(), arr.end(), conjd_arr.begin(), complex{0.0, 0.0}) == std::norm(arr[0]) + std::norm(arr[1]) ); + } -BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_lambda) { - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit + // BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_lambda) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit - multi::array arr = { 1.0 + 2.0*I, 3.0 + 4.0*I}; + multi::array arr = {1.0 + 2.0 * I, 3.0 + 4.0 * I}; - // g++ -std=20 needs the transformation (lambda) to be noexcept - auto const& conjd_arr = arr.element_transformed([](auto const& cee) noexcept {return std::conj(cee);}); - BOOST_REQUIRE( conjd_arr[0] == std::conj(arr[0]) ); - BOOST_REQUIRE( conjd_arr[1] == std::conj(arr[1]) ); + // g++ -std=20 needs the transformation (lambda) to be noexcept + auto const& conjd_arr = arr.element_transformed([](auto const& cee) noexcept { return std::conj(cee); }); + BOOST_TEST( conjd_arr[0] == std::conj(arr[0]) ); + BOOST_TEST( conjd_arr[1] == std::conj(arr[1]) ); -// Ac[0] = 5. + 4.*I; // this doesn't compile, good! - BOOST_REQUIRE( conjd_arr[0] == 1.0 - 2.0*I ); -} + // Ac[0] = 5. + 4.*I; // this doesn't compile, good! + BOOST_TEST( conjd_arr[0] == 1.0 - 2.0*I ); + } -BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_lambda_with_const_return) { - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit + // BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_lambda_with_const_return) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit - multi::array arr = { 1.0 + 2.0*I, 3.0 + 4.0*I}; + multi::array arr = {1.0 + 2.0 * I, 3.0 + 4.0 * I}; - // g++ -std=20 needs the transformation (lambda) to be noexcept - // NOLINTNEXTLINE(readability-const-return-type) a way to disable assignment - auto&& conjd_arr = arr.element_transformed([](auto const& cee) noexcept -> auto const {return std::conj(cee);}); // `const` allows this idiom. it needs -Wno-nonportable-cfstrings and -Wignored-qualifiers in clang - BOOST_REQUIRE( conjd_arr[0] == std::conj(arr[0]) ); - BOOST_REQUIRE( conjd_arr[1] == std::conj(arr[1]) ); + // g++ -std=20 needs the transformation (lambda) to be noexcept + auto&& conjd_arr = arr.element_transformed([](auto const& cee) noexcept { return std::conj(cee); }); - // conjd_arr[0] = 5.0 + 4.0*I; // this doesn't compile, good! otherwise it would be misleading (see above) - BOOST_REQUIRE( conjd_arr[0] == 1.0 - 2.0*I ); -} + BOOST_TEST( conjd_arr[0] == std::conj(arr[0]) ); + BOOST_TEST( conjd_arr[1] == std::conj(arr[1]) ); -template struct Conjd; + // conjd_arr[0] = 5.0 + 4.0*I; // this compiles, but classes the implement operator= naively can be misleading here + BOOST_TEST( conjd_arr[0] == 1.0 - 2.0*I ); + } -constexpr struct Conj_t { // NOLINT(readability-identifier-naming) for testing - template constexpr auto operator()(ComplexRef&& zee) const {return Conjd{zee};} - template constexpr auto operator()(Conjd const&) const = delete; - template constexpr auto operator()(Conjd &&) const = delete; - template constexpr auto operator()(Conjd &) const = delete; -} Conj; + // BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_proxy) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit -template -struct Conjd { // NOLINT(readability-identifier-naming) for testing - using decay_type = decltype( + std::declval() ); + multi::array const arr = {1.0 + 2.0 * I, 3.0 + 4.0 * I}; - constexpr operator decay_type() const {return std::conj(c_);} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + auto const& conj_arr = arr.element_transformed(Conj); + BOOST_TEST( std::conj(arr[0]) == conj_arr[0] ); + BOOST_TEST( std::conj(arr[1]) == conj_arr[1] ); - friend constexpr auto operator==(decay_type const& other, Conjd const& self) -> bool {return std::conj(self.c_) == other;} - friend constexpr auto operator!=(decay_type const& other, Conjd const& self) -> bool {return std::conj(self.c_) != other;} + // Ac[0] = 5. + 4.*I; // not allowed, compile error, Ac is const + BOOST_TEST( conj_arr[0] == 1.0 - 2.0*I ); + } - friend constexpr auto operator==(Conjd const& self, decay_type const& other) -> bool {return other == std::conj(self.c_);} - friend constexpr auto operator!=(Conjd const& self, decay_type const& other) -> bool {return other != std::conj(self.c_);} + // BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_mutable_proxy) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit - friend constexpr auto operator==(Conjd const& self, Conjd const& other) -> bool {return other.c_ == self.c_;} - friend constexpr auto operator!=(Conjd const& self, Conjd const& other) -> bool {return other.c_ != self.c_;} + multi::array arr = {1.0 + 2.0 * I, 3.0 + 4.0 * I}; - constexpr auto operator=(decay_type const& other) && -> Conjd& {c_ = std::conj(other); return *this;} + auto&& conj_arr = arr.element_transformed(Conj); // NOLINT(readability-const-return-type) to disable assignment - private: - constexpr explicit Conjd(ComplexRef& cee) : c_{cee} {} - ComplexRef& c_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) can be a reference - friend decltype(Conj); -}; + BOOST_TEST( std::conj(arr[0]) == conj_arr[0] ); + BOOST_TEST( std::conj(arr[1]) == conj_arr[1] ); -BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_proxy) { - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit + conj_arr[0] = 5.0 + 4.0 * I; + BOOST_TEST( conj_arr[0] == 5.0 + 4.0*I ); + BOOST_TEST( arr[0] == 5.0 - 4.0*I ); + } - multi::array const arr = { 1.0 + 2.0*I, 3.0 + 4.0*I}; + // BOOST_AUTO_TEST_CASE(transform_ptr_single_value) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit - auto const& conj_arr = arr.element_transformed(Conj); - BOOST_REQUIRE( std::conj(arr[0]) == conj_arr[0] ); - BOOST_REQUIRE( std::conj(arr[1]) == conj_arr[1] ); + complex cee = 1.0 + 2.0 * I; -// Ac[0] = 5. + 4.*I; // not allowed, compile error, Ac is const - BOOST_REQUIRE( conj_arr[0] == 1.0 - 2.0*I ); -} + // NOLINTNEXTLINE(readability-const-return-type,clang-diagnostic-ignored-qualifiers) to prevent assignment + constexpr auto conj_ro = [](auto const& zee) noexcept { + return std::conj(zee); + }; // g++ -std=20 needs the transformation (lambda) to be noexcept -BOOST_AUTO_TEST_CASE(element_transformed_1D_conj_using_mutable_proxy) { - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit + multi::transform_ptr const conjd_ceeP{&cee, conj_ro}; + BOOST_TEST( *conjd_ceeP == std::conj(1.0 + 2.0*I) ); + } - multi::array arr = { 1.0 + 2.0*I, 3.0 + 4.0*I}; + // BOOST_AUTO_TEST_CASE(transform_ptr_1D_array) + { + using complex = std::complex; + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit - auto&& conj_arr = arr.element_transformed(Conj); // NOLINT(readability-const-return-type) to disable assignment + multi::array arr = {1.0 + 2.0 * I, 3.0 + 4.0 * I}; - BOOST_REQUIRE( std::conj(arr[0]) == conj_arr[0] ); - BOOST_REQUIRE( std::conj(arr[1]) == conj_arr[1] ); + // NOLINT(readability-const-return-type,clang-diagnostic-ignored-qualifiers) to prevent assignment + constexpr auto conj_ro = [](auto const& zee) noexcept { + return std::conj(zee); + }; // g++ -std=20 needs the transformation (lambda) to be noexcept - conj_arr[0] = 5.0 + 4.0*I; - BOOST_REQUIRE( conj_arr[0] == 5.0 + 4.0*I ); - BOOST_REQUIRE( arr[0] == 5.0 - 4.0*I ); -} + auto const& conjd_arr = arr.element_transformed(conj_ro); + BOOST_TEST( conjd_arr[0] == conj_ro(arr[0]) ); + BOOST_TEST( conjd_arr[1] == conj_ro(arr[1]) ); -BOOST_AUTO_TEST_CASE(transform_ptr_single_value) { - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit + // Ac[0] = 5. + 4.i; // doesn't compile thanks to the `auto const` in the `conj` def + } - complex cee = 1.0 + 2.0*I; + // BOOST_AUTO_TEST_CASE(arthur_odwyer_array_transform_int) + { + struct S { // NOLINT(readability-identifier-naming) + int a; + int b; + }; - // NOLINTNEXTLINE(readability-const-return-type,clang-diagnostic-ignored-qualifiers) to prevent assignment - constexpr auto conj_ro = [](auto const& zee) noexcept {return std::conj(zee);}; // g++ -std=20 needs the transformation (lambda) to be noexcept + multi::array arr({2}, S{}); - multi::transform_ptr const conjd_ceeP{&cee, conj_ro}; - BOOST_REQUIRE( *conjd_ceeP == std::conj(1.0 + 2.0*I) ); -} + auto&& ref = arr.element_transformed(&S::a); -BOOST_AUTO_TEST_CASE(transform_ptr_1D_array) { - using complex = std::complex; - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) I imaginary unit + ref[0] = 990; - multi::array arr = { 1.0 + 2.0*I, 3.0 + 4.0*I}; + BOOST_TEST( arr[0].a == 990 ); - // NOLINT(readability-const-return-type,clang-diagnostic-ignored-qualifiers) to prevent assignment - constexpr auto conj_ro = [](auto const& zee) noexcept {return std::conj(zee);}; // g++ -std=20 needs the transformation (lambda) to be noexcept + auto const& cref = arr.element_transformed(&S::a); + BOOST_TEST( cref[0] == 990 ); + // cr[0] = 99.; // compile error "assignment of read-only location" + } - auto const& conjd_arr = arr.element_transformed(conj_ro); - BOOST_REQUIRE( conjd_arr[0] == conj_ro(arr[0]) ); - BOOST_REQUIRE( conjd_arr[1] == conj_ro(arr[1]) ); + // BOOST_AUTO_TEST_CASE(arthur_odwyer_array_transform_int_array) + { + struct S { // NOLINT(readability-identifier-naming) + int a[10]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) testing + int b; + }; -// Ac[0] = 5. + 4.i; // doesn't compile thanks to the `auto const` in the `conj` def -} + multi::array vec({2}, S{}); -BOOST_AUTO_TEST_CASE(arthur_odwyer_array_transform_int) { - struct S { // NOLINT(readability-identifier-naming) - int a; - int b; - }; + auto&& ref = vec.element_transformed(&S::a); - multi::array arr({2}, S{}); - auto&& ref = arr.element_transformed(&S::a); - ref[0] = 99.0; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif - BOOST_REQUIRE( arr[0].a == 99.0 ); + ref[0][1] = 990; - auto const& cref = arr.element_transformed(&S::a); - BOOST_REQUIRE( cref[0] == 99.0 ); -// cr[0] = 99.; // compile error "assignment of read-only location" -} + BOOST_TEST( ref[0][1] == 990 ); -BOOST_AUTO_TEST_CASE(arthur_odwyer_array_transform_int_array) { - struct S { // NOLINT(readability-identifier-naming) - int a[10]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) testing - int b; - }; + BOOST_TEST( vec[0].a[1] == 990 ); - multi::array vec({2}, S{}); - auto&& ref = vec.element_transformed(&S::a); - ref[0][1] = 99.0; + auto const& cref = vec.element_transformed(&S::a); + BOOST_TEST( cref[0][1] == 990 ); + // cref[0][1] = 990; // compile error "assignment of read-only location" - BOOST_REQUIRE( ref[0][1] == 99.0 ); - BOOST_REQUIRE( vec[0].a[1] == 99.0 ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } - auto const& cref = vec.element_transformed(&S::a); - BOOST_REQUIRE( cref[0][1] == 99.0 ); -// cref[0][1] = 99.; // compile error "assignment of read-only location" -} + // BOOST_AUTO_TEST_CASE(indirect_transformed) + { + std::vector vec = {00, 11, 22, 33, 44, 55}; // std::vector NOLINT(fuchsia-default-arguments-calls) -BOOST_AUTO_TEST_CASE(indirect_transformed) { - std::vector vec = {0.0, 1.1, 2.2, 3.3, 4.4, 5.5}; // std::vector NOLINT(fuchsia-default-arguments-calls) + using index_t = std::vector::size_type; - using index_t = std::vector::size_type; + multi::array const arr = {4, 3, 2, 1, 0}; - multi::array const arr = {4, 3, 2, 1, 0}; + auto&& indirect_v = arr.element_transformed([&vec](index_t idx) noexcept -> int& { return vec[idx]; }); - auto&& indirect_v = arr.element_transformed([&vec](index_t idx) noexcept -> double& {return vec[idx];}); + BOOST_TEST( indirect_v[1] == vec[3] ); + BOOST_TEST( &indirect_v[1] == &vec[3] ); - BOOST_REQUIRE( indirect_v[1] == vec[3] ); - BOOST_REQUIRE( &indirect_v[1] == &vec[3] ); + indirect_v[1] = 990; + BOOST_TEST( vec[3] == 990 ); - indirect_v[1] = 99.0; - BOOST_REQUIRE( vec[3] == 99.0 ); + // for(auto&& elem : indirect_v) {elem = 88.;} + // std::fill(indirect_v.begin(), indirect_v.end(), 88.0); -// for(auto&& elem : indirect_v) {elem = 88.;} -// std::fill(indirect_v.begin(), indirect_v.end(), 88.0); - indirect_v.fill(88.0); - BOOST_REQUIRE( vec[3] == 88.0 ); +#ifndef _MSC_VER + indirect_v.fill(880); + BOOST_TEST( vec[3] == 880 ); - auto const& const_indirect_v = indirect_v; (void)const_indirect_v; -// const_indirect_v[1] = 999.; // does not compile, good! - BOOST_REQUIRE(const_indirect_v[3] == 88.0); -} + auto const& const_indirect_v = indirect_v; + (void)const_indirect_v; + // const_indirect_v[1] = 9990; // does not compile, good! + BOOST_TEST(const_indirect_v[3] == 880); +#endif + } + + // BOOST_AUTO_TEST_CASE(indirect_transformed_carray) + { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) testing legacy types + int carr[5][3] = { + { 00, 10, 20}, + {100, 110, 120}, + {200, 210, 220}, + {300, 310, 320}, + {400, 410, 420}, + }; + + using index_t = std::vector::size_type; + + multi::array const arr = {4, 3, 2, 1, 0}; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + auto&& indirect_v = arr.element_transformed([&carr](index_t idx) noexcept -> int(&)[3] { return carr[idx]; }); -BOOST_AUTO_TEST_CASE(indirect_transformed_carray) { - double carr[5][3] = { // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) testing legacy types - { 0.0, 1.0, 2.0}, - {10.0, 11.0, 12.0}, - {20.0, 21.0, 22.0}, - {30.0, 31.0, 32.0}, - {40.0, 41.0, 42.0} - }; + BOOST_TEST( &indirect_v[1][2] == &carr[3][2] ); + BOOST_TEST( indirect_v[1][2] == 320 ); - using index_t = std::vector::size_type; - multi::array const arr = {4, 3, 2, 1, 0}; + indirect_v[1][2] = 111110; + BOOST_TEST ( indirect_v[1][2] == 111110 ); - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - auto&& indirect_v = arr.element_transformed([&carr](index_t idx) noexcept -> double(&)[3] {return carr[idx];}); + auto const& const_indirect_v = indirect_v; - BOOST_REQUIRE( &indirect_v[1][2] == &carr[3][2] ); - BOOST_REQUIRE( indirect_v[1][2] == 32.0 ); + BOOST_TEST( const_indirect_v[1][2] == 111110 ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) testing legacy type - indirect_v[1][2] = 11111.0; - BOOST_TEST ( indirect_v[1][2] == 11111.0 ); + // const_indirect_v[1][2] = 999.0; // doesn't compile, good! - auto const& const_indirect_v = indirect_v; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } - BOOST_TEST( const_indirect_v[1][2] == 11111.0 ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) testing legacy type -// const_indirect_v[1][2] = 999.; // doesn't compile, good! + return boost::report_errors(); } diff --git a/test/execution_policy.disable_cpp b/test/execution_policy.disable_cpp new file mode 100644 index 000000000..df6c6f038 --- /dev/null +++ b/test/execution_policy.disable_cpp @@ -0,0 +1,321 @@ +// Copyright 2024 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// #include + +#include // NOLINT(build/c++11) +#include +#include +#include +#include // NOLINT(build/c++11) + +#if defined(TBB_FOUND) || (defined(__GNUC__) && !defined(__clang__) && !defined(__NVCOMPILER) && (__GLIBCXX__ >= 20190502)) +#if !defined(__NVCC__) && !(defined(__clang__) && defined(__CUDA__)) +#if !defined(PSTL_USE_PARALLEL_POLICIES) || !(PSTL_USE_PARALLEL_POLICIES == 0) +#include +#endif +#endif +#endif + +// Suppress warnings from boost.test +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifndef BOOST_TEST_MODULE +# define BOOST_TEST_MAIN +#endif + +#include + +namespace multi = boost::multi; + +BOOST_AUTO_TEST_CASE(dummy_test) { + multi::static_array const arr(multi::extensions_t<1>{multi::iextension{10}}, 1.0); + BOOST_REQUIRE( arr[0] == 1.0 ); +} + +// BOOST_AUTO_TEST_CASE(multi_par_construct_1d) { +// multi::static_array const arr(multi::extensions_t<1>{multi::iextension{10}}, 1.0); +// // multi::static_array arr(multi::array::extensions_type{10}, 1.0); +// BOOST_REQUIRE( size(arr) == 10 ); +// BOOST_REQUIRE( arr[1] == 1.0 ); + +// #if defined(TBB_FOUND) || (defined(__GNUC__) && !defined(__clang__) && !defined(__NVCOMPILER) && (__GLIBCXX__ >= 20190502)) +// #if !defined(__NVCC__) && !(defined(__clang__) && defined(__CUDA__)) +// #if !defined(PSTL_USE_PARALLEL_POLICIES) || !(PSTL_USE_PARALLEL_POLICIES == 0) +// multi::static_array const arr2(std::execution::par, arr); + +// BOOST_REQUIRE( arr2 == arr ); +// #endif +// #endif +// #endif +// } + +// BOOST_AUTO_TEST_CASE(copy_par_1d) { +// multi::array const arr(1000000, 1.0); +// BOOST_REQUIRE( size(arr) == 1000000 ); +// BOOST_REQUIRE( arr[1] == 1.0 ); + +// #if defined(TBB_FOUND) || (defined(__GNUC__) && !defined(__clang__) && !defined(__NVCOMPILER) && (__GLIBCXX__ >= 20190502)) +// #if !defined(__NVCC__) && !(defined(__clang__) && defined(__CUDA__)) +// #if !defined(PSTL_USE_PARALLEL_POLICIES) || !(PSTL_USE_PARALLEL_POLICIES == 0) +// #if defined(__cpp_lib_execution) && (__cpp_lib_execution >= 201603L) +// multi::array arr2(arr.extensions()); + +// std::copy(std::execution::par, arr.begin(), arr.end(), arr2.begin()); + +// BOOST_REQUIRE( arr2 == arr ); +// #endif +// #endif +// #endif +// #endif +// } + +// class watch // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) // NOSONAR +// : private std::chrono::high_resolution_clock { +// std::string label_; +// time_point start_ = now(); + +// public: +// explicit watch(std::string label) : label_{std::move(label)} {} + +// ~watch() { +// std::cerr << label_ << ": " << std::chrono::duration(now() - start_).count() << " sec" << std::endl; +// } +// }; + +class slow_assign { + double val_; + + public: + constexpr explicit slow_assign(double const& vv) noexcept : val_{vv} {} + ~slow_assign() = default; + + slow_assign(slow_assign&& other) noexcept = default; + + slow_assign(slow_assign const& other) : val_{other.val_} { + using namespace std::chrono_literals; // NOLINT(build/namespaces) + std::this_thread::sleep_for(10ms); + } + auto operator=(slow_assign const& other) -> slow_assign& { + if(this == &other) { + return *this; + } + val_ = other.val_; + using namespace std::chrono_literals; // NOLINT(build/namespaces) + std::this_thread::sleep_for(10ms); + return *this; + } + auto operator=(slow_assign&& other) noexcept -> slow_assign& = default; + + auto operator==(slow_assign const& other) const noexcept { return val_ == other.val_; } + auto operator!=(slow_assign const& other) const noexcept { return val_ != other.val_; } +}; + +#if defined(TBB_FOUND) || (defined(__GNUC__) && !defined(__clang__) && !defined(__NVCOMPILER) && (__GLIBCXX__ >= 20190502)) +#if !defined(__NVCC__) && !(defined(__clang__) && defined(__CUDA__)) +#if !defined(PSTL_USE_PARALLEL_POLICIES) || !(PSTL_USE_PARALLEL_POLICIES == 0) +#if defined(__cpp_lib_execution) && (__cpp_lib_execution >= 201603L) + +// BOOST_AUTO_TEST_CASE(reduce_row_random) { +// std::random_device r; + +// std::seed_seq seed2{r(), r(), r(), r(), r(), r(), r(), r()}; +// std::mt19937 e2(seed2); // NOLINT(cpp:S2245) +// std::normal_distribution<> normal_dist{}; + +// multi::array arr({10000, 10000}); +// std::generate(arr.elements().begin(), arr.elements().end(), [&]() { return normal_dist(e2); }); + +// { +// multi::array vec(size(arr)); +// watch const _("reduce"); // NOLINT(fuchsia-default-arguments-calls) +// std::transform(arr.begin(), arr.end(), vec.begin(), [](auto const& row) {return std::reduce(row.begin(), row.end());} ); +// } + +// { +// multi::array vec(size(arr)); +// watch const _("par reduce"); // NOLINT(fuchsia-default-arguments-calls) +// std::transform(std::execution::par, arr.begin(), arr.end(), vec.begin(), [](auto const& row) {return std::reduce(row.begin(), row.end());} ); +// } + +// { +// multi::array vec(size(arr)); +// watch const _("par reduce"); // NOLINT(fuchsia-default-arguments-calls) +// std::transform(arr.begin(), arr.end(), vec.begin(), [](auto const& row) {return std::reduce(std::execution::par_unseq, row.begin(), row.end());} ); +// } +// } + +// BOOST_AUTO_TEST_CASE(sort_random) { +// std::random_device r; + +// std::seed_seq seed2{r(), r(), r(), r(), r(), r(), r(), r()}; +// std::mt19937 e2(seed2); // NOLINT(cpp:S2245) +// std::normal_distribution<> normal_dist{}; + +// multi::array arr({10000, 10000}, 0.0); +// std::generate(arr.elements().begin(), arr.elements().end(), [&]() { return normal_dist(e2); }); + +// auto arr_seq = arr; +// { +// watch const _("sort"); // NOLINT(fuchsia-default-arguments-calls) +// std::sort(arr_seq.begin(), arr_seq.end()); +// } + +// auto arr_par = arr; +// { +// watch const _("par sort"); // NOLINT(fuchsia-default-arguments-calls) +// std::sort(std::execution::par, arr_par.begin(), arr_par.end()); +// } + +// BOOST_REQUIRE( arr_seq == arr_par ); +// } + +// using T = slow_assign; +// auto const nelem = 8; + +// BOOST_AUTO_TEST_CASE(timing_copy_par_1d) { +// T const val{1.0}; +// T const val2{99.9}; + +// multi::array const arr(nelem, val); +// BOOST_REQUIRE( size(arr) == nelem ); +// BOOST_REQUIRE( arr[1] == val ); + +// { +// multi::array arr2(arr.extensions(), val2); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// { +// watch const _("normal copy"); // NOLINT(fuchsia-default-arguments-calls) +// std::copy(arr.begin(), arr.end(), arr2.begin()); +// } +// BOOST_REQUIRE( arr2 == arr ); +// } +// { +// multi::array arr2(arr.extensions(), val2); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// { +// watch const _("par copy"); // NOLINT(fuchsia-default-arguments-calls) +// std::copy(std::execution::par, arr.begin(), arr.end(), arr2.begin()); +// } +// BOOST_REQUIRE( arr2 == arr ); +// } +// } + +// BOOST_AUTO_TEST_CASE(timing_copy_par_2d_warm) { +// T const val{1.0}; + +// multi::array const arr({8, nelem / 8}, val); +// BOOST_REQUIRE( arr.num_elements() == nelem ); +// BOOST_REQUIRE( arr[1][1] == val ); +// } + +// BOOST_AUTO_TEST_CASE(timing_copy_par_2d) { +// for(auto factor : {8, 4, 2}) { +// T const val{1.0}; +// T const val2{99.9}; + +// multi::array const arr({factor, nelem / factor}, val); +// BOOST_REQUIRE( arr.num_elements() == nelem ); +// BOOST_REQUIRE( arr[1][1] == val ); + +// { +// multi::array arr2(arr.extensions(), val2); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// { +// watch const _("normal copy"); // NOLINT(fuchsia-default-arguments-calls) +// std::copy(arr.begin(), arr.end(), arr2.begin()); +// } +// BOOST_REQUIRE( arr2 == arr ); +// } +// { +// multi::array arr2(arr.extensions(), val2); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// { +// watch const _("par copy"); // NOLINT(fuchsia-default-arguments-calls) +// std::copy(std::execution::par, arr.begin(), arr.end(), arr2.begin()); +// } +// BOOST_REQUIRE( arr2 == arr ); +// } +// } +// } + +// BOOST_AUTO_TEST_CASE(timing_copy_par_2d_ultra_skinny) { +// T const val{1.0}; +// T const val2{99.9}; + +// multi::array const arr({2, nelem / 2}, val); +// BOOST_REQUIRE( arr.num_elements() == nelem ); +// BOOST_REQUIRE( arr[1][1] == val ); + +// { +// multi::array arr2(arr.extensions(), val2); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// { +// watch const _("~copy"); // NOLINT(fuchsia-default-arguments-calls) +// std::copy((~arr).begin(), (~arr).end(), (~arr2).begin()); +// } +// BOOST_REQUIRE( arr2 == arr ); +// } +// { +// multi::array arr2(arr.extensions(), val2); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// { +// watch const _("~par copy"); // NOLINT(fuchsia-default-arguments-calls) +// std::copy(std::execution::par, (~arr).begin(), (~arr).end(), (~arr2).begin()); +// } +// BOOST_REQUIRE( arr2 == arr ); +// } +// { +// multi::array arr2(arr.extensions(), val2); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// { +// watch const _("elements copy"); // NOLINT(fuchsia-default-arguments-calls) +// std::copy(arr.elements().begin(), arr.elements().end(), arr2.elements().begin()); +// } +// BOOST_REQUIRE( arr2 == arr ); +// } +// { +// multi::array arr2(arr.extensions(), val2); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// { +// watch const _("par elements copy"); // NOLINT(fuchsia-default-arguments-calls) +// std::copy(std::execution::par, arr.elements().begin(), arr.elements().end(), arr2.elements().begin()); +// } +// BOOST_REQUIRE( arr2 == arr ); +// } +// { +// { +// watch const _("constructor"); // NOLINT(fuchsia-default-arguments-calls) +// multi::array arr2(arr); // same as ...= arr; +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// BOOST_REQUIRE( arr2 == arr ); +// arr2.clear(); +// } +// } +// { +// { +// watch const _("par constructor"); // NOLINT(fuchsia-default-arguments-calls) +// multi::array const arr2(std::execution::par, arr); +// BOOST_REQUIRE( arr2.num_elements() == arr.num_elements() ); +// BOOST_REQUIRE( arr2 == arr ); +// } +// } +// } +#endif +#endif +#endif +#endif diff --git a/test/extensions.cpp b/test/extensions.cpp new file mode 100644 index 000000000..8ea4bad6c --- /dev/null +++ b/test/extensions.cpp @@ -0,0 +1,627 @@ +// Copyright 2021-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#include // IWYU pragma: keep + +#include // IWYU pragma: keep // for std::equal +#include // IWYU pragma: keep + +#if defined(__cplusplus) && (__cplusplus >= 202002L) && __has_include() +#if !defined(__clang_major__) || (__clang_major__ != 16) +#include // for totally_ordered +#include // IWYU pragma: keep +#endif +#endif + +#include // IWYU pragma: keep +#include // for std::is_same_v +// IWYU pragma: no_include // for get, iwyu bug + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(bugprone-exception-escape,readability-function-cognitive-complexity) + auto A2D = multi::array({5, 7}, 1); + auto const A2Dx = A2D.extension(); + + BOOST_TEST( &A2D() == &A2D(A2Dx) ); + + auto const A2Dxs = A2D.extensions(); + + using std::get; + + BOOST_TEST( get<0>(A2Dxs[1][2]) == 1 ); + BOOST_TEST( get<1>(A2Dxs[1][2]) == 2 ); + + BOOST_TEST( get<0>(A2Dxs) == A2Dx ); + BOOST_TEST( get<1>(A2Dxs) == A2D[0].extension() ); + + BOOST_TEST( &A2D() == &A2D(get<0>(A2D.extensions()), get<1>(A2D.extensions())) ); + BOOST_TEST( &A2D() == &std::apply(A2D, A2Dxs) ); + + BOOST_TEST( A2Dxs.size() == A2D.size() ); + BOOST_TEST( A2Dxs.sizes() == A2D.sizes() ); + + auto const [ni, nj] = A2Dxs.sizes(); + for(int i = 0; i != ni; ++i) { // NOLINT(altera-unroll-loops) + for(int j = 0; j != nj; ++j) { // NOLINT(altera-unroll-loops) + auto const [first, second] = A2Dxs[i][j]; + BOOST_TEST( first == i ); + BOOST_TEST( second == j ); + } + } + + auto const [is, js] = A2Dxs; + for(auto const i : is) { // NOLINT(altera-unroll-loops) + for(auto const j : js) { // NOLINT(altera-unroll-loops) + auto const [first, second] = A2Dxs[i][j]; + BOOST_TEST( first == i ); + BOOST_TEST( second == j ); + } + } + + BOOST_TEST( get<0>(A2Dxs).size() == 5 ); + + // auto it2d = A2Dxs.elements().begin(); (void)it2d; + + multi::array const A1D({37}, 1); + BOOST_TEST( A1D.size() == 37 ); + BOOST_TEST( A1D.num_elements() == 37 ); + BOOST_TEST( A1D.extensions().num_elements() == 37 ); + + BOOST_TEST( A1D.extensions().elements().size() == A1D.extensions().num_elements() ); + { + auto it = A1D.extensions().elements().begin(); + BOOST_TEST( get<0>(*it) == 0 ); + ++it; + BOOST_TEST( get<0>(*it) == 1 ); + + it = A1D.extensions().elements().end(); + --it; + BOOST_TEST( get<0>(*it) == 36 ); + } + { + auto x1d = multi::extensions_t<1>(3); + + BOOST_TEST( multi::extensions_t<1>(3) == multi::extensions_t(3) ); + + auto it = x1d.elements().begin(); + BOOST_TEST( get<0>(*it) == 0 ); + + ++it; + BOOST_TEST( get<0>(*it) == 1 ); + + ++it; + BOOST_TEST( get<0>(*it) == 2 ); + + ++it; + BOOST_TEST( it == x1d.elements().end() ); + + --it; + BOOST_TEST( get<0>(*it) == 2 ); + + --it; + BOOST_TEST( get<0>(*it) == 1 ); + + --it; + BOOST_TEST( get<0>(*it) == 0 ); + BOOST_TEST( it == x1d.elements().begin() ); + + BOOST_TEST( x1d.elements().begin() != x1d.elements().end() ); + BOOST_TEST( !(x1d.elements().begin() == x1d.elements().end()) ); + + BOOST_TEST( x1d.elements().begin() < x1d.elements().end() ); + BOOST_TEST( x1d.elements().begin() <= x1d.elements().end() ); + BOOST_TEST( x1d.elements().begin() <= x1d.elements().begin() ); + } + { + auto x1d = multi::extensions_t<1>(3); + + auto it = x1d.elements().begin(); + BOOST_TEST( get<0>(*it) == 0 ); + + ++it; + BOOST_TEST( get<0>(*it) == 1 ); + + it += 2; + + BOOST_TEST( it == x1d.elements().end() ); + + it -= 3; + BOOST_TEST( get<0>(*it) == 0 ); + BOOST_TEST( it == x1d.elements().begin() ); + + BOOST_TEST( x1d.elements().end() - x1d.elements().begin() == 3 ); + } + { + multi::extensions_t<2> const x2d({4, 3}); + + BOOST_TEST( multi::extensions_t<2>(4, 3) == multi::extensions_t(4, 3) ); + + auto ll = [](auto xx, auto yy) { + return xx + yy; + }; + multi::f_extensions_t<2, decltype(ll)> const x2df({4, 2}, ll); + (void)x2df; + auto val = x2df[3][1]; + BOOST_TEST(val == 4); + + auto elems = x2df.elements(); + BOOST_TEST( elems[7] == 4 ); + BOOST_TEST( *(x2df.elements().begin() + 1) == 1 + 0 ); + + // BOOST_TEST( *(*(x2df.begin()).begin()) == 0 ) + + // multi::detail::what(x2df[1]); + // std::cout << x2df[1][2] << std::endl; + + // auto x2d_trd = x2d.element_transformed([](auto is) { using std::get; return get<0>(is) + get<1>(is); }); + + BOOST_TEST( x2d.elements().end() - x2d.elements().begin() == 12 ); + + auto it = x2d.elements().begin(); + + BOOST_TEST( it == x2d.elements().begin() ); + + using std::get; + BOOST_TEST( 0 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + ++it; + BOOST_TEST( 0 == get<0>(*it) ); + BOOST_TEST( 1 == get<1>(*it) ); + + ++it; + BOOST_TEST( 0 == get<0>(*it) ); + BOOST_TEST( 2 == get<1>(*it) ); + + BOOST_TEST( it - x2d.elements().begin() == 2 ); + + ++it; + BOOST_TEST( 1 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + ++it; + BOOST_TEST( 1 == get<0>(*it) ); + BOOST_TEST( 1 == get<1>(*it) ); + + ++it; + BOOST_TEST( 1 == get<0>(*it) ); + BOOST_TEST( 2 == get<1>(*it) ); + + BOOST_TEST( it - x2d.elements().begin() == 5 ); + BOOST_TEST( x2d.elements().begin() - it == -5 ); + BOOST_TEST( x2d.elements().end() - it == 7 ); + BOOST_TEST( x2d.elements().end() - x2d.elements().begin() == 12 ); + + ++it; + BOOST_TEST( 2 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + ++it; + BOOST_TEST( 2 == get<0>(*it) ); + BOOST_TEST( 1 == get<1>(*it) ); + + ++it; + BOOST_TEST( 2 == get<0>(*it) ); + BOOST_TEST( 2 == get<1>(*it) ); + + ++it; + BOOST_TEST( 3 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + ++it; + BOOST_TEST( 3 == get<0>(*it) ); + BOOST_TEST( 1 == get<1>(*it) ); + + ++it; + BOOST_TEST( 3 == get<0>(*it) ); + BOOST_TEST( 2 == get<1>(*it) ); + + ++it; + BOOST_TEST( it == x2d.elements().end() ); + + --it; + BOOST_TEST( 3 == get<0>(*it) ); + BOOST_TEST( 2 == get<1>(*it) ); + + --it; + BOOST_TEST( 3 == get<0>(*it) ); + BOOST_TEST( 1 == get<1>(*it) ); + + --it; + BOOST_TEST( 3 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + --it; + BOOST_TEST( 2 == get<0>(*it) ); + BOOST_TEST( 2 == get<1>(*it) ); + + --it; + BOOST_TEST( 2 == get<0>(*it) ); + BOOST_TEST( 1 == get<1>(*it) ); + + --it; + BOOST_TEST( 2 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + --it; + BOOST_TEST( 1 == get<0>(*it) ); + BOOST_TEST( 2 == get<1>(*it) ); + + --it; + BOOST_TEST( 1 == get<0>(*it) ); + BOOST_TEST( 1 == get<1>(*it) ); + + --it; + BOOST_TEST( 1 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + --it; + BOOST_TEST( 0 == get<0>(*it) ); + BOOST_TEST( 2 == get<1>(*it) ); + + --it; + BOOST_TEST( 0 == get<0>(*it) ); + BOOST_TEST( 1 == get<1>(*it) ); + + --it; + BOOST_TEST( 0 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + BOOST_TEST( it == x2d.elements().begin() ); + } + { + multi::extensions_t<2> const x2d({4, 3}); + + // auto it2d = x2d.begin(); + + auto it = x2d.elements().begin(); + + BOOST_TEST( it == x2d.elements().begin() ); + + using std::get; + BOOST_TEST( 0 == get<0>(*it) ); + BOOST_TEST( 0 == get<1>(*it) ); + + BOOST_TEST( 0 == get<0>(*(it + 2)) ); + BOOST_TEST( 2 == get<1>(*(it + 2)) ); + + BOOST_TEST( 1 == get<0>(*(it + 5)) ); + BOOST_TEST( 2 == get<1>(*(it + 5)) ); + + auto const it2 = it + 5; + auto const it22 = it - (-5); + BOOST_TEST( it2 == it22 ); + + BOOST_TEST( 1 == get<0>(*(it2)) ); + BOOST_TEST( 2 == get<1>(*(it2)) ); + + BOOST_TEST( 1 == get<0>(*(it2-1)) ); + BOOST_TEST( 1 == get<1>(*(it2-1)) ); + + BOOST_TEST( 1 == get<0>(*(it2-2)) ); + BOOST_TEST( 0 == get<1>(*(it2-2)) ); + + auto const it3 = it2 - 5; + auto const it33 = it2 + (-5); + BOOST_TEST( it3 == it33 ); + + BOOST_TEST( it3 == it ); + } + { + multi::array const arr(10); + + auto xn = decltype(arr.extension())(10); + BOOST_TEST( xn. size() == 10 ); + + multi::extension_t const xn2(10); + BOOST_TEST( xn2.size() == 10 ); + + xn = xn2; + + multi::detail::extensions const xns2{xn2}; + using std::get; + BOOST_TEST( get<0>(xns2) == xn2 ); + + multi::detail::extensions const xns2d{xn2, xn2}; + auto [xns2d_a, xns2d_b] = xns2d; + + BOOST_TEST( xns2d_a == xn2 ); + BOOST_TEST( xns2d_b == xn2 ); + + multi::extensions_t<2> const met2{xns2d}; + + multi::layout_t<2> const lyt(met2); + multi::layout_t<2> const lyt_2(xns2d); + + BOOST_TEST( lyt == lyt_2 ); + + // multi::array const arr2({xn2}); + } + { + auto const x2df = [](auto x, auto y) { return x + y; } ^ multi::extensions_t<2>(3, 4); + + // boost::multi::f_extensions_t<2, decltype(ll)> x2df(multi::extensions_t<2>(3, 4), ll); + BOOST_TEST( x2df.elements()[0] == 0 ); + BOOST_TEST( x2df.elements()[1] == 1 ); + BOOST_TEST( x2df.elements()[2] == 2 ); + BOOST_TEST( x2df.elements()[3] == 3 ); + BOOST_TEST( x2df.elements()[4] == 1 ); + BOOST_TEST( x2df.elements()[5] == 2 ); + + BOOST_TEST( x2df[2][1] == 2 + 1 ); + + multi::array const arr2df = [](auto x, auto y) { return x + y; } ^ multi::extensions_t<2>(3, 4); + + BOOST_TEST( arr2df(2, 1) == 2 + 1 ); + BOOST_TEST( arr2df[2][1] == 2 + 1 ); + + BOOST_TEST(std::equal( + arr2df.elements().begin(), arr2df.elements().end(), + ([](auto x, auto y) { return x + y; } ^ multi::extensions_t<2>(3, 4)).elements().begin() + )); + + BOOST_TEST(std::equal( + arr2df.elements().begin(), arr2df.elements().end(), + (multi::extensions_t<2>(3, 4)->*[](auto x, auto y) { return x + y; }).elements().begin() + )); + + BOOST_TEST( arr2df.elements().begin() != arr2df.elements().end() ); + BOOST_TEST( !(arr2df.elements().begin() == arr2df.elements().end()) ); + + BOOST_TEST( arr2df[2][1] == ([](auto x, auto y) { return x + y; } ^ multi::extensions_t<2>(3, 4))[2][1] ); + + BOOST_TEST( arr2df[2][1] == ([](auto x, auto y) { return x + y; } ^ multi::extensions_t(3, 4))[2][1] ); + BOOST_TEST( + arr2df[2][1] + == multi::extensions_t<2>(3, 4).element_transformed( [](auto const& idxs) { using std::get; return get<0>(idxs) + get<1>(idxs); })[2][1] + ); + BOOST_TEST( + arr2df[2][1] + == multi::extensions_t<2>(3, 4).element_transformed( [](auto idxs) {auto [xx, yy] = idxs; return xx + yy; })[2][1] + ); + } + { + multi::extensions_t<3> const xs{3, 4, 5}; + + BOOST_TEST(( multi::extensions_t<3>{3, 4, 5} == multi::extensions_t(3, 4, 5) )); + + BOOST_TEST( xs.sub() == multi::extensions_t<2>(4, 5) ); + static_assert(std::is_same_v::element>); + } + { + multi::array const arr({3, 4}); + + auto const& xs = arr.extensions(); + + using std::get; + BOOST_TEST( get<0>(xs[0][0]) == 0 ); + BOOST_TEST( get<1>(xs[0][0]) == 0 ); + + BOOST_TEST( xs.begin() != xs.end() ); + BOOST_TEST( !(xs.begin() == xs.end()) ); + + BOOST_TEST( xs[0] == xs[0] ); + BOOST_TEST( xs[0] != xs[1] ); + + BOOST_TEST( xs[0] == *xs.begin() ); + BOOST_TEST( xs[1] == *(xs.begin() + 1) ); + + auto it = xs.begin(); + ++it; + BOOST_TEST( *it == xs[1] ); + + auto const& values = [](auto ii, auto jj) { return ii + jj; } ^ arr.extensions(); + + BOOST_TEST( values.dimensionality == 2 ); + BOOST_TEST( values.extensions() == arr.extensions() ); + BOOST_TEST( *values.elements().begin() == 0 ); + BOOST_TEST( values.elements().begin() < values.elements().end() ); + BOOST_TEST( values.elements().begin() != values.elements().end() ); + BOOST_TEST( values[0][0] == 0 ); + BOOST_TEST( values.begin() != values.end() ); + + { + auto arr2 = multi::array(arr.extensions()); + + arr2.elements() = values.elements(); + BOOST_TEST( std::equal(arr2.elements().begin(), arr2.elements().end(), values.elements().begin(), values.elements().end()) ); + } + { + auto arr2 = multi::array(arr.extensions()); + + arr2() = values; + BOOST_TEST( std::equal(arr2.elements().begin(), arr2.elements().end(), values.elements().begin(), values.elements().end()) ); + } + { + auto arr2 = multi::array(arr.extensions()); + + arr2 = values; + BOOST_TEST( std::equal(arr2.elements().begin(), arr2.elements().end(), values.elements().begin(), values.elements().end()) ); + } + +#ifdef __cpp_deduction_guides + { + multi::array const arr_gold = values; + multi::array const arr2 = values; + BOOST_TEST( arr_gold == arr2 ); + } +#endif + } + { + auto xs1D = multi::extensions_t(10); + BOOST_TEST( xs1D.size() == 10 ); + using std::get; + BOOST_TEST( get<0>(xs1D[3]) == 3 ); + + BOOST_TEST( xs1D.begin() != xs1D.end() ); + BOOST_TEST( !(xs1D.begin() == xs1D.end()) ); + BOOST_TEST( xs1D.begin() + 10 == xs1D.end() ); + BOOST_TEST( xs1D.begin() == xs1D.end() - 10 ); + + BOOST_TEST( *(xs1D.begin() + 3) == xs1D[3] ); + +#ifdef __NVCC__ // nvcc gets confused with inline lambdas + auto fun = [](auto ii) noexcept { return ii * ii; }; + auto v1D = fun ^ multi::extensions_t(10); +#else + auto v1D = [](auto ii) noexcept { return ii * ii; } ^ multi::extensions_t(10); +#endif + + BOOST_TEST( v1D.size() == 10 ); + BOOST_TEST( v1D[4] == 16 ); + + BOOST_TEST( v1D.elements().size() == 10 ); + BOOST_TEST( v1D.elements()[4] == v1D[4] ); + +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) && !defined(_MSC_VER) +#if !defined(__clang_major__) || (__clang_major__ != 16) + static_assert(std::is_trivially_default_constructible_v::iterator>); + static_assert(std::random_access_iterator::iterator>); + static_assert(std::ranges::random_access_range); + + BOOST_TEST( xs1D.begin() == std::ranges::begin(xs1D) ); + BOOST_TEST( xs1D.end() == std::ranges::end(xs1D) ); + + auto xs1Dr = xs1D | std::ranges::views::reverse; + + BOOST_TEST( *xs1Dr.begin() == 9 ); + BOOST_TEST( *(xs1Dr.end() - 1) == 0 ); + + BOOST_TEST( xs1Dr[9] == xs1D[0] ); + BOOST_TEST( xs1Dr[0] == xs1D[9] ); + + // auto xs1D_elements = xs1D.elements(); + BOOST_TEST( xs1D.elements().begin() == std::ranges::begin(xs1D.elements()) ); + + static_assert(std::input_or_output_iterator); + + BOOST_TEST( std::ranges::begin(v1D) == v1D.begin() ); + BOOST_TEST( std::ranges::end(v1D) == v1D.end() ); + + static_assert(std::totally_ordered); + static_assert(std::random_access_iterator); + + auto v1Dr = v1D | std::views::reverse; + BOOST_TEST( v1Dr[0] == v1D[9] ); + BOOST_TEST( v1Dr[9] == v1D[0] ); +#endif +#endif + } + { + auto xs2D = multi::extensions_t<2>(5, 7); + BOOST_TEST( xs2D.size() == 5 ); + + using std::get; + BOOST_TEST( get<0>(xs2D[3][2]) == 3 ); + BOOST_TEST( get<1>(xs2D[3][2]) == 2 ); + + BOOST_TEST( xs2D.begin() != xs2D.end() ); + BOOST_TEST( !(xs2D.begin() == xs2D.end()) ); + BOOST_TEST( xs2D.begin() + xs2D.size() == xs2D.end() ); + BOOST_TEST( xs2D.begin() == xs2D.end() - xs2D.size() ); + + BOOST_TEST( *(xs2D.begin() + 3) == xs2D[3] ); + + // auto it = xs2D.begin(); + // multi::detail::what(*it); + + auto xs3D = multi::extensions_t<3>(5, 7, 21); + BOOST_TEST( xs3D.size() == 5 ); + // multi::detail::what(*xs3D.begin()); + +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) && !defined(_MSC_VER) +#if !defined(__clang_major__) || (__clang_major__ != 16) + using xs2D_iterator = multi::extensions_t<2>::iterator; + + static_assert(std::is_trivially_default_constructible_v>); + static_assert(std::is_trivially_default_constructible_v>); + + static_assert(std::is_trivially_default_constructible_v>); + static_assert(std::is_trivially_default_constructible_v>); + + static_assert(std::is_trivially_default_constructible_v::base_>); + static_assert(std::is_trivially_default_constructible_v>); + static_assert(std::is_trivially_default_constructible_v::iterator>); + + static_assert(std::is_constructible_v); + static_assert(std::constructible_from); + static_assert(std::default_initializable::iterator>); + static_assert(std::semiregular::iterator>); + static_assert(std::regular::iterator>); + static_assert(std::incrementable::iterator>); + + static_assert(std::weakly_incrementable::iterator>); + static_assert(std::input_iterator::iterator>); + static_assert(std::forward_iterator::iterator>); + static_assert(std::bidirectional_iterator::iterator>); + static_assert(std::random_access_iterator::iterator>); + static_assert(std::ranges::random_access_range>); + + BOOST_TEST( xs2D.begin() == std::ranges::begin(xs2D) ); + BOOST_TEST( xs2D.end() == std::ranges::end(xs2D) ); + + auto xs2Dr = xs2D | std::ranges::views::reverse; + + BOOST_TEST( *xs2Dr.begin() == *(xs2D.end() - 1) ); + BOOST_TEST( *(xs2Dr.end() - 1) == *(xs2D.begin()) ); + + BOOST_TEST( xs2Dr[xs2D.size() - 1] == xs2D[0] ); + BOOST_TEST( xs2Dr[0] == xs2D[xs2D.size() - 1] ); +#endif +#endif + } + { + auto v2D = [](auto ii, auto jj) { return (ii * ii) + (jj * jj); } ^ multi::extensions_t<2>(3, 5); + BOOST_TEST( v2D[2][3] == (2*2) + (3*3) ); + // auto front = *v2D.begin(); + +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) && !defined(_MSC_VER) +#if !defined(__clang_major__) || (__clang_major__ != 16) + BOOST_TEST( v2D.begin() == std::ranges::begin(v2D) ); + BOOST_TEST( v2D.end() == std::ranges::end(v2D) ); + + auto v2Dr = v2D | std::ranges::views::reverse; + + BOOST_TEST( (*v2Dr.begin())[4] == (*(v2D.end() - 1))[4] ); + BOOST_TEST( (*(v2Dr.end() - 1))[4] == (*(v2D.begin()))[4] ); + + BOOST_TEST( v2Dr[v2D.size() - 1][5] == v2D[0][5] ); + BOOST_TEST( v2Dr[0][5] == v2D[v2D.size() - 1][5] ); + + // auto const v2DT = v2D.transposed() | std::views::reverse; // TODO(correaa) + // BOOST_TEST( v2DT[1][5] == v2D[2][1] ); + { + auto matrix = + ([](auto ii) noexcept { return static_cast(ii); } ^ + multi::extensions_t(6)) + .partitioned(2); + + auto [matrix_is, matrix_js] = matrix.extensions(); + BOOST_TEST( matrix_is.size() == 2 ); + BOOST_TEST( matrix_js.size() == 3 ); + +#ifndef __NVCC__ + static_assert(std::movable::iterator>); +#endif + BOOST_TEST( std::ranges::begin(matrix[0]) == matrix[0].begin() ); + BOOST_TEST( std::ranges::end(matrix[0]) == matrix[0].end() ); + } +#endif +#endif + } + { + multi::extensions_t<2> const x2D(6, 5); + multi::extensions_t<3> const p3D = multi::layout_t<2>(x2D).partition(2).extensions(); + + using std::get; + BOOST_TEST( get<0>(p3D).size() == 2 ); + BOOST_TEST( get<1>(p3D).size() == 3 ); + BOOST_TEST( get<2>(p3D).size() == 5 ); + } + return boost::report_errors(); +} diff --git a/test/fill.cpp b/test/fill.cpp index ef7878211..1fe9e190c 100644 --- a/test/fill.cpp +++ b/test/fill.cpp @@ -1,197 +1,229 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2024 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi fill" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#include // for array, apply, operator== -#include +#include -#include // for transform -#include -#include -#include // enable_if_t +#include // for fill, all_of, transform +#include // for ptrdiff_t +#include // for uint64_t +#include // for begin, end, size, next +#include // for numeric_limits +#include // for accumulate +#include // for uniform_int_distribution +#include // for enable_if_t, is_same_v namespace { + +using fnv1a_size = std::uint64_t; + // from Howard Hinnart hash -auto fnv1a(void const* key, std::ptrdiff_t len, std::size_t hash) noexcept { // NOLINT(bugprone-easily-swappable-parameters) - auto const* first = static_cast(key); +auto fnv1a(unsigned char const* first, std::ptrdiff_t len, fnv1a_size hash) noexcept { // NOLINT(bugprone-easily-swappable-parameters) return std::accumulate( first, std::next(first, len), hash, - [prime = 1099511628211U](auto acc, auto elem) {return (acc ^ elem) * prime;} + [prime = 1099511628211U](auto acc, auto elem) { return (acc ^ elem) * prime; } ); } } // namespace -// static constexpr auto fnv1a(void const* key, std::size_t len) noexcept { -// return fnv1a(key, len, 14695981039346656037U); -// } - class fnv1a_t { - std::size_t h = 14695981039346656037U; // offset + fnv1a_size h_ = 14695981039346656037U; // offset public: - using result_type = std::size_t; - static constexpr auto min() {return std::numeric_limits::min();} - static constexpr auto max() {return std::numeric_limits::max();} - void operator()(void const* key, std::ptrdiff_t len) noexcept {h = fnv1a(key, len, h);} - template, int> = 0> - auto operator()(T const& value) noexcept -> decltype(auto) {operator()(&value, sizeof(value)); return *this;} -// result_type operator()() && noexcept{return h;} - auto operator()() const& noexcept {return h;} -// explicit operator result_type() && noexcept {return h;} - explicit operator result_type() const& noexcept {return h;} + using result_type = fnv1a_size; + static constexpr auto(min)() { // paren for MSVC macros + return (std::numeric_limits::min)(); + } + static constexpr auto(max)() { + return (std::numeric_limits::max)(); + } + + void operator()(unsigned char const* key, std::ptrdiff_t len) noexcept { h_ = fnv1a(key, len, h_); } + + template, int> = 0> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + auto operator()(T const& value) noexcept -> decltype(auto) { + operator()(&value, sizeof(value)); + return *this; + } + // result_type operator()() && noexcept{return h;} + auto operator()() const& noexcept { return h_; } + // explicit operator result_type() && noexcept {return h;} + explicit operator result_type() const& noexcept { return h_; } }; -BOOST_AUTO_TEST_CASE(fill_1d) { - namespace multi = boost::multi; +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(fill_1d_a) { + namespace multi = boost::multi; + multi::array d1D(multi::extensions_t<1>{multi::iextension{10}}); - static_assert( std::is_same_v::value_type, multi::index>, "!"); + static_assert(std::is_same_v::value_type, multi::index>); using std::copy; copy(begin(extension(d1D)), end(extension(d1D)), begin(d1D)); - BOOST_REQUIRE( d1D[0] == 0 ); - BOOST_REQUIRE( d1D[1] == 1 ); - BOOST_REQUIRE( d1D[9] == 9 ); + BOOST_TEST( d1D[0] == 0 ); + BOOST_TEST( d1D[1] == 1 ); + BOOST_TEST( d1D[9] == 9 ); d1D.assign(extension(d1D)); - BOOST_REQUIRE( d1D[0] == 0 ); - BOOST_REQUIRE( d1D[1] == 1 ); - BOOST_REQUIRE( d1D[9] == 9 ); + BOOST_TEST( d1D[0] == 0 ); + BOOST_TEST( d1D[1] == 1 ); + BOOST_TEST( d1D[9] == 9 ); } + + // BOOST_AUTO_TEST_CASE(fill_1d_b) { + namespace multi = boost::multi; + multi::array d1D(begin(multi::index_extension(10)), end(multi::index_extension(10))); - BOOST_REQUIRE( size(d1D) == 10 ); - BOOST_REQUIRE( d1D[0] == 0 ); - BOOST_REQUIRE( d1D[1] == 1 ); - BOOST_REQUIRE( d1D[9] == 9 ); + BOOST_TEST( size(d1D) == 10 ); + BOOST_TEST( d1D[0] == 0 ); + BOOST_TEST( d1D[1] == 1 ); + BOOST_TEST( d1D[9] == 9 ); } + + // BOOST_AUTO_TEST_CASE(fill_1d_c) { + namespace multi = boost::multi; + multi::array d1D(multi::extensions_t<1>{multi::iextension{10}}); - BOOST_REQUIRE( size(d1D) == 10 ); + BOOST_TEST( size(d1D) == 10 ); d1D.assign(begin(extension(d1D)), end(extension(d1D))); - BOOST_REQUIRE( d1D[0] == 0 ); - BOOST_REQUIRE( d1D[1] == 1 ); - BOOST_REQUIRE( d1D[9] == 9 ); + BOOST_TEST( d1D[0] == 0 ); + BOOST_TEST( d1D[1] == 1 ); + BOOST_TEST( d1D[9] == 9 ); } + + // BOOST_AUTO_TEST_CASE(fill_1d_d) { + namespace multi = boost::multi; + multi::array d1D(multi::extensions_t<1>{multi::iextension{10}}); d1D.assign(extension(d1D)); - BOOST_REQUIRE( d1D[0] == 0 ); - BOOST_REQUIRE( d1D[1] == 1 ); - BOOST_REQUIRE( d1D[9] == 9 ); + BOOST_TEST( d1D[0] == 0 ); + BOOST_TEST( d1D[1] == 1 ); + BOOST_TEST( d1D[9] == 9 ); } -} -BOOST_AUTO_TEST_CASE(fill_member) { - namespace multi = boost::multi; - multi::array d1D = {1.0, 2.0, 3.0, 4.0}; - d1D.fill(42.0); - - multi::array d2D = { - {150.0, 16.0, 17.0, 18.0, 19.0}, - { 5.0, 5.0, 5.0, 5.0, 5.0}, - {100.0, 11.0, 12.0, 13.0, 14.0}, - { 50.0, 6.0, 7.0, 8.0, 9.0} - }; - - BOOST_REQUIRE( d2D.elements().size() == d2D.num_elements() ); - BOOST_REQUIRE( d2D.elements().base() == d2D.base() ); - BOOST_REQUIRE( d2D.elements()[3] == 18.0 ); - BOOST_REQUIRE( &*d2D.elements().begin() == d2D.data_elements() ); - BOOST_REQUIRE( &*d2D.elements().end() == d2D.data_elements() + d2D.num_elements() ); -// std::fill( d2D.elements().begin(), d2D.elements().end() , 99. ); -// multi::adl_fill_n( d2D.elements().begin(), d2D.elements().size(), 99. ); - d2D.elements().fill(99.0); - - BOOST_REQUIRE( d2D[1][1] == 99.0 ); -} + // BOOST_AUTO_TEST_CASE(fill_member) + { + namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(fill) { - std::random_device randdev; + multi::array d1D = {10, 20, 30, 40}; + d1D.fill(420); - namespace multi = boost::multi; + multi::array d2D = { + {1500, 160, 170, 180, 190}, + { 50, 50, 50, 50, 50}, + {1000, 110, 120, 130, 140}, + { 500, 60, 70, 80, 90}, + }; - multi::array d2D = { - {150.0, 16.0, 17.0, 18.0, 19.0}, - { 5.0, 5.0, 5.0, 5.0, 5.0}, - {100.0, 11.0, 12.0, 13.0, 14.0}, - { 50.0, 6.0, 7.0, 8.0, 9.0} - }; - using std::all_of; - BOOST_REQUIRE( all_of(begin(d2D[1]), end(d2D[1]), [](auto const& elem) {return elem == 5.;}) ); + BOOST_TEST( d2D.elements().size() == d2D.num_elements() ); + BOOST_TEST( d2D.elements().base() == d2D.base() ); + BOOST_TEST( d2D.elements()[3] == 180 ); - using std::fill; - fill(d2D[1].begin(), d2D[1].end(), 8.); + std::fill(d2D.elements().begin(), d2D.elements().end(), 990); - BOOST_REQUIRE( all_of(begin(d2D[1]), end(d2D[1]), [](auto const& elem) {return elem == 8.;}) ); + BOOST_TEST( d2D.elements().end() - d2D.elements().begin() == 20 ); + BOOST_TEST( d2D.elements().end() - (d2D.elements().begin() + 10)== 10 ); - fill(begin(rotated(d2D)[1]), end(rotated(d2D)[1]), 8.); - BOOST_REQUIRE( all_of(begin(rotated(d2D)[1]), end(rotated(d2D)[1]), [](auto&& elem) {return elem == 8.;}) ); + BOOST_TEST( d2D[1][1] == 990 ); + } - fill(begin((d2D.rotated())[1]), end((d2D.rotated())[1]), 8.); - BOOST_REQUIRE( all_of(begin((d2D.rotated())[1]), end((d2D.rotated())[1]), [](auto&& elem) {return elem == 8.;}) ); + // BOOST_AUTO_TEST_CASE(simple_fill) + { + namespace multi = boost::multi; - auto rand = [gauss = std::normal_distribution<>{}, gen = std::mt19937{randdev()}]() mutable {return gauss(gen);}; - multi::array r2D({5, 5}); - std::for_each(begin(r2D), end(r2D), [&](auto&& elem) {std::generate(begin(elem), end(elem), rand);}); -} + multi::array d1D = {10, 20, 30, 40}; + std::fill_n(d1D.begin(), d1D.size(), 420); -namespace multi = boost::multi; + multi::array d2D = { + {1500, 160, 170, 180, 190}, + { 50, 50, 50, 50, 50}, + {1000, 110, 120, 130, 140}, + { 500, 60, 70, 80, 90}, + }; -BOOST_AUTO_TEST_CASE(fill_1D) { - multi::array const arr = {1.0, 2.0, 3.0}; - multi::array arr2({10, 3}); + BOOST_TEST( (d2D.elements().end() - 5) - (d2D.elements().begin() + 4) == d2D.elements().size() - 9 ); + BOOST_TEST( d2D.elements().size() == d2D.num_elements() ); + BOOST_TEST( d2D.elements().base() == d2D.base() ); + BOOST_TEST( d2D.elements()[3] == 180 ); - std::fill( begin(arr2), end(arr2), arr ); + std::fill(d2D.elements().begin(), d2D.elements().end(), 990); - BOOST_REQUIRE( arr2[0] == arr ); - BOOST_REQUIRE( arr2[1] == arr ); - // ... - BOOST_REQUIRE( arr2[9] == arr ); -} + BOOST_TEST( d2D[1][1] == 990 ); + } -template -auto broadcast(BinaryOp op, Column const& col, Array const& in, Out&& out) -> Out&& { // NOLINT(readability-identifier-length) clang-tidy 14 bug - std::transform( - begin(~in), end(~in), begin(~out), begin(~out), - [acol = (~col)[0], &op](auto const& Acol, auto&& Bcol) { - std::transform(begin(Acol), end(Acol), begin(acol), begin(Bcol), op); - return std::forward(Bcol); - } - ); + // BOOST_AUTO_TEST_CASE(fill) + { + std::random_device randdev; - return std::forward(out); -} + namespace multi = boost::multi; + + multi::array d2D = { + {1500, 160, 170, 180, 190}, + { 50, 50, 50, 50, 50}, + {1000, 110, 120, 130, 140}, + { 500, 60, 70, 80, 90}, + }; + using std::all_of; + BOOST_TEST( all_of(begin(d2D[1]), end(d2D[1]), [](auto const& elem) { return elem == 50;}) ); + + using std::fill; + fill(d2D[1].begin(), d2D[1].end(), 80); + + BOOST_TEST( all_of(begin(d2D[1]), end(d2D[1]), [](auto const& elem) { return elem == 80;}) ); + + fill(begin(d2D.rotated()[1]), end(d2D.rotated()[1]), 80); + BOOST_TEST( all_of(begin(d2D.rotated()[1]), end(d2D.rotated()[1]), [](auto&& elem) { return elem == 80;}) ); + + fill(begin((d2D.rotated())[1]), end((d2D.rotated())[1]), 80); + BOOST_TEST( all_of(begin((d2D.rotated())[1]), end((d2D.rotated())[1]), [](auto&& elem) { return elem == 80;}) ); + + auto rand = [gauss = std::uniform_int_distribution<>(0, 10), gen = std::mt19937_64(randdev())]() mutable { + return gauss(gen); + }; // NOSONAR -BOOST_AUTO_TEST_CASE (julia_broadcast, *boost::unit_test::tolerance(0.00001) ) { - multi::array const col = { - {0.1}, - {0.2} - }; - multi::array arr = { - {1.10813, 1.72068, 1.15387}, - {1.36851, 1.66401, 1.47846} - }; - { // "broadcast" - multi::array arr2(extensions(arr)); - broadcast(std::plus<>{}, col, arr, arr2); - - BOOST_TEST( arr2[0][0] == 1.20813 ); BOOST_TEST( arr2[0][1] == 1.82068 ); BOOST_TEST( arr2[0][2] == 1.25387 ); - BOOST_TEST( arr2[1][0] == 1.56851 ); BOOST_TEST( arr2[1][1] == 1.86401 ); BOOST_TEST( arr2[1][2] == 1.67846 ); + multi::array r2D({5, 5}); + std::for_each(begin(r2D), end(r2D), [&](decltype(r2D)::reference elem) { std::generate(begin(elem), end(elem), rand); }); // NOLINT(modernize-use-ranges) } - { // inefficient: replicate the vector before summing elementwise - multi::array ax3({2, 3}); - std::fill( begin(~ax3), end(~ax3), (~col)[0] ); - BOOST_TEST( ax3[0][0] == 0.1 ); BOOST_TEST( ax3[0][1] == 0.1 ); BOOST_TEST( ax3[0][2] == 0.1 ); - BOOST_TEST( ax3[1][0] == 0.2 ); BOOST_TEST( ax3[1][1] == 0.2 ); BOOST_TEST( ax3[1][2] == 0.2 ); + // BOOST_AUTO_TEST_CASE(fill_1D) + { + namespace multi = boost::multi; + + multi::array const arr = {1, 2, 3}; + + multi::array arr2({10, 3}, 99); + + BOOST_TEST( (*begin(arr2)).elements().size() == arr.elements().size() ); + (*begin(arr2)).elements() = arr.elements(); + + BOOST_TEST( (*begin(arr2)).elements().size() == (*begin(arr2)).num_elements() ); + BOOST_TEST( ((*begin(arr2)).elements().end() - 2) - ((*begin(arr2)).elements().begin() + 2) == (*begin(arr2)).num_elements() - 4 ); + } + + // BOOST_AUTO_TEST_CASE(fill_n_1D) + { + namespace multi = boost::multi; - multi::array Ap(extensions(arr)); - std::transform(begin(arr.elements()), end(arr.elements()), begin(ax3.elements()), begin(Ap.elements()), std::plus<>{}); + multi::array arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - BOOST_TEST( Ap[0][0] == 1.20813 ); BOOST_TEST( Ap[0][1] == 1.82068 ); BOOST_TEST( Ap[0][2] == 1.25387 ); - BOOST_TEST( Ap[1][0] == 1.56851 ); BOOST_TEST( Ap[1][1] == 1.86401 ); BOOST_TEST( Ap[1][2] == 1.67846 ); + std::fill_n(arr.dropped(3).begin(), 4, 99); + // std::fill_n(arr.begin() + 3, 4, 99); + + BOOST_TEST( arr[2] == 2 ); + BOOST_TEST( arr[3] == 99 ); + BOOST_TEST( arr[4] == 99 ); + BOOST_TEST( arr[5] == 99 ); + BOOST_TEST( arr[6] == 99 ); + BOOST_TEST( arr[7] == 7 ); } + + return boost::report_errors(); } diff --git a/test/fix_complex.cpp b/test/fix_complex.cpp index b2332dec5..cebdf0401 100644 --- a/test/fix_complex.cpp +++ b/test/fix_complex.cpp @@ -1,18 +1,28 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi complex" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#include -#include +#include -#include +#include // for array +#include // for abs // IWYU pragma: keep +#include // for _LIBCPP_VERSION // IWYU pragma: keep // NOLINT(misc-include-cleaner) +// IWYU pragma: no_include // for abs +#include // for complex, operator== +#include // for data -#if(MULTI_PROVIDES_PMR_ARRAY) -#include // for polymorphic memory resource, monotonic buffer +#if __has_include() +#include // for __GLIBCXX__ // NOLINT(misc-include-cleaner) #endif -#include +#ifdef BOOST_MULTI_HAS_MEMORY_RESOURCE +#include // for monotonic_buffer_resource +#endif + +#include // for is_trivially_default_cons... // IWYU pragma: keep namespace multi = boost::multi; @@ -24,85 +34,167 @@ inline constexpr bool multi::force_element_trivial_default_construction -inline constexpr bool multi::force_element_trivial_default_construction> = std::is_trivially_default_constructible::value; +inline constexpr bool multi::force_element_trivial_default_construction> = std::is_trivially_default_constructible_v; #endif -BOOST_AUTO_TEST_CASE(pmr_double) { - multi::array, 2> Aarr({2, 2}, std::complex(4.0, 5.0)); - BOOST_REQUIRE(Aarr[0][0] == std::complex(4.0, 5.0) ); -} - -#if(MULTI_PROVIDES_PMR_ARRAY) -BOOST_AUTO_TEST_CASE(pmr_double_uninitialized) { - std::array buffer = {{4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 999.9, 999.9, 999.9, 999.9}}; - std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12*sizeof(double)}; - - multi::pmr::array Aarr({2, 2}, &pool); - - BOOST_TEST( buffer[0] == 4.0 ); - BOOST_TEST( buffer[1] == 5.0 ); - - BOOST_REQUIRE(Aarr[0][0] == 4.0); -} - -BOOST_AUTO_TEST_CASE(pmr_complex_initialized_2) { - std::array buffer = {{4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 999.9, 999.9, 999.9, 999.9}}; - std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12*sizeof(double)}; - - multi::pmr::array, 2> Aarr({2, 2}, &pool); - - // BOOST_TEST( buffer[0] == 4.0 ); - // BOOST_TEST( buffer[1] == 5.0 ); - - // BOOST_REQUIRE(Aarr[0][0] == std::complex(4.0, 5.0) ); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(pmr_double) + { + multi::array, 2> Aarr({2, 2}, std::complex(4.0, 5.0)); + BOOST_TEST(Aarr[0][0] == std::complex(4.0, 5.0) ); + } - Aarr[0][0] = std::complex{40.0, 50.0}; - BOOST_TEST( buffer[0] == 40.0 ); - BOOST_TEST( buffer[1] == 50.0 ); -} +#ifdef BOOST_MULTI_HAS_MEMORY_RESOURCE + // BOOST_AUTO_TEST_CASE(pmr_double_uninitialized) + { + std::array buffer = { + {4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 996.0, 997.0, 998.0, 999.0} + }; + std::pmr::monotonic_buffer_resource pool(static_cast(std::data(buffer)), 12 * sizeof(double)); + + multi::pmr::array Aarr({2, 2}, &pool); + + BOOST_TEST( std::abs( buffer[0] - 4.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[1] - 5.0 ) < 1E-6 ); + +#ifdef __GLIBCXX__ + BOOST_TEST ( &Aarr[0][0] == buffer.data() ); + BOOST_TEST( std::abs( Aarr[0][0] - 4.0 ) < 1E-6); +#elif defined(_LIBCPP_VERSION) + BOOST_TEST ( &Aarr[0][0] == &buffer[buffer.size() - 4] ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + BOOST_TEST( std::abs( Aarr[0][0] - 996.0 ) < 1E-6 ); +#endif + } + { + std::array buffer = { + {4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 996.0, 997.0, 998.0, 999.0} + }; + std::pmr::monotonic_buffer_resource pool(static_cast(std::data(buffer)), 12 * sizeof(double)); + + multi::pmr::array Aarr({2, 2}, double{}, &pool); + +#ifdef __GLIBCXX__ + BOOST_TEST( std::abs( buffer[0] - 0.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[1] - 0.0 ) < 1E-6 ); + + BOOST_TEST( &Aarr[0][0] == buffer.data() ); +#elif defined(_LIBCPP_VERSION) + BOOST_TEST( std::abs( buffer[0] - 4.0 ) < 1E-6); + BOOST_TEST( std::abs( buffer[1] - 5.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[buffer.size()-4] - 0.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[buffer.size()-3] - 0.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[buffer.size()-5] - 11.0 ) < 1E-6 ); + + BOOST_TEST( &Aarr[0][0] == &buffer[buffer.size() - 4] ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) +#endif -BOOST_AUTO_TEST_CASE(pmr_complex_initialized_4) { - std::array buffer = {{4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 999.9, 999.9, 999.9, 999.9}}; - std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12*sizeof(double)}; + BOOST_TEST( std::abs( Aarr[0][0] - 0.0 ) < 1E-6); + } - multi::pmr::array, 2> Aarr({2, 2}, &pool); + // BOOST_AUTO_TEST_CASE(pmr_complex_initialized_2) + { + std::array buffer = { + {4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 996.0, 997.0, 998.0, 999.0} + }; + std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12 * sizeof(double)}; + + multi::pmr::array, 2> Aarr({2, 2}, &pool); + +#ifdef __GLIBCXX__ + BOOST_TEST( std::abs( buffer[0] - 4.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[1] - 5.0 ) < 1E-6 ); + BOOST_TEST( Aarr[0][0] == std::complex(4.0, 5.0) ); +#elif defined(_LIBCPP_VERSION) + BOOST_TEST( std::abs( buffer[buffer.size() - 4] - 996.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[buffer.size() - 3] - 997.0 ) < 1E-6 ); + BOOST_TEST( std::abs( Aarr[0][0].real() - 8.0 ) < 1E-6 ); + BOOST_TEST( std::abs( Aarr[0][0].imag() - 9.0 ) < 1E-6 ); +#endif + Aarr[0][0] = std::complex{40.0, 50.0}; + +#ifdef __GLIBCXX__ + BOOST_TEST( std::abs( buffer[0] - 40.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[1] - 50.0 ) < 1E-6 ); +#elif defined(_LIBCPP_VERSION) + BOOST_TEST( std::abs( buffer[buffer.size() - 4] - 996.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[buffer.size() - 3] - 997.0 ) < 1E-6 ); +#endif + } - BOOST_REQUIRE(Aarr[0][0] == std::complex(4.0, 5.0) ); + // BOOST_AUTO_TEST_CASE(pmr_complex_initialized_4) + { + std::array buffer = { + {4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 999.9, 999.9, 999.9, 999.9} + }; + std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12 * sizeof(double)}; + + multi::pmr::array, 2> Aarr({2, 2}, &pool); + +#ifdef __GLIBCXX__ + BOOST_TEST( std::abs( Aarr[0][0].real() - 4.0 ) < 1E-6 ); + BOOST_TEST( std::abs( Aarr[0][0].imag() - 5.0 ) < 1E-6 ); +#elif defined(_LIBCPP_VERSION) + BOOST_TEST( std::abs( Aarr[0][0].real() - 8.0 ) < 1E-6 ); + BOOST_TEST( std::abs( Aarr[0][0].imag() - 9.0 ) < 1E-6 ); +#endif - BOOST_TEST( buffer[0] == 4.0 ); - BOOST_TEST( buffer[1] == 5.0 ); + BOOST_TEST( std::abs( buffer[0] - 4.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[1] - 5.0 ) < 1E-6 ); - BOOST_TEST( static_cast(buffer.data()) == static_cast(&Aarr[0][0]) ); -} +#ifdef __GLIBCXX__ + BOOST_TEST( static_cast(buffer.data()) == static_cast(&Aarr[0][0]) ); +#elif defined(_LIBCPP_VERSION) + BOOST_TEST( static_cast(&buffer[4]) == static_cast(&Aarr[0][0]) ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) +#endif + } -BOOST_AUTO_TEST_CASE(pmr_complex_initialized_3) { - std::array buffer = {{4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 999.9, 999.9, 999.9, 999.9}}; - std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12*sizeof(double)}; + // BOOST_AUTO_TEST_CASE(pmr_complex_initialized_3) + { + std::array buffer = { + {4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 996.0, 997.0, 998.0, 999.0} + }; + std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12 * sizeof(double)}; - multi::pmr::array, 2> const Aarr({2, 2}, std::complex{40.0, 50.0}, &pool); + multi::pmr::array, 2> const Aarr({2, 2}, std::complex{40.0, 50.0}, &pool); - BOOST_TEST( Aarr[0][0] == (std::complex{40.0, 50.0}) ); + BOOST_TEST( std::abs( Aarr[0][0].real() - 40.0 ) < 1E-6 ); + BOOST_TEST( std::abs( Aarr[0][0].imag() - 50.0 ) < 1E-6 ); - BOOST_TEST( buffer[0] == 40.0 ); - BOOST_TEST( buffer[1] == 50.0 ); -} +#ifdef __GLIBCXX__ + BOOST_TEST( std::abs( buffer[0] - 40.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[1] - 50.0 ) < 1E-6 ); +#elif defined(_LIBCPP_VERSION) + BOOST_TEST( std::abs( buffer[buffer.size() - 4] - 40.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[buffer.size() - 3] - 50.0 ) < 1E-6 ); +#endif + } -BOOST_AUTO_TEST_CASE(pmr_complex_initialized) { - std::array buffer = {{4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 999.9, 999.9, 999.9, 999.9}}; - std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12*sizeof(double)}; + // BOOST_AUTO_TEST_CASE(pmr_complex_initialized) + { + std::array buffer = { + {4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.00, 11.0, 996.0, 997.0, 998.0, 999.0} + }; + std::pmr::monotonic_buffer_resource pool{static_cast(std::data(buffer)), 12 * sizeof(double)}; - multi::pmr::array, 2> Aarr({2, 2}, &pool); + multi::pmr::array, 2> Aarr({2, 2}, &pool); - if constexpr(multi::force_element_trivial_default_construction>) { - BOOST_TEST( buffer[0] == 4.0 ); - BOOST_TEST( buffer[1] == 5.0 ); + if constexpr(multi::force_element_trivial_default_construction>) { + BOOST_TEST( std::abs( buffer[0] - 4.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[1] - 5.0 ) < 1E-6 ); - BOOST_REQUIRE(Aarr[0][0] == std::complex(4.0, 5.0) ); - } else { - BOOST_TEST( buffer[0] == 0.0 ); - BOOST_TEST( buffer[1] == 0.0 ); +#ifdef __GLIBCXX__ + BOOST_TEST(Aarr[0][0] == std::complex(4.0, 5.0) ); +#elif defined(_LIBCPP_VERSION) + BOOST_TEST( std::abs( Aarr[0][0].real() - 8.0 ) < 1E-6 ); + BOOST_TEST( std::abs( Aarr[0][0].imag() - 9.0 ) < 1E-6 ); +#endif + } else { + BOOST_TEST( std::abs( buffer[0] - 0.0 ) < 1E-6 ); + BOOST_TEST( std::abs( buffer[1] - 0.0 ) < 1E-6); - BOOST_REQUIRE(Aarr[0][0] == 0.0); + BOOST_TEST( std::abs( Aarr[0][0] - 0.0 ) < 1E-6); + } } -} #endif + return boost::report_errors(); +} diff --git a/test/flatted.cpp b/test/flatted.cpp index 5b1b5d372..9d8a70df0 100644 --- a/test/flatted.cpp +++ b/test/flatted.cpp @@ -1,29 +1,78 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#include +#include -#include "multi/array.hpp" +#include namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(array_flatted_3d) { - multi::array arr({13, 4, 5}); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(array_flatted_2d) + { + multi::array arr = { + {0, 1, 2}, + {3, 4, 5}, + }; + + BOOST_TEST( arr.flatted()[1] == 1 ); + BOOST_TEST( arr.flatted()[4] == 4 ); - BOOST_REQUIRE( arr.size() == 13 ); - BOOST_REQUIRE( arr.rotated().is_flattable() ); + arr.flatted()[4] = 44; + BOOST_TEST( arr.flatted()[4] == 44 ); + } + + // BOOST_AUTO_TEST_CASE(array_flatted_3d) { - auto&& arrRFU = arr.rotated().flatted().unrotated(); - BOOST_REQUIRE( &arrRFU[11][7] == &arr[11][1][2] ); + multi::array arr({13, 4, 5}); + + BOOST_TEST( arr.size() == 13 ); + // BOOST_TEST( arr.rotated().is_flattable() ); + + { + auto&& arrRFU = arr.rotated().flatted().unrotated(); // TODO(correaa) remove flatted? + BOOST_TEST( &arrRFU[11][7] == &arr[11][1][2] ); + } + { + auto&& arrRFU = (arr.rotated()).flatted().unrotated(); + BOOST_TEST( &arrRFU[11][7] == &arr[11][7/5][7%5] ); + } } + + // BOOST_AUTO_TEST_CASE(array_flatted_3d_bis) { - auto&& arrRFU = (arr.rotated()).flatted().unrotated(); - BOOST_REQUIRE( &arrRFU[11][7] == &arr[11][7/5][7%5] ); + multi::array const arr({13, 4, 5}); + BOOST_TEST( arr.size() == 13 ); + // BOOST_TEST( arr.is_flattable() ); + BOOST_TEST( arr.flatted().size() == 13L*4L ); + + auto const& arr2 = arr.rotated(); + BOOST_TEST( arr2.flatted().size() == 20 ); + } + + // BOOST_AUTO_TEST_CASE(empty_array_3D_flatted) + { + multi::array const arr; + // BOOST_TEST( arr.is_flattable() ); + BOOST_TEST( arr.flatted().size() == 0 ); + } + + // BOOST_AUTO_TEST_CASE(empty_array_2D_flatted) + { + multi::array const arr; + // BOOST_TEST( arr.is_flattable() ); + BOOST_TEST( arr.flatted().size() == 0 ); + } + + // flatted strided + { + multi::array arr({4, 10}, 99); + BOOST_TEST( &arr.strided(2).flatted()[5] != &arr[0][4] ); + BOOST_TEST( &arr.strided(2).flatted()[5] == &arr[0][5] ); } -} -BOOST_AUTO_TEST_CASE(array_flatted_3d_bis) { - multi::array const arr({13, 4, 5}); - BOOST_REQUIRE( arr.size() == 13 ); + return boost::report_errors(); } diff --git a/test/index_range.cpp b/test/index_range.cpp index dddbd4b44..4db5f1866 100644 --- a/test/index_range.cpp +++ b/test/index_range.cpp @@ -1,110 +1,265 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021-2023 Alfredo A. Correa +// Copyright 2021-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi index range" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#include -#include "multi/array_ref.hpp" +#include -#include - -#include // for accumulate +#include // for equal +#include // for size +#include // for accumulate +#include // for vector +// IWYU pragma: no_include // for tuple_element<>::type +#include namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_range) { -#if defined(__cpp_deduction_guides) and __cpp_deduction_guides and not defined(__NVCC__) - BOOST_REQUIRE(( multi::range{5, 5}.empty() )); -#else - BOOST_REQUIRE(( multi::range{5, 5}.empty() )); -#endif +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_range_in_constexpr) { - auto drng = multi::range{5, 10}; - std::vector vec(drng.begin(), drng.end()); // testing std::vector NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( vec[1] == 6 ); + BOOST_TEST(( multi::extension_t{5, 12}.contains(10) )); + + multi::range const irng{5, 12}; + + BOOST_TEST( !irng.contains( 4) ); + BOOST_TEST( irng.contains( 5) ); + BOOST_TEST( irng.contains( 6) ); + + BOOST_TEST( irng.contains(10) ); + BOOST_TEST( irng.contains(11) ); + + BOOST_TEST( !irng.contains(12) ); + BOOST_TEST( !irng.contains(13) ); + + BOOST_TEST( * irng.begin() == 5 ); + BOOST_TEST( *(irng.begin() + 1) == 6 ); + + BOOST_TEST( irng.begin() < irng.begin() + 1 ); + BOOST_TEST( !((irng.begin() + 1) < irng.begin()) ); + BOOST_TEST( !(irng.end() < irng.begin()) ); + + BOOST_TEST( irng.begin() != irng.begin() + 1 ); + BOOST_TEST( !(irng.begin() == irng.begin() + 1) ); + + BOOST_TEST( irng.first() == 5 ); + BOOST_TEST( irng.last() == 12 ); + + BOOST_TEST( irng.front() == 5 ); + BOOST_TEST( irng.back () == 11 ); + + std::vector vec = {5, 6, 7, 8, 9, 10, 11}; // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST(std::equal(irng.begin(), irng.end(), vec.begin(), vec.end())); // NOLINT(fuchsia-default-arguments-calls,modernize-use-ranges) + + auto sum = std::accumulate(irng.begin(), irng.end(), 0); + BOOST_TEST( sum == 5 + 6 + 7 + 8 + 9 + 10 + 11 ); } + { - auto drng = multi::range{5, 10}; + multi::range const irng{5, 12}; - auto fun = [](auto idx) { return idx + 1; }; + auto beg = irng.begin(); - std::vector vec( // testing std::vector NOLINT(fuchsia-default-arguments-calls) - boost::make_transform_iterator(drng.begin(), fun), - boost::make_transform_iterator(drng.end(), fun) - ); - BOOST_REQUIRE( vec[1] == 7 ); + ++beg; + --beg; + + BOOST_TEST( irng.begin() == beg ); // cppcheck-suppress knownConditionTrueFalse ; for test + BOOST_TEST( !(irng.end() < irng.begin()) ); } -} -BOOST_AUTO_TEST_CASE(crazy_range) { - // auto trng = multi::range( - // multi::detail::tuple{5, 3}, - // multi::detail::tuple{5, 9}, - // [](auto t , int d) {return std::get<1>(t) + d;} - // [](auto t1, auto t2) {return std::get<1>(t1) - std::get<1>(t2);} - // ); + // BOOST_AUTO_TEST_CASE(multi_range2) + { + multi::index_extension const iex(10); - // BOOST_REQUIRE( trng[0] == (std::tuple{5, 3}) ); - // BOOST_REQUIRE( trng[1] == (std::tuple{5, 4}) ); + using std::size; + BOOST_TEST( *begin(iex) == 0 ); + BOOST_TEST( size(iex) == 10 ); + BOOST_TEST( iex[0] == 0 ); + BOOST_TEST( iex[1] == 1 ); + BOOST_TEST( iex[9] == 9 ); - // BOOST_REQUIRE( *trng.begin() == (std::tuple{5, 3}) ); - // BOOST_REQUIRE( *(trng.begin() + 1) == (std::tuple{5, 4}) ); -} + auto const xbeg = begin(iex); + BOOST_TEST( xbeg[0] == iex[0] ); + BOOST_TEST( xbeg[1] == iex[1] ); -BOOST_AUTO_TEST_CASE(multi_range_in_constexpr) { - BOOST_REQUIRE( multi::extension_t{5} == 5 ); - BOOST_REQUIRE(( multi::extension_t{5, 12}.contains(10) )); + BOOST_TEST( !(iex.begin() < iex.begin() + 1 - 1) ); + BOOST_TEST( !(iex.end() < iex.end() - 1 + 1) ); + BOOST_TEST( !(iex.end() < iex.begin()) ); - multi::range const irng{5, 12}; + BOOST_TEST( iex.begin() < iex.end() ); + BOOST_TEST( !(iex.end() < iex.begin()) ); - BOOST_REQUIRE( irng.contains(6) ); - BOOST_REQUIRE( not irng.contains(12) ); + BOOST_TEST( std::accumulate( begin(iex), end(iex), static_cast(0U)) == 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 ); - BOOST_REQUIRE( * irng.begin() == 5 ); - BOOST_REQUIRE( *(irng.begin() + 1) == 6 ); + { + multi::iextensions<3> const ies({ + {0, 3}, + {0, 4}, + {0, 5}, + }); - BOOST_REQUIRE( irng.first() == 5 ); - BOOST_REQUIRE( irng.last() == 12 ); + using std::get; - BOOST_REQUIRE( irng.front() == 5 ); - BOOST_REQUIRE( irng.back () == 11 ); + auto const ies0 = get<0>(ies); + auto const ies1 = get<1>(ies); + auto const ies2 = get<2>(ies); - std::vector vec = {5, 6, 7, 8, 9, 10, 11}; // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( ies0.size() == 3 ); + BOOST_TEST( ies1.size() == 4 ); + BOOST_TEST( ies2.size() == 5 ); - BOOST_REQUIRE(std::equal(irng.begin(), irng.end(), vec.begin(), vec.end())); // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( get<0>(ies).size() == 3 ); + BOOST_TEST( get<1>(ies).size() == 4 ); + BOOST_TEST( get<2>(ies).size() == 5 ); - auto sum = std::accumulate(irng.begin(), irng.end(), 0); - BOOST_REQUIRE( sum == 5 + 6 + 7 + 8 + 9 + 10 + 11 ); -} +#ifndef _MSC_VER // doesn't work in MSVC 14.3 in c++17 mode + auto const [eyes, jays, kays] = ies; + BOOST_TEST( eyes.size() == 3 ); + BOOST_TEST( jays.size() == 4 ); + BOOST_TEST( kays.size() == 5 ); +#endif + } + } -BOOST_AUTO_TEST_CASE(multi_range2) { - multi::index_extension const iex(10); + // test prev canonical 2D + { + multi::extensions_t<2> const ies({ + {0, 3}, + {0, 4} + }); + multi::extensions_t<2>::index i = 1; + multi::extensions_t<2>::index j = 0; - BOOST_REQUIRE( *begin(iex) == 0 ); - BOOST_REQUIRE( size(iex) == 10 ); - BOOST_REQUIRE( iex[0] == 0 ); - BOOST_REQUIRE( iex[1] == 1 ); - BOOST_REQUIRE( iex[9] == 9 ); + ies.prev_canonical(i, j); + BOOST_TEST( i == 0 ); + BOOST_TEST( j == 3 ); - auto const xbeg = begin(iex); - BOOST_REQUIRE( xbeg[0] == iex[0] ); - BOOST_REQUIRE( xbeg[1] == iex[1] ); + ies.prev_canonical(i, j); + BOOST_TEST( i == 0 ); + BOOST_TEST( j == 2 ); - BOOST_REQUIRE( std::accumulate( begin(iex), end(iex), 0) == 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 ); + ies.prev_canonical(i, j); + BOOST_TEST( i == 0 ); + BOOST_TEST( j == 1); + ies.prev_canonical(i, j); + BOOST_TEST( i == 0 ); + BOOST_TEST( j == 0); + } + + // test prev canonical 2D { - multi::iextensions<3> const ies({ - {0, 3}, - {0, 4}, - {0, 5}, - }); - BOOST_REQUIRE( std::get<0>(ies).size() == 3 ); - BOOST_REQUIRE( std::get<1>(ies).size() == 4 ); - BOOST_REQUIRE( std::get<2>(ies).size() == 5 ); - - auto [eyes, jays, kays] = ies; - BOOST_REQUIRE( eyes.size() == 3 ); - BOOST_REQUIRE( jays.size() == 4 ); - BOOST_REQUIRE( kays.size() == 5 ); + multi::extensions_t<3> const ies({ + {0, 3}, + {0, 4}, + {0, 5} + }); + multi::extensions_t<3>::index i = 1; + multi::extensions_t<3>::index j = 1; + multi::extensions_t<3>::index k = 1; + + ies.prev_canonical(i, j, k); + BOOST_TEST( i == 1 ); + BOOST_TEST( j == 1 ); + BOOST_TEST( k == 0 ); + + ies.prev_canonical(i, j, k); + BOOST_TEST( i == 1 ); + BOOST_TEST( j == 0 ); + BOOST_TEST( k == 4 ); + + ies.prev_canonical(i, j, k); + BOOST_TEST( i == 1 ); + BOOST_TEST( j == 0 ); + BOOST_TEST( k == 3 ); + + ies.prev_canonical(i, j, k); + BOOST_TEST( i == 1 ); + BOOST_TEST( j == 0 ); + BOOST_TEST( k == 2 ); + + ies.prev_canonical(i, j, k); + BOOST_TEST( i == 1 ); + BOOST_TEST( j == 0 ); + BOOST_TEST( k == 1 ); + + ies.prev_canonical(i, j, k); + BOOST_TEST( i == 1 ); + BOOST_TEST( j == 0 ); + BOOST_TEST( k == 0 ); + + ies.prev_canonical(i, j, k); + BOOST_TEST( i == 0 ); + BOOST_TEST( j == 3 ); + BOOST_TEST( k == 4 ); } + + // BOOST_AUTO_TEST_CASE(multi_range_in_constexpr) + { + // BOOST_TEST(( multi::extension_t{5, 12}.contains(10) )); + + multi::range, int> const irng({}, 12); + +// && !defined(__PGI) && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +#if (__cplusplus >= 202002L) && (__has_cpp_attribute(no_unique_address) >= 201803L) && !defined(__NVCC__) && !defined(__NVCOMPILER) + static_assert(sizeof(irng) == sizeof(int)); +#endif + + BOOST_TEST( irng.first() == 0 ); + BOOST_TEST( irng.last() == 12 ); + + BOOST_TEST( irng.contains( 0) ); + BOOST_TEST( irng.contains( 4) ); + BOOST_TEST( irng.contains(11) ); + + BOOST_TEST( !irng.contains(12) ); + + BOOST_TEST( * irng.begin() == 0 ); + BOOST_TEST( *(irng.begin() + 1) == 1 ); + + BOOST_TEST( irng.front() == 0 ); + BOOST_TEST( irng.back () == 11 ); + + std::vector vec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST(std::equal(irng.begin(), irng.end(), vec.begin(), vec.end())); // NOLINT(fuchsia-default-arguments-calls,modernize-use-ranges) + + auto sum = std::accumulate(irng.begin(), irng.end(), 0); + BOOST_TEST( sum == 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 ); + } + + // BOOST_AUTO_TEST_CASE(multi_range_in_constexpr) + { + multi::range, int> const irng{{}, 12}; + + BOOST_TEST( !irng.contains( 4) ); + BOOST_TEST( irng.contains( 5) ); + BOOST_TEST( irng.contains( 6) ); + + BOOST_TEST( irng.contains(10) ); + BOOST_TEST( irng.contains(11) ); + + BOOST_TEST( !irng.contains(12) ); + BOOST_TEST( !irng.contains(13) ); + + BOOST_TEST( * irng.begin() == 5 ); + BOOST_TEST( *(irng.begin() + 1) == 6 ); + + BOOST_TEST( irng.first() == 5 ); + BOOST_TEST( irng.last() == 12 ); + + BOOST_TEST( irng.front() == 5 ); + BOOST_TEST( irng.back () == 11 ); + + std::vector vec = {5, 6, 7, 8, 9, 10, 11}; // testing std::vector of multi:array NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST(std::equal(irng.begin(), irng.end(), vec.begin(), vec.end())); // NOLINT(fuchsia-default-arguments-calls,modernize-use-ranges) + + auto sum = std::accumulate(irng.begin(), irng.end(), 0); + BOOST_TEST( sum == 5 + 6 + 7 + 8 + 9 + 10 + 11 ); + } + + return boost::report_errors(); } diff --git a/test/initializer_list.cpp b/test/initializer_list.cpp index a6914d058..94d3e5bf1 100644 --- a/test/initializer_list.cpp +++ b/test/initializer_list.cpp @@ -1,395 +1,494 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#include +#include // for array, static_array, num_elements -#include +#include -#include +// IWYU pragma: no_include // for copy // bug in iwyu 14.0.6? with GNU stdlib +#include // for array +#include // for operator*, operator+, complex +#include // for initializer_list, begin, end +#include // for size, begin, end +#include // for basic_string, allocator, char_tr... +#include // for is_same_v +#include // for vector namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_1d) { +namespace boost::multi { +// template +// auto operator+(std::initializer_list il) { +// return multi::array(il); +// } + +template +auto operator+(std::initializer_list il) { // NOLINT(misc-use-anonymous-namespace,misc-use-internal-linkage) + multi::array ret({static_cast(il.size())}, T{}); + std::copy(il.begin(), il.end(), ret.begin()); + return ret; +} + +template +auto operator+(std::initializer_list> il) { // NOLINT(misc-use-anonymous-namespace,misc-use-internal-linkage) + auto const size2 = il.size() == 0 ? 0 : std::max_element(il.begin(), il.end(), [](auto const& a, auto const& b) { return a.size() < b.size(); })->size(); + + multi::array ret({static_cast(il.size()), static_cast(size2)}, T{}); + std::copy(il.begin(), il.end(), ret.begin()); + return ret; +} + +} // end namespace boost::multi + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_1d) { - std::vector const vec = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( vec[1] == 2. ); + std::vector const vec = {10, 20, 30}; // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( vec[1] == 20 ); } { - multi::static_array arr = {1.2, 3.4, 5.6}; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == 5.6 ); + multi::static_array arr = {12, 34, 56}; + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); } { - multi::static_array const arr = {1.2, 3.4, 5.6}; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == 5.6 ); + multi::static_array const arr = {12, 34, 56}; + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); } { - auto const il = {1.2, 3.4, 5.6}; + auto const il = {12, 34, 56}; - multi::static_array const arr(il); - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == il.begin()[2] ); + multi::static_array const arr(il); + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) } { - auto const il = {1.2, 3.4, 5.6}; + auto const il = {12, 34, 56}; - multi::static_array const arr(begin(il), end(il)); - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == il.begin()[2] ); + multi::static_array const arr(begin(il), end(il)); + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) } { - multi::static_array const arr = {1.2, 3.4, 5.6}; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == 5.6 ); - BOOST_REQUIRE(( arr == multi::static_array{1.2, 3.4, 5.6} )); - BOOST_REQUIRE(( arr == decltype(arr){1.2, 3.4, 5.6} )); + multi::static_array const arr = {12, 34, 56}; + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); + BOOST_TEST(( arr == multi::static_array{12, 34, 56} )); + BOOST_TEST(( arr == decltype(arr){12, 34, 56} )); } { - auto const values = {1.2, 3.4, 5.6}; + auto const values = {12, 34, 56}; - multi::array const arr(values.begin(), values.end()); - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == 5.6 ); + multi::array const arr(values.begin(), values.end()); + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); } { - multi::array const arr = {1.2, 3.4, 5.6}; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == 5.6 ); - BOOST_REQUIRE(( arr == multi::array{1.2, 3.4, 5.6} )); - BOOST_REQUIRE(( arr == decltype(arr){1.2, 3.4, 5.6} )); - BOOST_REQUIRE(( arr == decltype(arr)::decay_type({1.2, 3.4, 5.6}) )); + multi::array const arr = {12, 34, 56}; + + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); + + BOOST_TEST(( arr == multi::array{12, 34, 56} )); + BOOST_TEST(( arr == decltype(arr){12, 34, 56} )); + BOOST_TEST(( arr == decltype(arr)::decay_type({12, 34, 56}) )); } { - std::array const stdarr = { - {1.1, 2.2, 3.3} - }; + std::array const stdarr = { + {11, 22, 33}, + }; using multi::num_elements; - BOOST_REQUIRE( num_elements(stdarr) == 3 ); + BOOST_TEST( num_elements(stdarr) == 3 ); using std::begin; using std::end; multi::static_array const arr(begin(stdarr), end(stdarr)); - BOOST_REQUIRE( size(arr) == 3 ); + BOOST_TEST( size(arr) == 3 ); } -} -BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_1d_ctad) { -#if defined(__cpp_deduction_guides) and not defined(__NVCC__) - { -#if not defined(__circle_build__) // crashes circle 198 - multi::static_array const arr = {1.2, 3.4, 5.6}; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == 5.6 ); - BOOST_REQUIRE(( arr == multi::static_array{1.2, 3.4, 5.6} )); -#else -// multi::static_array const arr = {1.2, 3.4, 5.6}; + // BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_1d_ctad) + { +#if defined(__cpp_deduction_guides) && !defined(__NVCC__) + // #if __cplusplus >= 202002L + // static constexpr auto f = [] + // { + // multi::array arr(3); + // arr[0] = 12; arr[1] = 34; arr[2] = 56; // TODO(correaa) getting "assignment to object outside its lifetime is not allowed in a constant expression" + // return (arr.size() == 3); + // }(); + // static_assert(f); + // #endif + + { + multi::static_array const arr = {12, 34, 56}; + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); + BOOST_TEST(( arr == multi::static_array{12, 34, 56} )); + } + { + multi::array arr(std::initializer_list{12, 34, 56}); + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); + BOOST_TEST(( arr == multi::array(std::initializer_list{12, 34, 56}) )); + } + { + multi::array arr({12, 34, 56}); + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); + BOOST_TEST(( arr == multi::array({12, 34, 56}) )); + } +#if !defined(__GNUC__) || (__GNUC__ < 14) // workaround bug in gcc 14.2 + { + multi::array arr({12, 34, 56}); + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[2] == 56 ); + BOOST_TEST(( arr == multi::array({12, 34, 56}) )); + } #endif - } - { - multi::array arr({1.2, 3.4, 5.6}); - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[2] == 5.6 ); - BOOST_REQUIRE(( arr == multi::array({1.2, 3.4, 5.6}) )); - } #endif -} + } -BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_array) { -#if defined(__INTEL_COMPILER) or (defined(__clang__) and (__clang_major__ >= 10)) // doesn't work on gcc + // BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_array) { +#if defined(__INTEL_COMPILER) || (defined(__clang__) && (__clang_major__ >= 10)) // doesn't work on gcc + { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wc99-designator" - // double const a[] = { [8] = 8.0, 9.0, 10.0 }; - std::array const stdarr = { - {[8] = 8.0, 9.0, 10.0}, - }; + // double const a[] = { [8] = 8.0, 9.0, 10.0 }; + std::array const stdarr = { + {[8] = 8.0, 9.0, 10.0}, + }; #pragma GCC diagnostic pop - multi::array arr = stdarr; - BOOST_REQUIRE( arr.size() == 11 ); - BOOST_REQUIRE( arr[9] == 9.0 ); - } + multi::array arr = stdarr; + BOOST_TEST( arr.size() == 11 ); + BOOST_TEST( arr[9] == 9.0 ); + } #endif -} + } -BOOST_AUTO_TEST_CASE(multi_initialize_from_carray_1d) { + // BOOST_AUTO_TEST_CASE(multi_initialize_from_carray_1d) { - multi::static_array const arr = {1.1, 2.2, 3.3}; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( arr[1] == 2.2 ); + multi::static_array const arr = {11, 22, 33}; + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( arr[1] == 22 ); } { -#if defined(__cpp_deduction_guides) and not defined(__NVCC__) +#if defined(__cpp_deduction_guides) && !defined(__NVCC__) // multi::array arr = {{1.1, 2.2, 3.3}}; // static_assert( decltype(arr)::dimensionality == 1 , "!"); -// BOOST_REQUIRE( size(arr)==3 and arr[1] == 2.2 ); +// BOOST_TEST( size(arr)==3 && arr[1] == 2.2 ); #endif } { - std::array stdarr = {{1.1, 2.2, 3.3}}; + std::array stdarr = { + {1.1, 2.2, 3.3} + }; multi::array const arr(begin(stdarr), end(stdarr)); - BOOST_REQUIRE(( arr == decltype(arr){1.1, 2.2, 3.3} )); + BOOST_TEST(( arr == decltype(arr){1.1, 2.2, 3.3} )); } -} -BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_2d) { + // BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_2d) { multi::static_array const arr = { { 1.2, 2.4, 3.6, 8.9}, {11.2, 34.4, 5.6, 1.1}, {15.2, 32.4, 5.6, 3.4}, }; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( size(arr[0]) == 4 ); - BOOST_REQUIRE(( arr == decltype(arr){ + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( size(arr[0]) == 4 ); + BOOST_TEST(( arr == decltype(arr){ { 1.2, 2.4, 3.6, 8.9}, {11.2, 34.4, 5.6, 1.1}, {15.2, 32.4, 5.6, 3.4}, })); } { - multi::array const arr = { - { 1.2, 2.4, 3.6}, - {11.2, 34.4, 5.6}, - {15.2, 32.4, 5.6}, + multi::array const arr = { + { 12, 24, 36}, + {112, 344, 56}, + {152, 324, 56}, }; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( size(arr) == 3 and size(arr[0]) == 3 ); - BOOST_REQUIRE( arr[1][1] == 34.4 ); + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( size(arr[0]) == 3 ); + BOOST_TEST( arr[1][1] == 344 ); + + using multi::operator+; + auto arr2 = operator+({ + { 12, 24, 36}, + {112, 344, 56}, + {152, 324, 56}, + }); + + BOOST_TEST( arr2 == arr ); } { - multi::array arr = { - { 1.2, 2.4, 3.6}, - {11.2, 34.4, 5.6}, - {15.2, 32.4, 5.6}, + multi::array arr = { + { 12, 24, 36}, + {112, 344, 56}, + {152, 324, 56}, }; - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( size(arr) == 3 and size(arr[0]) == 3 ); - BOOST_REQUIRE( arr[1][1] == 34.4 ); + + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( size(arr[0]) == 3 ); + BOOST_TEST( arr[1][1] == 344 ); + arr = { - { 0.0, 1.0, 02.0}, - {10.0, 11.0, 12.0}, - {20.0, 21.0, 22.0}, + {100, 10, 20}, + {100, 110, 120}, + {200, 210, 220}, }; - BOOST_REQUIRE( arr[1][2] == 12.0 ); + BOOST_TEST( arr[1][2] == 120 ); } { - multi::array vec; - vec = {4.0, 5.5}; - BOOST_REQUIRE( size(vec) == 2 ); - BOOST_REQUIRE( vec[1] == 5.5 ); + multi::array vec; + vec = {40, 55}; + BOOST_TEST( size(vec) == 2 ); + BOOST_TEST( vec[1] == 55 ); } { - std::array, 3> const nested = {{{{1.2, 2.4}}, {{11.2, 34.4}}, {{15.2, 32.4}}}}; + std::array, 3> const nested = { + {{{12, 24}}, {{112, 344}}, {{152, 324}}} + }; using std::begin; using std::end; - multi::static_array arr(begin(nested), end(nested)); - BOOST_REQUIRE( size(arr) == 3 ); - BOOST_REQUIRE( size(arr[0]) == 2 ); - BOOST_REQUIRE( arr[1][0] == 11.2 ); + multi::static_array arr(begin(nested), end(nested)); + + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( size(arr[0]) == 2 ); + BOOST_TEST( arr[1][0] == 112 ); } { - std::array, 3> const nested = {{{{1.2, 2.4}}, {{11.2, 34.4}}, {{15.2, 32.4}}}}; - multi::static_array const arr(std::begin(nested), std::end(nested)); + std::array, 3> const nested = { + {{{12, 24}}, {{112, 344}}, {{152, 324}}} + }; + multi::static_array const arr(std::begin(nested), std::end(nested)); - BOOST_REQUIRE(( - arr == multi::array {{ - {{ 1.2, 2.4}}, - {{11.2, 34.4}}, - {{15.2, 32.4}} + BOOST_TEST(( + arr == multi::array {{ + {{ 12, 24}}, + {{112, 344}}, + {{152, 324}} }} )); - BOOST_REQUIRE(not( arr != multi::array{ - { 1.2, 2.4}, - {11.2, 34.4}, - {15.2, 32.4} + + BOOST_TEST(!( arr != multi::array{ + { 12, 24}, + {112, 344}, + {152, 324}, } )); - BOOST_REQUIRE(( + BOOST_TEST(( arr == decltype(arr){ - { 1.2, 2.4}, - {11.2, 34.4}, - {15.2, 32.4} + { 12, 24}, + {112, 344}, + {152, 324}, } )); } { - std::array, 3> nested = { - {{{1.0, 2.0}}, - {{2.0, 4.0}}, - {{3.0, 6.0}}}, + std::array, 3> const nested = { + { + {{10, 20}}, + {{20, 40}}, + {{30, 60}}, + } }; - multi::array arr(begin(nested), end(nested)); - BOOST_REQUIRE( num_elements(arr) == 6 and arr[2][1] == 6.0 ); + multi::array arr(begin(nested), end(nested)); + BOOST_TEST( num_elements(arr) == 6 ); + BOOST_TEST( arr[2][1] == 60 ); } { using complex = std::complex; - complex const I{0., 1.}; // NOLINT(readability-identifier-length) imaginary unit + + complex const I{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit + multi::array arr = { {2.0 + 1.0 * I, 1.0 + 3.0 * I, 1.0 + 7.0 * I}, {3.0 + 4.0 * I, 4.0 + 2.0 * I, 0.0 + 0.0 * I}, }; - BOOST_REQUIRE( arr[1][1] == 4.0 + 2.0*I ); + BOOST_TEST( arr[1][1] == 4.0 + 2.0*I ); } -} - -BOOST_AUTO_TEST_CASE(multi_tests_static_array_initializer_list) { - multi::static_array, 2> SA = { - {{1.0, 0.0}, {2.0, 0.0}}, - {{3.0, 0.0}, {4.0, 0.0}}, - }; - BOOST_REQUIRE( SA[1][1] == 4.0 ); -} -BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_3d) { - multi::array const arr = { - { {1.2, 0.0}, {2.4, 1.0}}, - { {11.2, 3.0}, {34.4, 4.0}}, - {{15.2, 99.0}, {32.4, 2.0}}, - }; - BOOST_REQUIRE( arr[1][1][0] == 34.4 and arr[1][1][1] == 4.0 ); -} + // BOOST_AUTO_TEST_CASE(multi_tests_static_array_initializer_list) + { + multi::static_array, 2> SA = { + {{1.0, 0.0}, {2.0, 0.0}}, + {{3.0, 0.0}, {4.0, 0.0}}, + }; + BOOST_TEST( SA[1][1] == 4.0 ); + } -BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_3d_string) { + // BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_3d) { - using std::string; - multi::array B3 = { - {{"000", "001", "002"}, {"010", "011", "012"}}, // NOLINT(fuchsia-default-arguments-calls) - {{"100", "101", "102"}, {"110", "111", "112"}}, // NOLINT(fuchsia-default-arguments-calls) + multi::array const arr = { + { {12, 100}, {24, 10}}, + { {112, 30}, {344, 40}}, + {{152, 990}, {324, 20}}, }; - BOOST_REQUIRE( num_elements(B3)==12 and B3[1][0][1] == "101" ); + BOOST_TEST( arr[1][1][0] == 344 ); + BOOST_TEST( arr[1][1][1] == 40 ); } -} -#if defined(__cpp_deduction_guides) and not defined(__NVCC__) -BOOST_AUTO_TEST_CASE(initializer_list_1d_static) { + // BOOST_AUTO_TEST_CASE(multi_tests_initializer_list_3d_string) { -#if not defined(__circle_build__) - multi::static_array arr({1.0, 2.0, 3.0}); - static_assert(std::is_same_v); - BOOST_REQUIRE( size(arr) == 3 and num_elements(arr) == 3 ); - BOOST_REQUIRE( multi::rank{}==1 and num_elements(arr)==3 and arr[1] == 2.0 ); + { + using std::string; + + // NOLINTBEGIN(fuchsia-default-arguments-calls) + multi::array B3 = { + {{"000", "001", "002"}, {"010", "011", "012"}}, + {{"100", "101", "102"}, {"110", "111", "112"}}, + }; + // NOLINTEND(fuchsia-default-arguments-calls) + + BOOST_TEST( num_elements(B3) == 12 ); + BOOST_TEST( B3[1][0][1] == "101" ); + } + } + +#if defined(__cpp_deduction_guides) && !defined(__NVCC__) + // BOOST_AUTO_TEST_CASE(initializer_list_1d_static) + { + multi::static_array arr({10, 20, 30}); + + static_assert(std::is_same_v); + + BOOST_TEST( size(arr) == 3 && num_elements(arr) == 3 ); + BOOST_TEST( multi::rank::value == 1); + BOOST_TEST( num_elements(arr) == 3 ); + BOOST_TEST( arr[1] == 20 ); + static_assert(typename decltype(arr)::rank{} == 1); -#else -// multi::static_array arr( {1.0, 2.0, 3.0}); // crashes circle -// multi::static_array arr(std::initializer_list{1.0, 2.0, 3.0}); // crashes circle -#endif } -} -BOOST_AUTO_TEST_CASE(initializer_list_1d) { +#if !defined(__GNUC__) || (__GNUC__ < 14) // workaround bug in gcc 14.2 + // BOOST_AUTO_TEST_CASE(initializer_list_1d_a) { - multi::array arr({1.0, 2.0, 3.0}); - static_assert(std::is_same_v); - BOOST_REQUIRE( size(arr) == 3 and num_elements(arr) == 3 ); - BOOST_REQUIRE( multi::rank{}==1 and num_elements(arr)==3 and arr[1] == 2.0 ); + multi::array arr({10, 20, 30}); + + static_assert(std::is_same_v); + + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( num_elements(arr) == 3 ); + BOOST_TEST( multi::rank::value == 1 ); + BOOST_TEST( num_elements(arr) == 3 ); + BOOST_TEST( arr[1] == 20 ); + static_assert(typename decltype(arr)::rank{} == 1); } + + // BOOST_AUTO_TEST_CASE(initializer_list_1d_b) { - multi::array arr({1.0, 2.0}); - static_assert(std::is_same_v); - BOOST_REQUIRE( size(arr) == 2 and num_elements(arr) == 2 ); - BOOST_REQUIRE( multi::rank{}==1 and num_elements(arr) == 2 and arr[1] == 2.0 ); - BOOST_REQUIRE( multi::rank{} == 1 ); + multi::array arr({10, 20}); + static_assert(std::is_same_v); + + BOOST_TEST( size(arr) == 2 ); + BOOST_TEST( num_elements(arr) == 2 ); + BOOST_TEST( multi::rank::value == 1 ); + BOOST_TEST( num_elements(arr) == 2 ); + BOOST_TEST( arr[1] == 20 ); + BOOST_TEST( multi::rank::value == 1 ); } + + // BOOST_AUTO_TEST_CASE(initializer_list_1d_c) { multi::array arr({0, 2}); // multi::array arr = {0, 2}; not working with CTAD + static_assert(std::is_same_v); - BOOST_REQUIRE( size(arr) == 2 and num_elements(arr) == 2 ); - BOOST_REQUIRE( multi::rank{} == 1 and num_elements(arr) == 2 and arr[1] == 2.0 ); - BOOST_REQUIRE( multi::rank{} == 1 ); + + BOOST_TEST( size(arr) == 2 ); + BOOST_TEST( num_elements(arr) == 2 ); + BOOST_TEST( multi::rank{} == 1 ); + BOOST_TEST( num_elements(arr) == 2 ); + BOOST_TEST( arr[1] == 2 ); + BOOST_TEST( multi::rank{} == 1 ); } + + // BOOST_AUTO_TEST_CASE(initializer_list_1d_d) { - multi::array arr({9.0}); // multi::array arr = {9.0}; not working with CTAD - static_assert(std::is_same_v); - BOOST_REQUIRE( multi::rank{}==1 and num_elements(arr)==1 and arr[0]==9. ); - BOOST_REQUIRE( multi::rank{}==1 ); + multi::array arr({90}); // multi::array arr = {90}; not working with CTAD + + static_assert(std::is_same_v); + + BOOST_TEST( multi::rank::value == 1 ); + BOOST_TEST( num_elements(arr) == 1 ); + BOOST_TEST( arr[0] == 90 ); + BOOST_TEST( multi::rank::value == 1 ); } + + // BOOST_AUTO_TEST_CASE(initializer_list_1d_e) { - multi::array arr({9}); // multi::array arr = {9}; not working with CTAD + multi::array arr({90}); // multi::array arr = {90}; not working with CTAD + static_assert(std::is_same_v); - BOOST_REQUIRE( size(arr) == 1 and num_elements(arr) == 1 ); - BOOST_REQUIRE( multi::rank{} == 1 ); - BOOST_REQUIRE( num_elements(arr) == 1 and arr[0] == 9.0 ); + + BOOST_TEST( size(arr) == 1 ); + BOOST_TEST( num_elements(arr) == 1 ); + BOOST_TEST( multi::rank::value == 1 ); + BOOST_TEST( num_elements(arr) == 1 ); + BOOST_TEST( arr[0] == 90 ); } -} -BOOST_AUTO_TEST_CASE(initializer_list_2d) { - { -#if not defined(__circle_build__) - multi::static_array const arr({ - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - }); - BOOST_TEST_REQUIRE( multi::rank{} == 2 ); - BOOST_TEST_REQUIRE( num_elements(arr) == 6 ); -#else - // // vvv--- gives segfault in circle - // multi::static_array const arr({ - // {1.0, 2.0, 3.0}, - // {4.0, 5.0, 6.0}, - // }); -#endif + // BOOST_AUTO_TEST_CASE(initializer_list_2d) + { + { + multi::static_array const arr({ + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + }); + BOOST_TEST( multi::rank{} == 2 ); + BOOST_TEST( num_elements(arr) == 6 ); + } + { + multi::array const arr({ + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + }); + BOOST_TEST( multi::rank::value == 2 ); + BOOST_TEST( num_elements(arr) == 6 ); + } } - { -#if not defined(__circle_build__) - multi::array const arr({ - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - }); - BOOST_TEST_REQUIRE( multi::rank{} == 2 ); - BOOST_TEST_REQUIRE( num_elements(arr) == 6 ); -#else - // // vvv--- gives error in circle, not viable constructor - // multi::array const arr({ - // {1.0, 2.0, 3.0}, - // {4.0, 5.0, 6.0}, - // }); - - // vvv--- gives ODR violation in circle - // multi::array const arr(std::initializer_list>{ - // {1.0, 2.0, 3.0}, - // {4.0, 5.0, 6.0}, - // }); - - // vvv--- gives ODR violation in circle - // multi::array const arr(std::initializer_list>{ - // std::initializer_list{1.0, 2.0, 3.0}, - // std::initializer_list{4.0, 5.0, 6.0}, - // }); #endif - } -} #endif -BOOST_AUTO_TEST_CASE(partially_formed) { - multi::array arr1({10, 10}, double{}); - multi::array arr2({10, 10}, {}); - multi::array arr3({10, 10}, 0.0); + // BOOST_AUTO_TEST_CASE(partially_formed) + { + multi::array arr1({10, 10}, int{}); + multi::array arr2({10, 10}, {}); + multi::array arr3({10, 10}, 0); - BOOST_REQUIRE( arr1[0][0] == 0.0); - BOOST_REQUIRE( arr2[0][0] == 0.0); - BOOST_REQUIRE( arr3[0][0] == 0.0); -} + BOOST_TEST( arr1[0][0] == 0); + BOOST_TEST( arr2[0][0] == 0); + BOOST_TEST( arr3[0][0] == 0); + } -BOOST_AUTO_TEST_CASE(partially_formed_int_1) { - multi::array arr1({10, 10}, int{1}); - multi::array arr2({10, 10}, {1}); - multi::array arr3({10, 10}, 1); + // BOOST_AUTO_TEST_CASE(partially_formed_int_1) + { + multi::array arr1({10, 10}, static_cast(1U)); + multi::array arr2({10, 10}, {1}); + multi::array arr3({10, 10}, 1); - BOOST_REQUIRE( arr1[0][0] == 1); - BOOST_REQUIRE( arr2[0][0] == 1); - BOOST_REQUIRE( arr3[0][0] == 1); -} + BOOST_TEST( arr1[0][0] == 1); + BOOST_TEST( arr2[0][0] == 1); + BOOST_TEST( arr3[0][0] == 1); + } -BOOST_AUTO_TEST_CASE(partially_formed_int_0) { - multi::array arr1({10, 10}, int{}); - multi::array arr2({10, 10}, {}); - multi::array arr3({10, 10}, 0); + // BOOST_AUTO_TEST_CASE(partially_formed_int_0) + { + multi::array arr1({10, 10}, int{}); + multi::array arr2({10, 10}, {}); + multi::array arr3({10, 10}, 0); + + BOOST_TEST( arr1[0][0] == 0); + BOOST_TEST( arr2[0][0] == 0); + BOOST_TEST( arr3[0][0] == 0); + } - BOOST_REQUIRE( arr1[0][0] == 0); - BOOST_REQUIRE( arr2[0][0] == 0); - BOOST_REQUIRE( arr3[0][0] == 0); + return boost::report_errors(); } diff --git a/test/interpolator.cpp b/test/interpolator.cpp new file mode 100644 index 000000000..b69a79a5e --- /dev/null +++ b/test/interpolator.cpp @@ -0,0 +1,173 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array, implicit_cast, explicit_cast + +#include + +#include // for copy +#include +#include +#include // for distance +#include + +namespace multi = boost::multi; + +class uniform_cspline { + using argument_type = double; + using result_type = double; // typename std::decay()[0])>::type; + using vector = std::vector; + using size_type = typename vector::size_type; + using index = multi::array::index; + + argument_type lower_; + argument_type dx_; + + public: + auto dx() const -> argument_type { return dx_; } + auto lower() const -> argument_type { return lower_; } + auto upper() const -> argument_type { return lower_ + (dx_ * static_cast(K_.size() - 1)); } + + private: + multi::array K_; + // vector a; + // vector b; + // vector c; + // vector d; + // std::vector> K; + public: + // template + // uniform_cspline(It a_begin, It a_end, argument_type lower, argument_type dx) + // : uniform_cspline(std::vector(a_begin, a_end), lower, dx) {} + + template + uniform_cspline(It a_first, It a_last, argument_type lower, argument_type dx) // NOLINT(bugprone-easily-swappable-parameters) + : lower_{lower}, dx_{dx}, K_({static_cast::size_type>(std::distance(a_first, a_last)), 4}) { + auto const n = K_.size(); // NOLINT(readability-identifier-length) + auto&& a = K_.rotated()[0]; // NOLINT(readability-identifier-length) + + std::copy(a_first, a_last, a.begin()); + + auto&& b = K_.rotated()[1]; // NOLINT(readability-identifier-length) + auto&& c = K_.rotated()[2]; // NOLINT(readability-identifier-length) + auto&& d = K_.rotated()[3]; // NOLINT(readability-identifier-length) + + auto&& A = b; // NOLINT(readability-identifier-length) + auto&& l = c; // NOLINT(readability-identifier-length) + auto&& u = d; // NOLINT(readability-identifier-length) + auto&& z = A; // NOLINT(readability-identifier-length) + + for(index i = 1; i != n - 1; ++i) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) TODO(correaa) use algorithms + A[i] = (3 * (a[i + 1] - a[i]) / dx_) - (3 * (a[i] - a[i - 1]) / dx_); + } + + l[0] = 4 * dx_; + u[0] = dx_ / l[0]; + z[0] = A[1] / l[0]; + + for(index i = 1; i != n - 2; ++i) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) TODO(correaa) use algorithms + l[i] = (4 * dx_) - (dx_ * u[i - 1]); + u[i] = dx_ / l[i]; + z[i] = (A[i + 1] - dx_ * z[i - 1]) / l[i]; + } + + c[n - 1] = 0; + + for(index j = n - 2; j != 0; --j) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) TODO(correaa) use algorithms + c[j] = z[j - 1] - (u[j - 1] * c[j + 1]); + b[j] = ((a[j + 1] - a[j]) / dx_) - (dx_ * (c[j + 1] + 2 * c[j]) / 3); + d[j] = (c[j + 1] - c[j]) / (3 * dx_); + } + + c[0] = 0; + b[0] = ((a[1] - a[0]) / dx_) - (dx_ * c[1] / 3); + d[0] = c[1] / (3 * dx_); + // for(std::size_t i = 0; i != K.size(); ++i) K[i] = {a[i], b[i], c[i], d[i]}; + } + auto operator()(argument_type x) const -> result_type { + auto const i = static_cast((x - lower_) / dx_); + auto const Dx = x - (static_cast(i) * dx_) - lower_; + // return a[i] + Dx*(b[i] + Dx*(c[i] + Dx*d[i])); + // auto const& Ki = K[i]; using std::get; + // return K[0][i] + Dx*(K[1][i] + Dx*(K[2][i] + Dx*K[3][i])); + auto const& Ki = K_[i]; + return Ki[0] + (Dx * (Ki[1] + Dx * (Ki[2] + Dx * Ki[3]))); + } +}; + +// #if defined(__cpp_deduction_guides) +// template uniform_cspline(It, It, ValueType, ValueType) -> uniform_cspline::value_type>; +// #endif + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + using std::cout; + using vector = std::vector; + + // 0.0, 0.4, 0.8, 1.2, 1.6 + vector a = {10.0, 12.0, 9.0, 8.0, 13.0}; // NOLINT(readability-identifier-length) + + uniform_cspline const as(a.begin(), a.end(), 1.0, 0.4); + { + // for(double x = as.lower() + 0.01; x < as.upper(); x += 0.01) { + for(int i = 0; i != static_cast((as.upper() - as.lower()) / 0.01); ++i) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + double const x = as.lower() + (static_cast(i) * 0.01); // NOLINT(readability-identifier-length) + std::cout << x << '\t' << as(x) << '\n'; + } + } + + BOOST_TEST( std::abs(as.dx() - 0.4) < 1.0e-10 ); + BOOST_TEST( std::abs( as.lower() - 1.0) < 1.0e-10 ); + BOOST_TEST( std::abs( as.upper() - ( 1.0 + as.dx()*static_cast(a.size() - 1)) ) < 1.0e-10 ); + BOOST_TEST( std::abs( as(1.0) - 10.0) < 1.0e-10 ); + BOOST_TEST( std::abs( as(1.4) - 12.0) < 1.0e-10 ); + + { + // using Clock = std::chrono::high_resolution_clock; + // using ns = std::chrono::nanoseconds; + + // std::mt19937 eng{std::random_device{}()}; + // std::uniform_real_distribution{-10.0, 10.0}; + + // auto gen = [ + // dist = std::uniform_real_distribution<>{-10.0, 10.0}, + // eng = std::mt19937{std::random_device{}()} + // ]() mutable { return dist(eng); }; + + // int const N = 1000; + + // std::vector rep(100000); + + // generate(rep.begin(), rep.end(), [&]() { vector ret(N); generate(ret.begin(), ret.end(), gen); return ret; }); + // { + // cout << "using val semantics\nconstruction: repeats: " << rep.size() << " size: " << rep[0].size() << '\n'; + // auto tic = Clock::now(); + // for(std::size_t i = 0; i != rep.size(); ++i) { + // uniform_cspline ucsp(rep[i].begin(), rep[i].end(), 1.0, 0.4); + // } + // auto toc = Clock::now(); + // cout << '\t' << static_cast(ns{toc - tic}.count()) / 1.0e9 << " sec\n"; + // } + // { + // vector a(10000); + // generate(begin(a), end(a), gen); + // uniform_cspline as(begin(a), end(a), 0., 0.4); + // { + // std::uniform_real_distribution x_dist{as.left_endpoint(), as.right_endpoint()}; + // vector x(1000000000); + // double nodiscard = 0; + // generate(begin(x), end(x), [&x_dist, &eng]() { return x_dist(eng); }); + // { + // cout << "evaluation: repeats: " << x.size() << " for size: " << a.size() << '\n'; + // auto tic = Clock::now(); + // for(std::size_t i = 0; i != x.size(); ++i) + // nodiscard += as(x[i]); + // auto toc = Clock::now(); + // cout << '\t' << ns{toc - tic}.count() / 1e9 << " sec [" << (char)nodiscard << "]\n"; + // } + // } + // } + } + + return boost::report_errors(); +} diff --git a/test/iterator.cpp b/test/iterator.cpp index abe2f87ad..89f47f8b8 100644 --- a/test/iterator.cpp +++ b/test/iterator.cpp @@ -1,236 +1,354 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi iterators" // title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array, implicit_cast, explicit_cast -#include "multi/array.hpp" +#include -#include -#include +#include // for is_sorted, copy +#include // for begin, end, size, cbegin, make_r... +#include // for accumulate +#include // for basic_ostringstream, basic_ostre... +#include // for basic_string, char_traits, opera... +#include // for is_same_v, is_same +#include // for forward +#include // for allocator, vector namespace multi = boost::multi; -template auto take(Array&& array) -> decltype(array[0]) {return array[0];} - -BOOST_AUTO_TEST_CASE(iterator_1d) { +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // iterator_1d { - multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, 99.0); - BOOST_REQUIRE( size(arr) == 100 ); - BOOST_REQUIRE( begin(arr) < end(arr) ); - BOOST_REQUIRE( end(arr) - begin(arr) == size(arr) ); + BOOST_TEST((std::is_trivially_copy_constructible_v >)); + BOOST_TEST((std::is_trivially_copy_assignable_v >)); + BOOST_TEST((std::is_trivially_default_constructible_v>)); + + BOOST_TEST((std::is_trivially_copy_constructible_v >)); + BOOST_TEST((std::is_trivially_copy_assignable_v >)); + BOOST_TEST((std::is_trivially_default_constructible_v>)); + + // BOOST_TEST((std::is_trivially_copy_constructible_v >)); + // BOOST_TEST((std::is_trivially_copy_assignable_v >)); + // BOOST_TEST((std::is_trivially_default_constructible_v>)); + + BOOST_TEST((std::is_trivially_default_constructible_v::iterator>)); + BOOST_TEST((std::is_trivially_copy_constructible_v ::iterator>)); + BOOST_TEST((std::is_trivially_copy_assignable_v ::iterator>)); + + { + multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, 99.0); + BOOST_TEST( arr.size() == 100 ); + BOOST_TEST( arr.begin() < arr.end() ); + BOOST_TEST( arr.end() - arr.begin() == arr.size() ); + + auto const begin_plus_10 = arr.begin() + 10; + BOOST_TEST( begin_plus_10 - 10 == arr.begin() ); + + multi::array::const_iterator const cbarr = arr.cbegin(); + multi::array::iterator barr = arr.begin(); + + [[maybe_unused]] multi::array::const_iterator const cbarr3{barr}; - multi::array::const_iterator const cbarr = cbegin(arr); - multi::array::iterator barr = begin(arr); + BOOST_TEST( barr == cbarr ); // problem in C++20 + BOOST_TEST( cbarr == barr ); // problem in C++20 - [[maybe_unused]] multi::array::const_iterator const cbarr3{barr}; + barr += 1; + barr -= 1; + BOOST_TEST( cbarr == barr ); - BOOST_REQUIRE( barr == cbarr ); - BOOST_REQUIRE( cbarr == barr ); + multi::array::const_iterator const cbarr2 = begin(arr); + BOOST_TEST( cbarr2 == cbarr ); + } + { + multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, 99.0); + BOOST_TEST( arr.size() == 100 ); + BOOST_TEST( arr.begin() < arr.end() ); + + auto arr2 = arr.begin(); + multi::array::const_iterator const cbb = arr2; + BOOST_TEST( cbb == arr2 ); + // BOOST_TEST( arr2 == cbb ); // TODO(correaa) problem in C++20 + } + { + multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, 99.0); + BOOST_TEST( arr.size() == 100 ); + BOOST_TEST( arr.begin() < arr.end() ); - barr += 1; - barr -= 1; - BOOST_REQUIRE( cbarr == barr ); + auto const arrend = arr.end(); + auto const arrlast = arrend - 1; - multi::array::const_iterator const cbarr2 = begin(arr); - BOOST_REQUIRE( cbarr2 == cbarr ); + BOOST_TEST( arrlast + 1 == arrend ); + } } + + // iterator_2d { - multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, 99.0); - BOOST_REQUIRE( size(arr) == 100 ); - BOOST_REQUIRE( begin(arr) < end(arr) ); - - auto arr2 = arr.begin(); - multi::array::const_iterator const cbb = arr2; - BOOST_REQUIRE( cbb == arr2 ); - BOOST_REQUIRE( arr2 == cbb ); + BOOST_TEST((std::is_trivially_copy_constructible_v >)); + BOOST_TEST((std::is_trivially_copy_assignable_v >)); + BOOST_TEST((std::is_trivially_default_constructible_v>)); + + // BOOST_TEST((std::is_trivially_copy_constructible_v >)); + // BOOST_TEST((std::is_trivially_copy_assignable_v >)); + // BOOST_TEST((std::is_trivially_default_constructible_v>)); + + // BOOST_TEST((std::is_trivially_default_constructible_v::iterator>)); // TODO(correaa) + BOOST_TEST((std::is_trivially_copy_constructible_v ::iterator>)); + BOOST_TEST((std::is_trivially_copy_assignable_v ::iterator>)); + + { + multi::array const arr({120, 140}, 99.0); + + BOOST_TEST( arr.size() == 120 ); + BOOST_TEST( arr.cbegin() < arr.cend() ); + BOOST_TEST( !(arr.cend() < arr.cend()) ); + BOOST_TEST( arr.cend() - arr.cbegin() == arr.size() ); + + using iter = multi::array::iterator; + static_assert(std::is_same_v); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v); + + using citer = multi::array::const_iterator; + static_assert(std::is_same_v); + static_assert(std::is_same_v>); + + static_assert(std::is_same_v>); + static_assert(std::is_same_v); + + auto const arrend = arr.end(); + auto const arrlast = arrend - 1; + + BOOST_TEST( arrlast + 1 == arrend ); + } + { + std::vector vec(10000); // std::vector NOLINT(fuchsia-default-arguments-calls) + multi::array_ref arr(vec.data(), {100, 100}); + BOOST_TEST(arr.size() == 100); + begin(arr)[4][3] = 20; + + BOOST_TEST(begin(arr)[4][3] == 20); + } } -} -BOOST_AUTO_TEST_CASE(iterator_2d) { + // iterator_interface { - multi::array const arr({120, 140}, 99.0); - - BOOST_REQUIRE( arr.size() == 120 ); - BOOST_REQUIRE( size(arr) == 120 ); - BOOST_REQUIRE( arr.cbegin() < arr.cend() ); - BOOST_REQUIRE( arr.cend() - arr.cbegin() == arr.size() ); - - using iter = multi::array::iterator; - static_assert( std::is_same_v< iter::element , double >, "!"); - static_assert( std::is_same_v< iter::value_type, multi::array >, "!"); - static_assert( std::is_same_v< iter::reference, multi::subarray>, "!"); - static_assert( std::is_same_v< iter::element_ptr, double*>, "!"); - - using citer = multi::array::const_iterator; - static_assert( std::is_same_v< citer::element , double >, "!"); - static_assert( std::is_same_v< citer::value_type, multi::array >, "!"); - static_assert( std::is_same_v< citer::reference, multi::subarray>, "!"); - static_assert( std::is_same_v< citer::element_ptr, double const* >, "!"); + multi::array arr = { + { {12, 11}, {24, 10}}, + {{112, 30}, {344, 40}}, + { {12, 11}, {24, 10}} + }; + + BOOST_TEST( size(arr) == 3 ); + BOOST_TEST( size(arr[0]) == 2 ); + BOOST_TEST( size(arr[0][0]) == 2 ); + BOOST_TEST( arr[0][0][1] == 11 ); + + BOOST_TEST( begin(arr) < end(arr) ); + BOOST_TEST( cbegin(arr) < cend(arr) ); + BOOST_TEST( begin(arr[0]) < end(arr[0]) ); + BOOST_TEST( begin(arr[0]) < end(arr[0]) ); + + // BOOST_TEST(( multi::array::reverse_iterator {A.begin()} == rend(A) )); + + // BOOST_TEST( rbegin(A) < rend(A) ); + + BOOST_TEST( end(arr) - begin(arr) == size(arr) ); + // BOOST_TEST( rend(A) - rbegin(A) == size(A) ); + + BOOST_TEST( size(*begin(arr) ) == 2 ); + BOOST_TEST( size( begin(arr)[1]) == 2 ); + + BOOST_TEST( &(arr[1][1].begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( &arr[0][1][0] == &arr[0][1][0] ); + BOOST_TEST( &((*arr.begin())[1][0]) == &arr[0][1][0] ); + + BOOST_TEST( &((*arr.begin()).operator[](1)[0]) == &arr[0][1][0] ); + BOOST_TEST( &( arr.begin()->operator[](1)[0]) == &arr[0][1][0] ); + + BOOST_TEST( &((*arr.begin()).operator[](1).begin()[0]) == &arr[0][1][0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( &( arr.begin()->operator[](1).begin()[0]) == &arr[0][1][0] ); // NOLINT(readability-container-data-pointer) test access + + BOOST_TEST( &((*(arr.begin()+1)).operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( &( (arr.begin()+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + + BOOST_TEST( &((*(begin(arr)+1)).operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( &(( begin(arr)+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + + BOOST_TEST( &((*(cbegin(arr)+1)).operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( &(( cbegin(arr)+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access } + + // iterator_semantics { - std::vector vec(10000); // std::vector NOLINT(fuchsia-default-arguments-calls) - multi::array_ref arr(vec.data(), {100, 100}); - BOOST_REQUIRE(size(arr) == 100); - begin(arr)[4][3] = 2.0; - } -} + multi::array arr = { + { {1.2, 1.1}, {2.4, 1.0}}, + {{11.2, 3.0}, {34.4, 4.0}}, + { {1.2, 1.1}, {2.4, 1.0}} + }; -BOOST_AUTO_TEST_CASE(iterator_interface ) { - multi::array arr = { - { - { 1.2, 1.1}, { 2.4, 1.0} - }, - { - {11.2, 3.0}, {34.4, 4.0} - }, - { - { 1.2, 1.1}, { 2.4, 1.0} - } - }; + multi::array::iterator it; + // BOOST_TEST(( multi::array::iterator{} == it )); // `it` is uninitialized + // BOOST_TEST(( it == multi::array::iterator{} )); - BOOST_REQUIRE( size(arr)==3 and size(arr[0]) == 2 and size(arr[0][0]) == 2 ); - BOOST_REQUIRE( arr[0][0][1] == 1.1 ); + it = begin(arr); + BOOST_TEST( it == begin(arr) ); - BOOST_REQUIRE( begin(arr) < end(arr) ); - BOOST_REQUIRE( cbegin(arr) < cend(arr) ); - BOOST_REQUIRE( begin(arr[0]) < end(arr[0]) ); - BOOST_REQUIRE( begin(arr[0]) < end(arr[0]) ); + it += 1; + it -= 1; + BOOST_TEST( it == begin(arr) ); -// BOOST_REQUIRE(( multi::array::reverse_iterator {A.begin()} == rend(A) )); + auto const& arrc = arr(); + BOOST_TEST( &arrc[0][0][0] == &arr[0][0][0] ); -// BOOST_REQUIRE( rbegin(A) < rend(A) ); + auto const& arrc2 = arr(); - BOOST_REQUIRE( end(arr) - begin(arr) == size(arr) ); -// BOOST_REQUIRE( rend(A) - rbegin(A) == size(A) ); + BOOST_TEST( arrc.addressof() == arrc2.addressof() ); // BOOST_TEST( &arrc == &arrc2 ); - BOOST_REQUIRE( size(*begin(arr)) == 2 ); - BOOST_REQUIRE( size(begin(arr)[1]) == 2 ); + multi::array::iterator const it2 = begin(arr); + BOOST_TEST(it == it2); - BOOST_REQUIRE( &(arr[1][1].begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access - BOOST_REQUIRE( &arr[0][1][0] == &arr[0][1][0] ); - BOOST_REQUIRE( &((*arr.begin())[1][0]) == &arr[0][1][0] ); - BOOST_REQUIRE( &((*arr.begin()).operator[](1)[0]) == &arr[0][1][0] ); - BOOST_REQUIRE( &(arr.begin()->operator[](1)[0]) == &arr[0][1][0] ); - BOOST_REQUIRE( &(arr.begin()->operator[](1).begin()[0]) == &arr[0][1][0] ); // NOLINT(readability-container-data-pointer) test access - BOOST_REQUIRE( &((arr.begin()+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access - BOOST_REQUIRE( &((begin(arr)+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access - BOOST_REQUIRE( &((cbegin(arr)+1)->operator[](1).begin()[0]) == &arr[1][1][0] ); // NOLINT(readability-container-data-pointer) test access -} + it = end(arr); + BOOST_TEST(it != it2); + BOOST_TEST(it > it2); -BOOST_AUTO_TEST_CASE(iterator_semantics) { - multi::array arr = { - {{ 1.2, 1.1}, { 2.4, 1.0}}, - {{11.2, 3.0}, {34.4, 4.0}}, - {{ 1.2, 1.1}, { 2.4, 1.0}} - }; + multi::array::iterator const it3{it}; + BOOST_TEST( it3 == it ); - multi::array::iterator it; - BOOST_REQUIRE(( multi::array::iterator{} == it )); - BOOST_REQUIRE(( it == multi::array::iterator{} )); + static_assert(std::is_same::iterator::element_ptr, double*>{}); - it = begin(arr); - BOOST_REQUIRE( it == begin(arr) ); + // cit = it3; + // BOOST_REQUIRE( cit == it3 ); // TODO(correaa) + // BOOST_REQUIRE( it3 == cit ); // TODO(correaa) - it += 1; - it -= 1; - BOOST_REQUIRE( it == begin(arr) ); + // cit = it3; + // BOOST_TEST( cit == it3 ); // TODO(correaa) + // BOOST_TEST( it3 == cit ); // TODO(correaa) - auto const& arrc = arr(); - BOOST_REQUIRE( &arrc[0][0][0] == &arr[0][0][0] ); + BOOST_TEST( &arr[0][1][1] == &begin(arr)[0][1][1] ); - auto const& arrc2 = arr(); - BOOST_REQUIRE( &arrc == &arrc2 ); + [[maybe_unused]] multi::array::const_iterator const cit2 = it3; - multi::array::iterator const it2 = begin(arr); - BOOST_REQUIRE(it == it2); + static_assert(decltype(begin(arr))::rank_v == 3, "!"); + static_assert(decltype(begin(arr))::rank{} == 3, "!"); - it = end(arr); - BOOST_REQUIRE(it != it2); - BOOST_REQUIRE(it > it2); + // auto&& ref = multi::ref(begin(arr), end(arr)); - multi::array::iterator const it3{it}; - BOOST_REQUIRE( it3 == it ); + // BOOST_TEST( arr.base() == ref.base() ); + // BOOST_TEST( arr[0][2][1] == ref[0][2][1] ); + // BOOST_TEST( &arr[0][2][1] == &ref[0][2][1] ); + // BOOST_TEST( arr.layout().stride() == ref.layout().stride()); + // BOOST_TEST( arr.layout().offset() == ref.layout().offset()); + // BOOST_TEST( arr.layout().nelems() == ref.layout().nelems()); - multi::array::const_iterator cit; - static_assert( std::is_same::iterator::element_ptr, double*>{}, "!"); + // BOOST_TEST( arr.num_elements() == ref.num_elements() ); + // BOOST_TEST( arr.stride() == ref.stride() ); + // BOOST_TEST( arr.layout() == ref.layout() ); - [[maybe_unused]] multi::array::const_iterator const cit3{it3}; + // BOOST_TEST( &multi::ref(begin(arr), end(arr)) == &arr ); + } - cit = it3; - BOOST_REQUIRE( cit == it3 ); - BOOST_REQUIRE( it3 == cit ); - BOOST_REQUIRE( &arr[0][2][1] == &begin(arr)[0][2][1] ); + // iterator_arrow_operator + { + // NOLINTBEGIN(fuchsia-default-arguments-calls) std::string has a default constructor + multi::array arr = { + {"00", "01"}, + {"10", "11"}, + {"20", "21"} + }; + // NOLINTEND(fuchsia-default-arguments-calls) - [[maybe_unused]] multi::array::const_iterator const cit2 = it3; + BOOST_TEST( arr[1][0] == "10" ); - static_assert( decltype(begin(arr))::rank_v == 3 , "!" ); - static_assert( decltype(begin(arr))::rank {} == 3 , "!" ); + BOOST_TEST( std::is_sorted(begin(arr), end(arr)) ); // sorted by rows // NOLINT(modernize-use-ranges) + BOOST_TEST( std::is_sorted(begin(arr.rotated()), end(arr.rotated())) ); // sorted by cols // NOLINT(modernize-use-ranges) - auto&& ref = multi::ref(begin(arr), end(arr)); + BOOST_TEST( (*begin( arr )).size() == arr[0].size() ); + BOOST_TEST( begin( arr )->size() == arr[0].size() ); - BOOST_TEST( arr.base() == ref.base() ); - BOOST_TEST( arr[0][2][1] == ref[0][2][1] ); - BOOST_TEST( &arr[0][2][1] == &ref[0][2][1] ); - BOOST_TEST( arr.layout().stride() == ref.layout().stride()); - BOOST_TEST( arr.layout().offset() == ref.layout().offset()); - BOOST_TEST( arr.layout().nelems() == ref.layout().nelems()); + BOOST_TEST( (*begin( arr.rotated() )).size() == arr.size() ); + BOOST_TEST( begin( arr.rotated() )->size() == arr.size() ); - BOOST_REQUIRE( arr.num_elements() == ref.num_elements() ); - BOOST_REQUIRE( arr.stride() == ref.stride() ); - BOOST_REQUIRE( arr.layout() == ref.layout() ); + BOOST_TEST( &((*begin( arr )).operator[](1)) == &(arr[0][1]) ); + BOOST_TEST( &( begin( arr )->operator[](1)) == &(arr[0][1]) ); - BOOST_REQUIRE( &multi::ref(begin(arr), end(arr)) == &arr ); -} + BOOST_TEST( &((*begin( arr.rotated() )).operator[](1)) == &(arr[1][0]) ); + BOOST_TEST( &( begin( arr.rotated() )->operator[](1)) == &(arr[1][0]) ); + } -BOOST_AUTO_TEST_CASE(iterator_arrow_operator) { - multi::array arr = { - {"00", "01"}, // std::string NOLINT(fuchsia-default-arguments-calls) std::string has a default constructor - {"10", "11"}, // std::string NOLINT(fuchsia-default-arguments-calls) - {"20", "21"} // std::string NOLINT(fuchsia-default-arguments-calls) - }; + // index_range_iteration + { + multi::index_range irng(0, 5); // semiopen interval + std::ostringstream out; + std::copy(irng.begin(), irng.end(), std::ostream_iterator(out, ",")); // NOLINT(modernize-use-ranges) + BOOST_TEST_EQ(out.str(), std::string{"0,1,2,3,4,"}); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr[1][0] == "10" ); + BOOST_TEST( std::accumulate(begin(irng), end(irng), static_cast(0U)) == irng.size()*(irng.size()-1)/2 ); - BOOST_REQUIRE( std::is_sorted(begin(arr), end(arr)) ); // sorted by rows - BOOST_REQUIRE( std::is_sorted(begin(arr.rotated()), end(arr.rotated())) ); // sorted by cols + auto const sum_of_cubes = [](auto&& acc, auto const& elem) { + return std::forward(acc) + (elem * elem * elem); + }; + BOOST_TEST( std::accumulate(begin(irng), end(irng), multi::index_range::value_type{}, sum_of_cubes) > 0 ); // NOLINT(boost-use-ranges) + } - BOOST_REQUIRE( begin( arr )->size() == arr[0].size() ); - BOOST_REQUIRE( begin( arr.rotated() )->size() == arr.size() ); + // multi_reverse_iterator_1D + { + multi::array arr(100, 66.0); + BOOST_TEST( &arr[99] == &*std::make_reverse_iterator(arr.end()) ); - BOOST_REQUIRE( &(begin( arr )->operator[](1)) == &(arr[0][1]) ); - BOOST_REQUIRE( &(begin( arr.rotated() )->operator[](1)) == &(arr[1][0]) ); -} + auto rbegin = std::make_reverse_iterator(arr.end()); + rbegin += 100; + multi::array::iterator const begin{rbegin.base()}; + BOOST_TEST( begin == arr.begin() ); + } -BOOST_AUTO_TEST_CASE(index_range_iteration) { - multi::index_range irng{0, 5}; // semiopen interval - std::ostringstream out; - std::copy(begin(irng), end(irng), std::ostream_iterator{out, ","}); - BOOST_REQUIRE( out.str() == "0,1,2,3,4," ); + // multi_reverse_iterator_2D + { + multi::array arr = { + { 10, 20}, + { 100, 200}, + {1000, 2000}, + }; + BOOST_TEST( (*arr.begin())[1] == 20 ); + BOOST_TEST( arr.begin()->operator[](1) == 20 ); - BOOST_REQUIRE( std::accumulate(begin(irng), end(irng), 0) == irng.size()*(irng.size()-1)/2 ); + auto rbegin = std::make_reverse_iterator(arr.end()); - BOOST_REQUIRE( std::accumulate(begin(irng), end(irng), 0, [](auto&& acc, auto const& elem) {return acc + elem*elem*elem;}) > 0 ); // sum of cubes -} + BOOST_TEST( (*rbegin)[1] == 2000 ); -BOOST_AUTO_TEST_CASE(multi_reverse_iterator_1D) { - multi::array arr(100, 66.0); - BOOST_REQUIRE( &arr[99] == &*std::make_reverse_iterator(arr.end()) ); + BOOST_TEST( arr.begin() < arr.begin() + 1 ); + BOOST_TEST( arr.end() - 1 < arr.end() ); + } - auto rbegin = std::make_reverse_iterator(arr.end()); - rbegin += 100; - multi::array::iterator const begin{rbegin.base()}; - BOOST_REQUIRE( begin == arr.begin() ); -} + // simple example arrow iterator 1D + { + multi::array const arr({4}, std::string{"hello"}); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr.size() == 4 ); + BOOST_TEST( arr.begin()->size() == 5 ); + + BOOST_TEST( arr[0] == "hello" ); + BOOST_TEST( *(arr.begin()->begin()) == 'h' ); + + // arr[0][0] = 'H'; + // *(arr.begin()->begin()) = "H"; // can't assign to const value + // BOOST_TEST( arr[0] == "Hello" ); + } -BOOST_AUTO_TEST_CASE(multi_reverse_iterator_2D) { - multi::array arr = { - { 1.0, 2.0}, - { 10.0, 20.0}, - {100.0, 200.0} - }; - BOOST_REQUIRE( (*arr.begin())[1] == 2.0 ); - auto rbegin = std::make_reverse_iterator(arr.end()); + // simple example arrow operator 2D + { + multi::array const arr({2, 3}, 5); + BOOST_TEST( arr[0].size() == 3 ); + BOOST_TEST( arr.begin()->size() == 3 ); + + BOOST_TEST( arr[0][0] == 5 ); + BOOST_TEST( *(arr.begin()->begin()) == 5 ); + + // arr[0][0] = 6; // ok, doesn't compile, it is read-only + // *(arr.begin()->begin()) = 6; // ok, doesn't work, it is read-only + // BOOST_TEST( arr[0][0] == 6 ); + } - BOOST_TEST( (*rbegin)[1] == 200.0 ); + return boost::report_errors(); } diff --git a/test/layout.cpp b/test/layout.cpp index 8224a3f40..3713ab35e 100644 --- a/test/layout.cpp +++ b/test/layout.cpp @@ -1,913 +1,1310 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi layout" // title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for range, layout_t, get, extensions_t -#include "multi/array.hpp" -#include "multi/utility.hpp" +#include -#include "multi/detail/tuple_zip.hpp" +#include // for copy +#include // for array, array<>::value_type +#include // for ptrdiff_t, size_t // IWYU pragma: keep +#include // for size -#include +#if defined(__cplusplus) && (__cplusplus >= 202002L) && __has_include() +#if !defined(__clang_major__) || (__clang_major__ != 16) +#include // IWYU pragma: keep +#endif +#endif + +#include // for make_tuple, tuple_element<>::type +#include // for vector +// IWYU pragma: no_include +#include namespace multi = boost::multi; namespace { auto second_finish(multi::extensions_t<3> exts) { - return std::get<1>(exts).finish(); + using std::get; // workaround: function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + return get<1>(exts).last(); } } // namespace -BOOST_AUTO_TEST_CASE(extensions_3D) { - BOOST_REQUIRE( 20 == second_finish( multi::extensions_t<3> { {0, 10}, {0, 20}, {0, 30} } ) ); - BOOST_REQUIRE( 20 == second_finish( multi::extensions_t<3>( { {0, 10}, {0, 20}, {0, 30} } )) ); - BOOST_REQUIRE( 20 == second_finish( { {0, 10}, {0, 20}, {0, 30} } ) ); - - multi::extensions_t<3> const exts({0, 10}, {0, 20}, {0, 30}); - BOOST_REQUIRE( 20 == second_finish(exts ) ); -} - -BOOST_AUTO_TEST_CASE(extensions_to_linear) { - multi::extensions_t<3> exts{4, 5, 3}; - BOOST_REQUIRE( exts.to_linear(0, 0, 0) == 0 ); - BOOST_REQUIRE( exts.to_linear(0, 0, 1) == 1 ); - BOOST_REQUIRE( exts.to_linear(0, 0, 2) == 2 ); - BOOST_REQUIRE( exts.to_linear(0, 1, 0) == 3 ); - BOOST_REQUIRE( exts.to_linear(0, 1, 1) == 4 ); - BOOST_REQUIRE( exts.to_linear(0, 1, 2) == 5 ); - BOOST_REQUIRE( exts.to_linear(1, 0, 0) == 15 ); - - for(int eye = 0; eye != 4; ++eye) { - for(int jay = 0; jay != 5; ++jay) { - for(int kay = 0; kay != 3; ++kay) { - BOOST_REQUIRE(( exts.from_linear(exts.to_linear(eye, jay, kay)) == decltype(exts.from_linear(exts.to_linear(eye, jay, kay))){eye, jay, kay} )); - } - } - } - - BOOST_REQUIRE( exts.to_linear(4, 0, 0) == exts.num_elements() ); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(extensions_3D) + { + BOOST_TEST( 20 == second_finish( multi::extensions_t<3> { {0, 10}, {0, 20}, {0, 30} } ) ); + BOOST_TEST( 20 == second_finish( multi::extensions_t<3>( { {0, 10}, {0, 20}, {0, 30} } )) ); + BOOST_TEST( 20 == second_finish( { {0, 10}, {0, 20}, {0, 30} } ) ); - for(int idx = 0; idx != exts.num_elements(); ++idx) { - BOOST_REQUIRE( std::apply([&](auto... indices) { return exts.to_linear(indices...);}, exts.from_linear(idx)) == idx ); + multi::extensions_t<3> const exts({0, 10}, {0, 20}, {0, 30}); + BOOST_TEST( 20 == second_finish(exts) ); } -} -BOOST_AUTO_TEST_CASE(extensions_layout_to_linear) { - multi::array arr({40, 50, 80}); - auto&& sub = arr({10, 30}, {20, 32}, {60, 75}); - - for(int i = 0; i != 10; ++i) { - for(int j = 0; j != 12; ++j) { - for(int k = 0; k != 15; ++k) { - BOOST_REQUIRE( & sub.base() [sub.layout()(i, j, k)] == &sub(i, j, k) ); - BOOST_REQUIRE( &*(sub.base() + sub.layout()(i, j, k)) == &sub(i, j, k) ); + // BOOST_AUTO_TEST_CASE(extensions_to_linear) + { + multi::extensions_t<3> exts{4, 5, 3}; + BOOST_TEST( exts.to_linear(0, 0, 0) == 0 ); + BOOST_TEST( exts.to_linear(0, 0, 1) == 1 ); + BOOST_TEST( exts.to_linear(0, 0, 2) == 2 ); + BOOST_TEST( exts.to_linear(0, 1, 0) == 3 ); + BOOST_TEST( exts.to_linear(0, 1, 1) == 4 ); + BOOST_TEST( exts.to_linear(0, 1, 2) == 5 ); + BOOST_TEST( exts.to_linear(1, 0, 0) == 15 ); + + for(int eye = 0; eye != 4; ++eye) { + for(int jay = 0; jay != 5; ++jay) { + for(int kay = 0; kay != 3; ++kay) { // NOLINT(altera-unroll-loops) + BOOST_TEST(( exts.from_linear(exts.to_linear(eye, jay, kay)) == decltype(exts.from_linear(exts.to_linear(eye, jay, kay))){eye, jay, kay} )); + } } } - } -} - -BOOST_AUTO_TEST_CASE(extensions_layout_to_linear_2) { - multi::array arr({40, 50, 80}); - auto&& sub = arr({10, 30}, {20, 32}, {60, 75}); - auto const& rot = sub.rotated(); + BOOST_TEST( exts.to_linear(4, 0, 0) == exts.num_elements() ); - auto const [is, js, ks] = rot.extensions(); - for(auto i : is) { - for(auto j : js) { - for(auto k : ks) { - BOOST_REQUIRE( & rot.base() [rot.layout()(i, j, k)] == &rot(i, j, k) ); - BOOST_REQUIRE( &*(rot.base() + rot.layout()(i, j, k)) == &rot(i, j, k) ); - } + for(int idx = 0; idx != exts.num_elements(); ++idx) { // NOLINT(altera-unroll-loops) + BOOST_TEST( std::apply([&](auto... indices) { return exts.to_linear(indices...);}, exts.from_linear(idx)) == idx ); } } -} - -BOOST_AUTO_TEST_CASE(linearize) { - multi::array const arr({10, 20, 30}); - - BOOST_REQUIRE(( 25 % extensions(arr) == decltype( 25 % extensions(arr)){0, 0, 25} )); - BOOST_REQUIRE(( 55 % extensions(arr) == decltype( 55 % extensions(arr))(0, 1, 25) )); - BOOST_REQUIRE(( 655 % extensions(arr) == decltype( 655 % extensions(arr))(1, 1, 25) )); - BOOST_REQUIRE((1255 % extensions(arr) == decltype(1255 % extensions(arr))(2, 1, 25) )); - - auto const point = arr.extensions().from_linear(655); - // BOOST_REQUIRE( p == std::make_tuple(1, 1, 25) ); - using multi::detail::get; - BOOST_REQUIRE( get<0>(point) == 1 ); - BOOST_REQUIRE( get<1>(point) == 1 ); - BOOST_REQUIRE( get<2>(point) == 25 ); -} -BOOST_AUTO_TEST_CASE(layout_0) { - multi::array arr( -#if defined(__INTEL_COMPILER) or (defined(__GNUC__) and (__GNUC__ < 6)) - multi::extensions_t<3> -#endif - {51, 52, 53} - ); - BOOST_REQUIRE( size(arr) == 51 ); - BOOST_REQUIRE( arr.size() == 51 ); - - BOOST_REQUIRE( size(arr[0]) == 52 ); - BOOST_REQUIRE( arr[0].size() == 52 ); - - BOOST_REQUIRE( size(arr[0][0]) == 53 ); - BOOST_REQUIRE( arr[0][0].size() == 53 ); -} + // BOOST_AUTO_TEST_CASE(contiguous_layout) + { + std::vector vec(10, 99); // NOLINT(fuchsia-default-arguments-calls) + using ArrayRef = multi::array_ref>; + auto arr = ArrayRef({static_cast(vec.size())}, vec.data()); -BOOST_AUTO_TEST_CASE(layout_1) { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): testing feature - double arr[50][50][50]; - using multi::size; - BOOST_REQUIRE( size(arr) == 50 ); + BOOST_TEST( &arr[1] == &vec[1] ); - using multi::extension; - BOOST_REQUIRE(( extension(arr) == multi::index_extension{0, 50} )); - BOOST_REQUIRE(( extension(arr) == multi::iextension{0, 50} )); - BOOST_REQUIRE(( extension(arr) == multi::irange{0, 50} )); -} + static_assert( + std::is_base_of_v< + std::random_access_iterator_tag, ArrayRef::const_iterator::iterator_category> + ); -BOOST_AUTO_TEST_CASE(layout_2) { - std::array, 50>, 50> const arr{}; - using multi::size; - BOOST_REQUIRE( size(arr) == 50 ); + // #if (__cplusplus >= 202002L) + // static_assert( + // std::is_base_of_v< + // std::contiguous_iterator_tag, ArrayRef::const_iterator::iterator_category> + // ); + // #endif + } - using multi::extension; - BOOST_REQUIRE(( extension(arr) == multi::index_extension{0, 50} )); - BOOST_REQUIRE(( extension(arr) == multi::iextension{0, 50} )); - BOOST_REQUIRE(( extension(arr) == multi::irange{0, 50} )); -} + { + std::vector vec = {1, 2, 3, 4, 5}; // NOLINT(fuchsia-default-arguments-calls) + std::vector vec2(5); // NOLINT(fuchsia-default-arguments-calls) -BOOST_AUTO_TEST_CASE(layout_3) { - multi::array arr({50, 50}); - BOOST_REQUIRE( size(arr) == 50 ); - BOOST_REQUIRE( arr.size() == 50 ); + multi::array_ref> const arr(static_cast(vec.size()), vec.data()); - BOOST_REQUIRE( arr[0].sliced(10, 20).size() == 10 ); - BOOST_REQUIRE( size(arr[0].sliced(10, 20)) == 10 ); + auto&& arr_d = arr.dropped(1); + BOOST_TEST( arr_d.size() == arr.size() - 1 ); + BOOST_TEST( &arr_d[0] == &arr[1] ); - static_assert(decltype(arr(0, {10, 20}))::rank_v == 1, "!"); + auto&& arr_s = arr.sliced(1, 4); + BOOST_TEST( &arr_s[0] == &arr[1] ); - BOOST_REQUIRE( size(arr(0, {10, 20})) == 10 ); + static_assert( + std::is_base_of_v< + std::random_access_iterator_tag, + decltype(arr.cbegin())::iterator_category> + ); - BOOST_REQUIRE( arr.layout() == arr.layout() ); - BOOST_REQUIRE( not (arr.layout() < arr.layout()) ); -} +#if defined(__cplusplus) && (__cplusplus >= 202002L) && defined(__cpp_lib_ranges) && (!defined(__clang__) || __clang_major__ != 10) + static_assert( + std::is_base_of_v< + std::contiguous_iterator_tag, + decltype(arr.cbegin())::iterator_category> + ); -BOOST_AUTO_TEST_CASE(layout) { - { - multi::array const A2 = { - {1.0, 2.0, 3.0}, - {4.0, 5.0, 6.0}, - {7.0, 8.0, 9.0}, - }; + static_assert(std::contiguous_iterator); + static_assert(std::contiguous_iterator); - BOOST_REQUIRE( size(A2) == 3 ); + static_assert(std::contiguous_iterator); + static_assert(std::contiguous_iterator); - multi::array B2( -#if defined(__INTEL_COMPILER) or (defined(__GNUC__) and (__GNUC__ < 6)) - multi::extensions_t<2> + int const* beg(arr.begin()); + BOOST_TEST( beg == &arr.front() ); #endif - {4, 4} - ); - BOOST_REQUIRE( size(B2) == 4 ); - B2[3][3] = 99.0; - - auto B2copy = +B2({0, 2}, {0, 2}); - - BOOST_REQUIRE( &B2copy[1][1] != &B2({0, 2}, {0, 2})[1][1] ); - std::array, - 2> - B2blk = {{ - {{B2({0, 2}, {0, 2}), B2({0, 2}, {2, 4})}}, - {{B2({2, 4}, {0, 2}), B2({2, 4}, {2, 4})}}, - }}; + // std::copy(arr.begin(), arr.end(), vec2.begin()); + BOOST_TEST( arr.cbegin().stride() == 1 ); + BOOST_TEST( arr.cend().stride() == 1 ); - BOOST_REQUIRE( &B2blk[1][1][1][1] == &B2[3][3] ); - } - { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type - double arr[3][4][5] = {}; - using multi::dimensionality; - static_assert(dimensionality(arr) == 3, "!"); - using multi::extensions; - auto xA = extensions(arr); + auto size = arr.cend() - arr.cbegin(); + BOOST_TEST( size == 5 ); + BOOST_TEST( arr.size() == 5 ); - BOOST_REQUIRE( size(std::get<0>(xA)) == 3 ); - BOOST_REQUIRE( size(std::get<1>(xA)) == 4 ); - BOOST_REQUIRE( size(std::get<2>(xA)) == 5 ); + BOOST_TEST( arr.cbegin().stride() == 1 ); - static_assert(multi::stride(arr) == 20); + BOOST_TEST( arr.cend().base() - arr.cbegin().base() == 5 ); - static_assert(multi::stride(arr[1]) == 5); - static_assert(multi::stride(arr[0][0]) == 1); + BOOST_TEST( (arr.cend().base() - arr.cbegin().base()) % arr.cbegin().stride() == 0 ); - multi::array AA({3, 4, 5}); - using multi::layout; - BOOST_REQUIRE( layout(AA) == layout(arr) ); + // std::copy(arr.data_elements(), std::next(arr.data_elements(), arr.num_elements()), vec2.begin()); + BOOST_TEST( &*arr.cbegin() == arr.data_elements() ); - BOOST_REQUIRE( AA .stride() == 20 ); - } - { - std::array, 4>, 3> arr = {}; - static_assert(multi::dimensionality(arr) == 3); + // vvv this is UB, never dereference an end iterator + // BOOST_TEST( &*arr.cend() == std::next(arr.data_elements(), arr.num_elements()) ); - using multi::extensions; - auto xA = extensions(arr); - using std::get; - BOOST_REQUIRE( size(std::get<0>(xA)) == 3 ); - BOOST_REQUIRE( size(std::get<1>(xA)) == 4 ); - BOOST_REQUIRE( size(std::get<2>(xA)) == 5 ); + BOOST_TEST( *arr.cbegin() == 1 ); + BOOST_TEST( *std::next(arr.cbegin(), 1) == 2 ); - multi::array AA({3, 4, 5}); - using multi::layout; - BOOST_REQUIRE( layout(AA) == layout(arr) ); + // multi::what(arr.cbegin()); + std::copy(arr.cbegin(), arr.cbegin() + arr.size(), vec2.begin()); + // std::copy_n(arr.begin(), arr.size(), vec2.begin()); + // std::copy(arr.cbegin(), arr.cend(), vec2.begin()); + // std::copy_n(arr.cbegin(), arr.size(), vec2.begin()); - BOOST_REQUIRE( AA.stride() == 20 ); + // for(auto idx : arr.extension()) { // NOLINT(altera-unroll-loops) + // vec2[static_cast(idx)] = arr[idx]; + // } - static_assert(multi::stride(arr) == 20); + BOOST_TEST( vec2[0] == 1 ); + BOOST_TEST( vec2[1] == 2 ); + BOOST_TEST( vec2[2] == 3 ); + BOOST_TEST( vec2[3] == 4 ); + BOOST_TEST( vec2[4] == 5 ); - BOOST_REQUIRE( multi::stride(arr[0]) == 5 ); - BOOST_REQUIRE( multi::stride(arr[1]) == 5 ); - BOOST_REQUIRE( multi::stride(arr[0][0]) == 1 ); + BOOST_TEST( vec == vec2 ); } + { - multi::array const B2 = { - {1.0}, - {2.0}, - {3.0}, + multi::array d2D = { + {150.0, 16.0, 17.0, 18.0, 19.0}, + { 30.0, 1.0, 2.0, 3.0, 4.0}, + {100.0, 11.0, 12.0, 13.0, 14.0}, + { 50.0, 6.0, 7.0, 8.0, 9.0}, }; - BOOST_REQUIRE( size(B2) == 3 ); - BOOST_REQUIRE( size(rotated(B2)) == 1 ); - BOOST_REQUIRE( size(B2[0]) == 1); - BOOST_REQUIRE( B2 .stride() == 1 ); - BOOST_REQUIRE( B2[0].stride() == 1 ); - } -} -BOOST_AUTO_TEST_CASE(multi_layout_with_offset) { - { - multi::layout_t<1> const l1(multi::iextension(2, 5)); - BOOST_REQUIRE( l1.extension().start() == 2 ); - BOOST_REQUIRE( l1.extension().finish() == 5 ); + // #if __has_cpp_attribute(no_unique_address) >=201803L and not defined(__NVCC__) and not defined(__PGI) + // BOOST_TEST( sizeof(d2D)==sizeof(double*)+7*sizeof(std::size_t) ); + // #endif + BOOST_TEST( d2D.is_compact() ); + BOOST_TEST( d2D.rotated().is_compact() ); + BOOST_TEST( d2D[3].is_compact() ); + BOOST_TEST( !(d2D.rotated()[2].is_compact()) ); } { - boost::multi::layout_t<2>::extensions_type const exts{ - multi::iextension(2, 5), - multi::iextension(0, 5)}; - multi::layout_t<2> const l2(exts); - BOOST_REQUIRE( l2.extension().start() == std::get<0>(exts).start() ); - BOOST_REQUIRE( l2.extension().finish() == std::get<0>(exts).finish() ); + multi::array d2D({5, 3}); + BOOST_TEST( d2D.is_compact() ); + BOOST_TEST( d2D.rotated().is_compact() ); + BOOST_TEST( d2D[3].is_compact() ); + BOOST_TEST( !d2D.rotated()[2].is_compact() ); } - { - multi::layout_t<2> const l2({multi::iextension(0, 3), multi::iextension(2, 7)}); - BOOST_REQUIRE( std::get<1>(l2.extensions()).start() == 2 ); - BOOST_REQUIRE( std::get<1>(l2.extensions()).finish() == 7 ); - } -} -BOOST_AUTO_TEST_CASE(multi_layout_part1) { + // BOOST_AUTO_TEST_CASE(extensions_layout_to_linear) { - multi::layout_t<0> const lyt; - static_assert(decltype(lyt)::rank_v == 0); - BOOST_REQUIRE( num_elements(lyt) == 1 ); - } - { - multi::iextensions<0> const exts{}; - multi::layout_t<0> const lyt(exts); - BOOST_REQUIRE(lyt.num_elements() == 1); - } - { - multi::layout_t<1> const lyt{}; - static_assert(decltype(lyt)::rank_v == 1); - BOOST_REQUIRE( num_elements(lyt) == 0 ); - BOOST_REQUIRE( size(lyt) == 0 ); - BOOST_REQUIRE( size(extension(lyt))==0 ); - BOOST_REQUIRE( stride(lyt)!=0 ); - BOOST_REQUIRE( is_empty(lyt) ); - } - { - multi::layout_t<2> const lyt({2, 10}); - static_assert(decltype(lyt)::rank_v == 2); - BOOST_REQUIRE( num_elements(lyt) == 20 ); - BOOST_REQUIRE( size(lyt) == 2 ); - BOOST_REQUIRE( size(extension(lyt))==2 ); - BOOST_REQUIRE( stride(lyt)==10 ); - BOOST_REQUIRE( not is_empty(lyt) ); - } - { - multi::layout_t<1> const lyt(multi::iextensions<1>{20}); - static_assert(decltype(lyt)::rank_v == 1, "!"); - BOOST_REQUIRE( num_elements(lyt) == 20 ); - BOOST_REQUIRE( size(lyt) == 20 ); - BOOST_REQUIRE( stride(lyt) == 1 ); - } -} + multi::array arr({40, 50, 80}); -BOOST_AUTO_TEST_CASE(multi_layout_part2) { - { - multi::layout_t<1> const lyt(multi::iextensions<1>{1}); - static_assert(decltype(lyt)::rank_v == 1); - BOOST_REQUIRE( num_elements(lyt) == 1 ); - BOOST_REQUIRE( size(lyt) == 1 ); - BOOST_REQUIRE( stride(lyt) == 1 ); - } - { - multi::layout_t<2> const lyt({1, 10}); - static_assert(decltype(lyt)::rank_v == 2); - BOOST_REQUIRE( num_elements(lyt) == 10 ); - BOOST_REQUIRE( size(lyt) == 1); - BOOST_REQUIRE( not is_empty(lyt) ); - BOOST_REQUIRE( size(extension(lyt))==1 ); - BOOST_REQUIRE( stride(lyt)== 10 ); // std::numeric_limits::max() ); - - using std::get; - BOOST_REQUIRE( get<0>(strides(lyt)) == 10); - BOOST_REQUIRE( get<1>(strides(lyt)) == 1 ); - } -} + auto&& sub = arr({10, 30}, {20, 32}, {60, 75}); -BOOST_AUTO_TEST_CASE(multi_layout_part3) { - { - multi::layout_t<2> const lyt({10, 1}); - static_assert(decltype(lyt)::rank_v == 2, "!"); - BOOST_REQUIRE( num_elements(lyt) == 10 ); - BOOST_REQUIRE( size(lyt) == 10 ); - using std::get; - BOOST_REQUIRE( get<0>(strides(lyt)) == 1 ); - BOOST_REQUIRE( get<1>(strides(lyt)) == 1 ); - } - { - multi::layout_t<2> const lyt{}; - BOOST_REQUIRE( dimensionality(lyt)==2 ); - BOOST_REQUIRE( num_elements(lyt) == 0 ); - BOOST_REQUIRE( size(lyt) == 0 ); - BOOST_REQUIRE( size(extension(lyt))==0 ); - BOOST_REQUIRE( stride(lyt)!=0 ); - BOOST_REQUIRE( is_empty(lyt) ); - } - { - multi::layout_t<3> const lyt{}; - BOOST_REQUIRE( num_elements(lyt) == 0 ); - } - { - multi::layout_t<3> const lyt({ - {0, 10}, - {0, 10}, - {0, 10}, - }); - BOOST_REQUIRE( num_elements(lyt) == 1000 ); - } - { - multi::layout_t<3> const lyt({{10}, {10}, {10}}); - BOOST_REQUIRE( num_elements(lyt) == 1000 ); - } - { - multi::layout_t<3> const lyt({10, 10, 10}); - BOOST_REQUIRE( num_elements(lyt) == 1000 ); - } - { - multi::layout_t<3> const lyt({ - multi::index_extension{0, 10}, - {0, 10}, - {0, 10}, - }); - BOOST_REQUIRE( num_elements(lyt) == 1000 ); - } - { - multi::layout_t<3> const lyt(multi::layout_t<3>::extensions_type{ - {0, 10}, - {0, 10}, - {0, 10}, - }); - BOOST_REQUIRE( num_elements(lyt) == 1000 ); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // TODO(correaa) use checked span +#endif + + for(int i = 0; i != 10; ++i) { + for(int j = 0; j != 12; ++j) { + for(int k = 0; k != 15; ++k) { // NOLINT(altera-unroll-loops) + BOOST_TEST( & sub.base() [sub.layout()(i, j, k)] == &sub(i, j, k) ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + BOOST_TEST( &*(sub.base() + sub.layout()(i, j, k)) == &sub(i, j, k) ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + } + } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } -} -BOOST_AUTO_TEST_CASE(layout_to_offset) { - multi::layout_t<3> const lyt({10, 20, 30}); - multi::array const arr({10, 20, 30}); - BOOST_REQUIRE( lyt[0][0][0] == &arr[0][0][0] - arr.data_elements() ); - BOOST_REQUIRE( lyt[0][0][1] == &arr[0][0][1] - arr.data_elements() ); - BOOST_REQUIRE( lyt[0][0][2] == &arr[0][0][2] - arr.data_elements() ); + // BOOST_AUTO_TEST_CASE(extensions_layout_to_linear_2) + { + multi::array arr( +#ifdef _MSC_VER // problem with MSVC 14.3 c++17 + multi::extensions_t<3> +#endif + {40, 50, 80} + ); - BOOST_TEST_REQUIRE(lyt[0][1][2] == &arr[0][1][2] - arr.data_elements()); - BOOST_TEST_REQUIRE(lyt[3][1][2] == &arr[3][1][2] - arr.data_elements()); -} + auto&& sub = arr({10, 30}, {20, 32}, {60, 75}); -BOOST_AUTO_TEST_CASE(layout_to_offset_sub) { - multi::array arr({10, 20, 30}); + auto const& rot = sub.rotated(); - auto&& sub = arr({2, 6}, {4, 8}, {10, 20}); + auto const [is, js, ks] = rot.extensions(); - auto const lyt = sub.layout(); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif - BOOST_REQUIRE( lyt[0][0][0] == &sub[0][0][0] - base(sub) ); - BOOST_REQUIRE( lyt[0][0][1] == &sub[0][0][1] - base(sub) ); - BOOST_REQUIRE( lyt[0][0][2] == &sub[0][0][2] - base(sub) ); - BOOST_REQUIRE( lyt[0][1][2] == &sub[0][1][2] - base(sub) ); - BOOST_REQUIRE( lyt[3][1][2] == &sub[3][1][2] - base(sub) ); -} + for(auto const i : is) { + for(auto const j : js) { + for(auto const k : ks) { // NOLINT(altera-unroll-loops) + BOOST_TEST( & rot.base() [rot.layout()(i, j, k)] == &rot(i, j, k) ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + BOOST_TEST( &*(rot.base() + rot.layout()(i, j, k)) == &rot(i, j, k) ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + } + } + } -BOOST_AUTO_TEST_CASE(continued_part1) { - { - multi::layout_t<3> const lyt(multi::layout_t<3>::extensions_type{ - {0, 10}, - {0, 10}, - {0, 10}, - }); - BOOST_REQUIRE( num_elements(lyt) == 1000); - } - { - multi::layout_t<3> const lyt({ - multi::iextension{0, 10}, - multi::iextension{0, 10}, - multi::iextension{0, 10}, - }); - BOOST_REQUIRE(lyt.num_elements() == 1000); - } - { - multi::layout_t<3> const lyt({multi::iextension{10}, multi::iextension{10}, multi::iextension{10}}); - BOOST_REQUIRE( num_elements(lyt) == 1000); - } - { - multi::layout_t<3> const lyt({10, 10, multi::iextension{10}}); - BOOST_REQUIRE( num_elements(lyt) == 1000 ); - } - { - multi::layout_t<1> const lyt; - BOOST_REQUIRE( size(lyt) == 0 ); - } - { - multi::layout_t<1> lyt({ - {0, 10}, - }); - BOOST_REQUIRE( size(lyt) == 10 ); - BOOST_REQUIRE( extension(lyt).start () == 0 ); - BOOST_REQUIRE( extension(lyt).finish() == 10 ); - - lyt.reindex(1); - BOOST_REQUIRE( size(lyt) == 10 ); - BOOST_REQUIRE( extension(lyt).start () == 1 ); - BOOST_REQUIRE( extension(lyt).finish() == 11 ); - } - { - multi::layout_t<2> const lyt; - BOOST_REQUIRE( size(lyt) == 0 ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } - { - multi::layout_t<2> lyt({ - {0, 10}, - {0, 20}, - }); - BOOST_REQUIRE( size(lyt) == 10 ); - BOOST_REQUIRE( extension(lyt).start () == 0 ); - BOOST_REQUIRE( extension(lyt).finish() == 10 ); - - lyt.reindex(1); - BOOST_REQUIRE( extension(lyt).start () == 1 ); - BOOST_REQUIRE( extension(lyt).finish() == 11 ); - lyt.rotate().reindex(3).unrotate(); - BOOST_TEST_REQUIRE( extension(lyt).start () == 1 ); - BOOST_TEST_REQUIRE( extension(lyt).finish() == 11 ); + // BOOST_AUTO_TEST_CASE(linearize) + { + multi::array const arr( +#ifdef _MSC_VER // problem with MSVC 14.3 c++17 + multi::extensions_t<3> +#endif + {10, 20, 30} + ); - BOOST_TEST_REQUIRE( std::get<0>(extensions(lyt)).start () == 1 ); - BOOST_TEST_REQUIRE( std::get<1>(extensions(lyt)).start () == 3 ); - BOOST_TEST_REQUIRE( std::get<1>(extensions(lyt)).finish() == 23 ); + BOOST_TEST(( 25 % extensions(arr) == decltype( 25 % extensions(arr)){0, 0, 25} )); + BOOST_TEST(( 55 % extensions(arr) == decltype( 55 % extensions(arr))(0, 1, 25) )); + BOOST_TEST(( 655 % extensions(arr) == decltype( 655 % extensions(arr))(1, 1, 25) )); + BOOST_TEST((1255 % extensions(arr) == decltype(1255 % extensions(arr))(2, 1, 25) )); + + auto const point = arr.extensions().from_linear(655); + // BOOST_TEST( p == std::make_tuple(1, 1, 25) ); + using multi::detail::get; + BOOST_TEST( get<0>(point) == 1 ); + BOOST_TEST( get<1>(point) == 1 ); + BOOST_TEST( get<2>(point) == 25 ); } -} -BOOST_AUTO_TEST_CASE(continued_part2) { - multi::layout_t<3> const lyt({ - {0, 10}, - {0, 20}, - {0, 30}, - }); - - BOOST_REQUIRE( not lyt.empty() ); + // BOOST_AUTO_TEST_CASE(layout_tuple_2d) + { + multi::extensions_t<2> const x1({51, 52}); + multi::extensions_t<2> const x2({multi::iextension(0, 51), multi::iextension(0, 52)}); - BOOST_REQUIRE( stride(lyt) == lyt.stride() ); - BOOST_REQUIRE( offset(lyt) == lyt.offset() ); - BOOST_REQUIRE( nelems(lyt) == lyt.nelems() ); + BOOST_TEST( x1 == x2 ); - BOOST_REQUIRE( stride(lyt) == 20*30L ); - BOOST_REQUIRE( offset(lyt) == 0 ); - BOOST_REQUIRE( nelems(lyt) == 10*20L*30L ); + multi::extensions_t<2> const x3(std::make_tuple(multi::iextension{0, 51}, multi::iextension{0, 52})); + BOOST_TEST( x1 == x3 ); - BOOST_REQUIRE( lyt.stride() == stride(lyt) ); - BOOST_REQUIRE( lyt.offset() == offset(lyt) ); - BOOST_REQUIRE( lyt.nelems() == nelems(lyt) ); + multi::extensions_t<2> const x4 = std::make_tuple(multi::iextension{0, 51}, multi::iextension{0, 52}); + BOOST_TEST( x1 == x4 ); - using boost::multi::detail::get; - BOOST_REQUIRE( get<1>(lyt.strides()) == 30 ); - BOOST_REQUIRE( get<1>(lyt.offsets()) == 0 ); - BOOST_REQUIRE( get<1>(lyt.nelemss()) == 20*30L ); + multi::extensions_t<2> const x5 = std::tuple{ + multi::iextension{0, 51}, + multi::iextension{0, 52}, + }; + BOOST_TEST( x1 == x5 ); - BOOST_REQUIRE( get<2>(lyt.strides()) == 1 ); - BOOST_REQUIRE( get<2>(lyt.offsets()) == 0 ); - BOOST_REQUIRE( get<2>(lyt.nelemss()) == 30 ); -} + multi::extensions_t<2> const x6 = std::tuple{51, 52}; + BOOST_TEST( x1 == x6 ); -BOOST_AUTO_TEST_CASE(continued_part3) { - multi::layout_t<3> const lyt({ - {0, 10}, - {0, 20}, - {0, 30}, - }); + multi::extensions_t<2> const x7{51, 52}; + BOOST_TEST( x1 == x7 ); - BOOST_REQUIRE( lyt.num_elements() == num_elements(lyt) ); - BOOST_REQUIRE( lyt.size() == size(lyt) ); - BOOST_REQUIRE( lyt.extension() == extension(lyt) ); + multi::extensions_t<2> const x8 = {51, 52}; + BOOST_TEST( x1 == x8 ); - BOOST_REQUIRE( num_elements(lyt) == 10*20L*30L ); - BOOST_REQUIRE( size(lyt) == 10 ); - BOOST_REQUIRE( extension(lyt).first() == 0 ); - BOOST_REQUIRE( extension(lyt).last() == 10 ); + auto const x9 = multi::extensions_t<2>{51, 52}; + BOOST_TEST( x1 == x9 ); - BOOST_REQUIRE( std::get<0>(lyt.extensions()) == lyt.extension() ); + // multi::extensions_t x10{51, 52, 53}; // TODO(correaa) should it work? + // BOOST_TEST( x1 == x10 ); + } - boost::multi::extensions_t<2> const exts2; + // BOOST_AUTO_TEST_CASE(layout_tuple_3d) + { + multi::extensions_t<3> const x1({51, 52, 53}); + multi::extensions_t<3> const x2({ + multi::iextension{0, 51}, + multi::iextension{0, 52}, + multi::iextension{0, 53}, + }); + BOOST_TEST( x1 == x2 ); - using boost::multi::detail::get; - using std::get; + multi::extensions_t<3> const x3(std::make_tuple(multi::iextension{0, 51}, multi::iextension{0, 52}, multi::iextension{0, 53})); + BOOST_TEST( x1 == x3 ); - BOOST_REQUIRE( get<0>(exts2).is_empty() ); + multi::extensions_t<3> const x4 = std::make_tuple(multi::iextension{0, 51}, multi::iextension{0, 52}, multi::iextension{0, 53}); + BOOST_TEST( x1 == x4 ); - // BOOST_REQUIRE( std::get<0>(L.sizes()) == L.size(0) ); - // BOOST_REQUIRE( std::get<0>(L.extensions()) == L.extension(0) ); + multi::extensions_t<3> const x5 = std::tuple{ + multi::iextension{0, 51}, + multi::iextension{0, 52}, + multi::iextension{0, 53}, + }; + BOOST_TEST( x1 == x5 ); - BOOST_REQUIRE(( get<0>(lyt.extensions()) == multi::index_extension{0, 10} )); + multi::extensions_t<3> const x6 = std::tuple{51, 52, 53}; + BOOST_TEST( x1 == x6 ); - BOOST_REQUIRE( get<0>(lyt.extensions()).first() == 0 ); - BOOST_REQUIRE( get<0>(lyt.extensions()).last() == 10 ); + multi::extensions_t<3> const x7{51, 52, 53}; + BOOST_TEST( x1 == x7 ); - // BOOST_REQUIRE( L.size(1) == 20 ); - BOOST_REQUIRE( get<1>(lyt.extensions()).first() == 0 ); - BOOST_REQUIRE( get<1>(lyt.extensions()).last() == 20 ); + // multi::extensions_t x8{51, 52, 53}; // TODO(correaa) should it work? + // BOOST_TEST( x1 == x8 ); + } - // BOOST_REQUIRE( L.size(2) == 30 ); - BOOST_REQUIRE( get<2>(lyt.extensions()).first() == 0 ); - BOOST_REQUIRE( get<2>(lyt.extensions()).last() == 30 ); + // BOOST_AUTO_TEST_CASE(layout_0) + { + multi::array arr( +#ifdef _MSC_VER // problem with MSVC 14.3 c++17 + multi::extensions_t<3> +#endif + {51, 52, 53} + ); - using std::get; - BOOST_REQUIRE( get<0>(strides(lyt)) == lyt.stride() ); + BOOST_TEST( size(arr) == 51 ); + BOOST_TEST( arr.size() == 51 ); - auto const& strides = lyt.strides(); - BOOST_REQUIRE( get<0>(strides) == lyt.stride() ); -} + BOOST_TEST( size(arr[0]) == 52 ); + BOOST_TEST( arr[0].size() == 52 ); -BOOST_AUTO_TEST_CASE(continued) { - { - multi::layout_t<3> const lyt; - BOOST_REQUIRE( size(lyt) == 0 ); - } - { - multi::layout_t<3> const lyt({ - {0, 10}, - {0, 20}, - {0, 30}, - }); - BOOST_REQUIRE( stride(lyt) == 20*30L ); - } - { - multi::layout_t<1> const lyt({ - {0, 10}, - }); - BOOST_REQUIRE( extension(lyt).first() == 0 ); - BOOST_REQUIRE( extension(lyt).last() == 10 ); - } - { - multi::layout_t<1> const lyt({ - {8, 18}, - }); - BOOST_REQUIRE( extension(lyt).first() == 8 ); - BOOST_REQUIRE( extension(lyt).last() == 18 ); - } - { - multi::layout_t<2> const lyt({ - {0, 10}, - {0, 20}, - }); - BOOST_REQUIRE( extension(lyt).first() == 0 ); - BOOST_REQUIRE( extension(lyt).last() == 10 ); - } - { - multi::layout_t<2> const lyt({ - { 0, 10}, - {11, 31}, - }); - BOOST_REQUIRE( size(lyt) == 10 ); - BOOST_REQUIRE( stride(lyt) == 20 ); - BOOST_REQUIRE( offset(lyt) == 0 ); - } - { - multi::layout_t<2> const lyt({ - {8, 18}, - {0, 20}, - }); - BOOST_REQUIRE( size(lyt) == 10 ); - BOOST_REQUIRE( stride(lyt) == 20 ); - } - { - multi::layout_t<3> const lyt({ - { 0, 3}, - { 0, 5}, - {10, 17}, - }); - BOOST_REQUIRE( stride(lyt) == 5*7L ); - BOOST_REQUIRE( stride(lyt.sub().sub()) == 1 ); - } - { - multi::layout_t<3> const lyt({ - {0, 10}, - {0, 20}, - {0, 30}, - }); - BOOST_REQUIRE( stride(lyt) == 20*30L ); - BOOST_REQUIRE( offset(lyt) == 0 ); - BOOST_REQUIRE( nelems(lyt) == 10*20L*30L ); - } - { - multi::layout_t<3> const lyt({ - {10, 20}, - {10, 30}, - {10, 40}, - }); - BOOST_REQUIRE( stride(lyt) == 20*30L ); + BOOST_TEST( size(arr[0][0]) == 53 ); + BOOST_TEST( arr[0][0].size() == 53 ); } - { - auto const ttt = boost::multi::tuple{1, 2, 3}; - auto const arr = std::apply([](auto... elems) { return std::array{{elems...}}; }, ttt); - BOOST_REQUIRE(arr[1] == 2); - } -} - -// BOOST_AUTO_TEST_CASE(tuple_zip_test) { // TODO(correaa) make it work -// auto t1 = std::make_tuple( 1, 2, 3); -// auto t2 = std::make_tuple(10, 20, 30); -// auto t3 = std::make_tuple(std::string{"10"}, std::string{"20"}, std::string{"30"}); -// auto t123 = boost::multi::detail::tuple_zip(t1, t2, t3); -// BOOST_REQUIRE( std::get<2>(std::get<0>(t123)) == std::string{"10"} ); -// } -BOOST_AUTO_TEST_CASE(extensions_from_linear_1d) { - multi::extensions_t<1> const exts{11}; - - auto ijk = exts.from_linear(9); + // BOOST_AUTO_TEST_CASE(layout_1) + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): testing feature + double arr[25][25][25]; // this can overflow the stack: double arr[50][50][50]; 50*50*50*8bytes = 1MB - using multi::detail::get; - BOOST_TEST_REQUIRE( get<0>(ijk) == 9 ); + using multi::size; + BOOST_TEST( size(arr) == 25 ); - multi::layout_t<1> const lyt{exts}; - BOOST_TEST_REQUIRE( lyt[get<0>(ijk)] == 9 ); - BOOST_TEST_REQUIRE( lyt(get<0>(ijk)) == 9 ); + using multi::extension; - BOOST_TEST_REQUIRE( lyt(std::get<0>(lyt.extensions().from_linear(9))) == 9 ); + BOOST_TEST(( extension(arr) == multi::index_extension{0, 25} )); + BOOST_TEST(( extension(arr) == multi::iextension {0, 25} )); + // BOOST_TEST(( extension(arr) == multi::irange{0, 25} )); + } - BOOST_TEST_REQUIRE( std::apply(lyt, lyt.extensions().from_linear(9)) == 9 ); -} + // BOOST_AUTO_TEST_CASE(layout_2) + { + std::array, 25>, 25> const arr{}; + using multi::size; + BOOST_TEST( size(arr) == 25 ); -BOOST_AUTO_TEST_CASE(extensions_from_linear_2d_structured_binding) { - multi::extensions_t<2> const exts{3, 5}; - auto [eye, jay] = exts.from_linear(7); + using multi::extension; + BOOST_TEST(( extension(arr) == multi::index_extension{0, 25} )); + BOOST_TEST(( extension(arr) == multi::iextension {0, 25} )); + } - BOOST_TEST_REQUIRE( eye == 1 ); - BOOST_TEST_REQUIRE( jay == 2 ); - // BOOST_TEST_REQUIRE( std::apply(l, l.extensions().from_linear(9)) == 9 ); -} + // BOOST_AUTO_TEST_CASE(layout_3) + { + multi::array arr( +#ifdef _MSC_VER // problem with MSVC 14.3 c++17 + multi::extensions_t<2> +#endif + {50, 50} + ); + BOOST_TEST( size(arr) == 50 ); + BOOST_TEST( arr.size() == 50 ); -BOOST_AUTO_TEST_CASE(extensions_from_linear_2d_std_get) { - multi::extensions_t<2> const exts{3, 5}; - auto eye = std::get<0>(exts.from_linear(7)); - auto jay = std::get<1>(exts.from_linear(7)); - BOOST_TEST_REQUIRE( eye == 1 ); - BOOST_TEST_REQUIRE( jay == 2 ); -} + BOOST_TEST( arr[0].sliced(10, 20).size() == 10 ); + BOOST_TEST( size(arr[0].sliced(10, 20)) == 10 ); -BOOST_AUTO_TEST_CASE(extensions_from_linear_2d_std_get_using) { - multi::extensions_t<2> const exts{3, 5}; - using std::get; - auto fl = exts.from_linear(7L); - auto const eye = get<0>(fl); - auto const jay = get<1>(fl); - BOOST_TEST_REQUIRE( eye == 1 ); - BOOST_TEST_REQUIRE( jay == 2 ); -} + static_assert(decltype(arr(0, {10, 20}))::rank_v == 1); -BOOST_AUTO_TEST_CASE(extensions_from_linear_2d_get_using) { - multi::extensions_t<2> const exts{3, 5}; + BOOST_TEST( size(arr(0, {10, 20})) == 10 ); - using multi::detail::get; + BOOST_TEST( arr.layout() == arr.layout() ); + BOOST_TEST( !(arr.layout() < arr.layout()) ); - auto eye = get<0>(exts.from_linear(7)); - auto jay = get<1>(exts.from_linear(7)); - BOOST_TEST_REQUIRE( eye == 1 ); - BOOST_TEST_REQUIRE( jay == 2 ); -} + // auto bl = arr.layout().flatten(); + auto const& barr = arr.flattened(); + BOOST_TEST( &barr[10] == &arr[0][10] ); + } + { + multi::array arr({6, 10}); -BOOST_AUTO_TEST_CASE(extensions_from_linear_2d) { - multi::extensions_t<2> const exts{3, 5}; + auto const& barr = arr.strided(2).flattened(); - auto ij = exts.from_linear(7); + BOOST_TEST( &barr [0] == &arr[0][0] ); + BOOST_TEST( &barr [1] == &arr[0][1] ); + // ... + BOOST_TEST( &barr [9] == &arr[0][9] ); - using multi::detail::get; + BOOST_TEST( &barr[10] == &arr[2][0] ); + BOOST_TEST( &barr[11] == &arr[2][1] ); + BOOST_TEST( &barr[12] == &arr[2][2] ); + // ... + BOOST_TEST( &barr[19] == &arr[2][9] ); - BOOST_TEST_REQUIRE( get<0>(ij) == 1 ); - BOOST_TEST_REQUIRE( get<1>(ij) == 2 ); + BOOST_TEST( &barr[20] == &arr[4][0] ); + BOOST_TEST( &barr[21] == &arr[4][1] ); + BOOST_TEST( &barr[22] == &arr[4][2] ); + // ... + BOOST_TEST( &barr[29] == &arr[4][9] ); - multi::layout_t<2> const lyt{exts}; - BOOST_TEST_REQUIRE( lyt[get<0>(ij)][get<1>(ij)] == 7 ); -} + BOOST_TEST( arr.num_elements() == 60 ); + BOOST_TEST( barr.size() == 30 ); + } + { + multi::array arr({6, 10}); + + auto const& barr = arr.strided(2).transposed().strided(2).transposed().flattened(); + + BOOST_TEST( &barr [0] == &arr[0][0] ); + BOOST_TEST( &barr [1] == &arr[0][2] ); + BOOST_TEST( &barr [2] == &arr[0][4] ); + BOOST_TEST( &barr [3] == &arr[0][6] ); + BOOST_TEST( &barr [4] == &arr[0][8] ); + + BOOST_TEST( &barr [5] == &arr[2][0] ); + BOOST_TEST( &barr [6] == &arr[2][2] ); + BOOST_TEST( &barr [7] == &arr[2][4] ); + BOOST_TEST( &barr [8] == &arr[2][6] ); + BOOST_TEST( &barr [9] == &arr[2][8] ); + + BOOST_TEST( &barr [10] == &arr[4][0] ); + BOOST_TEST( &barr [11] == &arr[4][2] ); + BOOST_TEST( &barr [12] == &arr[4][4] ); + BOOST_TEST( &barr [13] == &arr[4][6] ); + BOOST_TEST( &barr [14] == &arr[4][8] ); + + BOOST_TEST( arr.num_elements() == 60 ); + BOOST_TEST( barr.size() == 15 ); + } -BOOST_AUTO_TEST_CASE(extensions_from_linear_3d_std_get) { - multi::extensions_t<3> const exts{11, 13, 17}; + // BOOST_AUTO_TEST_CASE(layout_AA) + { + multi::array const A2 = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + }; - BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear( 0)) == 0 ); - BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear( 0)) == 0 ); - BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear( 0)) == 0 ); +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) +#if !defined(__clang_major__) || ((__clang_major__ < 14) && (__clang_major__ != 10)) +#if !defined(__NVCC__) +#if !defined(_MSC_VER) + static_assert(std::ranges::random_access_range); - BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear( 1)) == 0 ); - BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear( 1)) == 0 ); - BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear( 1)) == 1 ); + auto tiA2 = std::views::transform( + // A2.extension(), + std::views::iota(0L, A2.size()), + [](auto idx) noexcept { return idx; } + ); + BOOST_TEST( *tiA2.begin() == 0 ); + BOOST_TEST( tiA2[0] == 0 ); +#endif +#endif +#endif +#endif - BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear(16)) == 0 ); - BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear(16)) == 0 ); - BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear(16)) == 16 ); + BOOST_TEST( size(A2) == 3 ); - BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear(17)) == 0 ); - BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear(17)) == 1 ); - BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear(17)) == 0 ); + multi::array B2({4, 4}, 5); - BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear(18)) == 0 ); - BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear(18)) == 1 ); - BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear(18)) == 1 ); + BOOST_TEST( size(B2) == 4 ); + B2[3][3] = 99; - multi::layout_t<3> const lyt{exts}; + BOOST_TEST( B2[3][3] == 99 ); // cppcheck-suppress knownConditionTrueFalse ; for test - using std::get; - BOOST_TEST_REQUIRE( lyt[get<0>(exts.from_linear(19))][get<1>(exts.from_linear(19))][get<2>(exts.from_linear(19))] == 19 ); - BOOST_TEST_REQUIRE( lyt(get<0>(exts.from_linear(19)), get<1>(exts.from_linear(19)), get<2>(exts.from_linear(19))) == 19 ); -} + multi::array B2copy{B2({0, 2}, {0, 2})}; -BOOST_AUTO_TEST_CASE(extensions_from_linear_3d_std_get_using) { - multi::extensions_t<3> const exts{11, 13, 17}; + BOOST_TEST( &B2copy[1][1] != &B2({0, 2}, {0, 2})[1][1] ); - using std::get; + auto B2copy2 = B2({0, 2}, {0, 2}).decay(); - BOOST_TEST_REQUIRE( get<0>(exts.from_linear( 0)) == 0 ); - BOOST_TEST_REQUIRE( get<1>(exts.from_linear( 0)) == 0 ); - BOOST_TEST_REQUIRE( get<2>(exts.from_linear( 0)) == 0 ); + BOOST_TEST( B2copy2 == B2({0, 2}, {0, 2}) ); + BOOST_TEST( B2copy2.base() != B2({0, 2}, {0, 2}).base() ); - BOOST_TEST_REQUIRE( get<0>(exts.from_linear( 1)) == 0 ); - BOOST_TEST_REQUIRE( get<1>(exts.from_linear( 1)) == 0 ); - BOOST_TEST_REQUIRE( get<2>(exts.from_linear( 1)) == 1 ); + // clang-format off + std::array, 2> B2blk = {{ + {{B2({0, 2}, {0, 2}), B2({0, 2}, {2, 4})}}, + {{B2({2, 4}, {0, 2}), B2({2, 4}, {2, 4})}}, + }}; + // clang-format on - BOOST_TEST_REQUIRE( get<0>(exts.from_linear(16)) == 0 ); - BOOST_TEST_REQUIRE( get<1>(exts.from_linear(16)) == 0 ); - BOOST_TEST_REQUIRE( get<2>(exts.from_linear(16)) == 16 ); + BOOST_TEST( &B2blk[1][1][1][1] == &B2[3][3] ); + } - BOOST_TEST_REQUIRE( get<0>(exts.from_linear(17)) == 0 ); - BOOST_TEST_REQUIRE( get<1>(exts.from_linear(17)) == 1 ); - BOOST_TEST_REQUIRE( get<2>(exts.from_linear(17)) == 0 ); + // BOOST_AUTO_TEST_CASE(layout_BB) + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy type + double arr[3][4][5] = {}; - BOOST_TEST_REQUIRE( get<0>(exts.from_linear(18)) == 0 ); - BOOST_TEST_REQUIRE( get<1>(exts.from_linear(18)) == 1 ); - BOOST_TEST_REQUIRE( get<2>(exts.from_linear(18)) == 1 ); + using multi::dimensionality; + static_assert(dimensionality(arr) == 3); - BOOST_TEST_REQUIRE( get<0>(exts.from_linear(19)) == 0 ); - BOOST_TEST_REQUIRE( get<1>(exts.from_linear(19)) == 1 ); - BOOST_TEST_REQUIRE( get<2>(exts.from_linear(19)) == 2 ); + using multi::extensions; + auto xA = extensions(arr); - multi::layout_t<3> const lyt{exts}; - BOOST_TEST_REQUIRE( lyt[get<0>(exts.from_linear(19))][get<1>(exts.from_linear(19))][get<2>(exts.from_linear(19))] == 19 ); - BOOST_TEST_REQUIRE( lyt(get<0>(exts.from_linear(19)), get<1>(exts.from_linear(19)), get<2>(exts.from_linear(19))) == 19 ); -} + using std::get; // needed for C++17 + using std::size; + BOOST_TEST( size(get<0>(xA)) == 3 ); + BOOST_TEST( size(get<1>(xA)) == 4 ); + BOOST_TEST( size(get<2>(xA)) == 5 ); + } -BOOST_AUTO_TEST_CASE(extensions_from_linear_3d_struct_bind) { - multi::extensions_t<3> const exts{11, 13, 17}; + // static_assert(multi::stride(arr) == 20); + + // static_assert(multi::stride(arr[1]) == 5); + // static_assert(multi::stride(arr[0][0]) == 1); + + // multi::array AA({3, 4, 5}); + // using multi::layout; + // BOOST_TEST( layout(AA) == layout(arr) ); + + // BOOST_TEST( AA.stride() == 20 ); + // } + // { + // std::array, 4>, 3> arr = {}; + // static_assert(multi::dimensionality(arr) == 3); + + // using multi::extensions; + // auto xA = extensions(arr); + // using std::get; + // BOOST_TEST( size(std::get<0>(xA)) == 3 ); + // BOOST_TEST( size(std::get<1>(xA)) == 4 ); + // BOOST_TEST( size(std::get<2>(xA)) == 5 ); + + // multi::array AA({3, 4, 5}); + // using multi::layout; + // BOOST_TEST( layout(AA) == layout(arr) ); + + // BOOST_TEST( AA.stride() == 20 ); + + // static_assert(multi::stride(arr) == 20); + + // BOOST_TEST( multi::stride(arr[0]) == 5 ); + // BOOST_TEST( multi::stride(arr[1]) == 5 ); + // BOOST_TEST( multi::stride(arr[0][0]) == 1 ); + // } + // { + // multi::array const B2 = { + // {1.0}, + // {2.0}, + // {3.0}, + // }; + // BOOST_TEST( size(B2) == 3 ); + // BOOST_TEST( B2.rotated().size() == 1 ); + // BOOST_TEST( size(B2[0]) == 1); + // BOOST_TEST( B2 .stride() == 1 ); + // BOOST_TEST( B2[0].stride() == 1 ); + // } + // } + + // BOOST_AUTO_TEST_CASE(multi_layout_with_offset) { + // static_assert( std::is_trivially_default_constructible_v< multi::layout_t<0> > ); + // static_assert( std::is_trivially_default_constructible_v< multi::layout_t<1> > ); + // static_assert( std::is_trivially_default_constructible_v< multi::layout_t<2> > ); + + // static_assert( std::is_trivially_copyable_v< multi::layout_t<2> > ); + + // { + // multi::layout_t<1> const l1(multi::iextension(2, 5)); + // BOOST_TEST( l1.extension().first() == 2 ); + // BOOST_TEST( l1.extension().last() == 5 ); + // } + // { + // boost::multi::layout_t<2>::extensions_type const exts{ + // multi::iextension(2, 5), + // multi::iextension(0, 5) + // }; + // multi::layout_t<2> const l2(exts); + // BOOST_TEST( l2.extension().first() == std::get<0>(exts).first() ); + // BOOST_TEST( l2.extension().last () == std::get<0>(exts).last() ); + // } + // { + // multi::layout_t<2> const l2({multi::iextension(0, 3), multi::iextension(2, 7)}); + // BOOST_TEST( std::get<1>(l2.extensions()).first() == 2 ); + // BOOST_TEST( std::get<1>(l2.extensions()).last() == 7 ); + // } + // } + + // BOOST_AUTO_TEST_CASE(multi_layout_part1) { + // { + // multi::layout_t<0> const lyt; + // static_assert(decltype(lyt)::rank_v == 0); + // BOOST_TEST( num_elements(lyt) == 1 ); + // } + // { + // multi::iextensions<0> const exts{}; + // multi::layout_t<0> const lyt(exts); + // BOOST_TEST(lyt.num_elements() == 1); + // } + // { + // multi::layout_t<1> const lyt{}; + // static_assert(decltype(lyt)::rank_v == 1); + // BOOST_TEST( num_elements(lyt) == 0 ); + // BOOST_TEST( size(lyt) == 0 ); + // BOOST_TEST( size(extension(lyt)) == 0 ); + // BOOST_TEST( stride(lyt) != 0 ); + // BOOST_TEST( is_empty(lyt) ); + // } + // { + // multi::layout_t<2> const lyt({2, 10}); + // static_assert(decltype(lyt)::rank_v == 2); + // BOOST_TEST( num_elements(lyt) == 20 ); + // BOOST_TEST( size(lyt) == 2 ); + // BOOST_TEST( size(extension(lyt)) == 2 ); + // BOOST_TEST( stride(lyt) == 10 ); + // BOOST_TEST( !is_empty(lyt) ); + // } + // { + // multi::layout_t<1> const lyt(multi::iextensions<1>{20}); + // static_assert(decltype(lyt)::rank_v == 1); + // BOOST_TEST( num_elements(lyt) == 20 ); + // BOOST_TEST( size(lyt) == 20 ); + // BOOST_TEST( stride(lyt) == 1 ); + // } + // } + + // BOOST_AUTO_TEST_CASE(multi_layout_part2) { + // { + // multi::layout_t<1> const lyt(multi::iextensions<1>{1}); + // static_assert(decltype(lyt)::rank_v == 1); + // BOOST_TEST( num_elements(lyt) == 1 ); + // BOOST_TEST( size(lyt) == 1 ); + // BOOST_TEST( stride(lyt) == 1 ); + // } + // { + // multi::layout_t<2> const lyt({1, 10}); + // static_assert(decltype(lyt)::rank_v == 2); + // BOOST_TEST( num_elements(lyt) == 10 ); + // BOOST_TEST( size(lyt) == 1); + // BOOST_TEST( !is_empty(lyt) ); + // BOOST_TEST( size(extension(lyt)) == 1 ); + // BOOST_TEST( stride(lyt) == 10 ); // std::numeric_limits::max() ); + + // using std::get; + // BOOST_TEST( get<0>(strides(lyt)) == 10); + // BOOST_TEST( get<1>(strides(lyt)) == 1 ); + // } + // } + + // BOOST_AUTO_TEST_CASE(multi_layout_part3) { + // { + // multi::layout_t<2> const lyt({10, 1}); + // static_assert(decltype(lyt)::rank_v == 2); + // BOOST_TEST( num_elements(lyt) == 10 ); + // BOOST_TEST( size(lyt) == 10 ); + // using std::get; + // BOOST_TEST( get<0>(strides(lyt)) == 1 ); + // BOOST_TEST( get<1>(strides(lyt)) == 1 ); + // } + // { + // multi::layout_t<2> const lyt{}; + // BOOST_TEST( dimensionality(lyt) == 2 ); + // BOOST_TEST( num_elements(lyt) == 0 ); + // BOOST_TEST( size(lyt) == 0 ); + // BOOST_TEST( size(extension(lyt)) == 0 ); + // BOOST_TEST( stride(lyt) != 0 ); + // BOOST_TEST( is_empty(lyt) ); + // } + // { + // multi::layout_t<3> const lyt{}; + // BOOST_TEST( num_elements(lyt) == 0 ); + // } + // { + // multi::layout_t<3> const lyt({ + // {0, 10}, + // {0, 10}, + // {0, 10}, + // }); + // BOOST_TEST( num_elements(lyt) == 1000 ); + // } + // { + // multi::layout_t<3> const lyt({{10}, {10}, {10}}); + // BOOST_TEST( num_elements(lyt) == 1000 ); + // } + // { + // multi::layout_t<3> const lyt({10, 10, 10}); + // BOOST_TEST( num_elements(lyt) == 1000 ); + // } + // { + // multi::layout_t<3> const lyt({ + // multi::index_extension{0, 10}, + // {0, 10}, + // {0, 10}, + // }); + // BOOST_TEST( num_elements(lyt) == 1000 ); + // } + // { + // multi::layout_t<3> const lyt(multi::layout_t<3>::extensions_type{ + // {0, 10}, + // {0, 10}, + // {0, 10}, + // }); + // BOOST_TEST( num_elements(lyt) == 1000 ); + // } + // } + + // BOOST_AUTO_TEST_CASE(layout_to_offset) { + // multi::layout_t<3> const lyt({10, 20, 30}); + // multi::array const arr({10, 20, 30}); + // BOOST_TEST( lyt[0][0][0] == &arr[0][0][0] - arr.data_elements() ); + // BOOST_TEST( lyt[0][0][1] == &arr[0][0][1] - arr.data_elements() ); + // BOOST_TEST( lyt[0][0][2] == &arr[0][0][2] - arr.data_elements() ); + + // BOOST_TEST_REQUIRE(lyt[0][1][2] == &arr[0][1][2] - arr.data_elements()); + // BOOST_TEST_REQUIRE(lyt[3][1][2] == &arr[3][1][2] - arr.data_elements()); + // } + + // BOOST_AUTO_TEST_CASE(layout_to_offset_sub) { + // multi::array arr({10, 20, 30}); + + // auto&& sub = arr({2, 6}, {4, 8}, {10, 20}); + + // auto const lyt = sub.layout(); + + // BOOST_TEST( lyt[0][0][0] == &sub[0][0][0] - base(sub) ); + // BOOST_TEST( lyt[0][0][1] == &sub[0][0][1] - base(sub) ); + // BOOST_TEST( lyt[0][0][2] == &sub[0][0][2] - base(sub) ); + // BOOST_TEST( lyt[0][1][2] == &sub[0][1][2] - base(sub) ); + // BOOST_TEST( lyt[3][1][2] == &sub[3][1][2] - base(sub) ); + // } + + // BOOST_AUTO_TEST_CASE(continued_part1) { + // { + // multi::layout_t<3> const lyt(multi::layout_t<3>::extensions_type{ + // {0, 10}, + // {0, 10}, + // {0, 10}, + // }); + // BOOST_TEST( num_elements(lyt) == 1000); + // } + // { + // multi::layout_t<3> const lyt({ + // multi::iextension{0, 10}, + // multi::iextension{0, 10}, + // multi::iextension{0, 10}, + // }); + // BOOST_TEST(lyt.num_elements() == 1000); + // } + // { + // multi::layout_t<3> const lyt({multi::iextension{10}, multi::iextension{10}, multi::iextension{10}}); + // BOOST_TEST( num_elements(lyt) == 1000); + // } + // { + // multi::layout_t<3> const lyt({10, 10, multi::iextension{10}}); + // BOOST_TEST( num_elements(lyt) == 1000 ); + // } + // { + // multi::layout_t<1> const lyt; + // BOOST_TEST( size(lyt) == 0 ); + // } + // { + // multi::layout_t<1> lyt({ + // {0, 10}, + // }); + // BOOST_TEST( size(lyt) == 10 ); + // BOOST_TEST( extension(lyt).first() == 0 ); + // BOOST_TEST( extension(lyt).last () == 10 ); + + // lyt.reindex(1); + // BOOST_TEST( size(lyt) == 10 ); + // BOOST_TEST( extension(lyt).first() == 1 ); + // BOOST_TEST( extension(lyt).last () == 11 ); + // } + // { + // multi::layout_t<2> const lyt; + // BOOST_TEST( size(lyt) == 0 ); + // } + // { + // multi::layout_t<2> lyt(multi::extensions_t<2>({ + // {0, 10}, + // {0, 20}, + // })); + // BOOST_TEST( size(lyt) == 10 ); + // BOOST_TEST( extension(lyt).first() == 0 ); + // BOOST_TEST( extension(lyt).last () == 10 ); + + // lyt.reindex(1); + // BOOST_TEST( extension(lyt).first() == 1 ); + // BOOST_TEST( extension(lyt).last () == 11 ); + + // lyt.rotate().reindex(3).unrotate(); + // BOOST_TEST_REQUIRE( extension(lyt).first() == 1 ); + // BOOST_TEST_REQUIRE( extension(lyt).last () == 11 ); + + // BOOST_TEST_REQUIRE( std::get<0>(extensions(lyt)).first() == 1 ); + // BOOST_TEST_REQUIRE( std::get<1>(extensions(lyt)).first() == 3 ); + // BOOST_TEST_REQUIRE( std::get<1>(extensions(lyt)).last () == 23 ); + // } + // } + + // BOOST_AUTO_TEST_CASE(continued_part2) { + // multi::layout_t<3> const lyt({ + // {0, 10}, + // {0, 20}, + // {0, 30}, + // }); + + // BOOST_TEST( !lyt.empty() ); + + // BOOST_TEST( stride(lyt) == lyt.stride() ); + // BOOST_TEST( offset(lyt) == lyt.offset() ); + // BOOST_TEST( nelems(lyt) == lyt.nelems() ); + + // BOOST_TEST( stride(lyt) == 20*30L ); + // BOOST_TEST( offset(lyt) == 0 ); + // BOOST_TEST( nelems(lyt) == 10*20L*30L ); + + // BOOST_TEST( lyt.stride() == stride(lyt) ); + // BOOST_TEST( lyt.offset() == offset(lyt) ); + // BOOST_TEST( lyt.nelems() == nelems(lyt) ); + + // using boost::multi::detail::get; + // BOOST_TEST( get<1>(lyt.strides()) == 30 ); + // BOOST_TEST( get<1>(lyt.offsets()) == 0 ); + // BOOST_TEST( get<1>(lyt.nelemss()) == 20*30L ); + + // BOOST_TEST( get<2>(lyt.strides()) == 1 ); + // BOOST_TEST( get<2>(lyt.offsets()) == 0 ); + // BOOST_TEST( get<2>(lyt.nelemss()) == 30 ); + // } + + // BOOST_AUTO_TEST_CASE(continued_part3) { + // multi::layout_t<3> const lyt({ + // {0, 10}, + // {0, 20}, + // {0, 30}, + // }); + + // BOOST_TEST( lyt.num_elements() == num_elements(lyt) ); + // BOOST_TEST( lyt.size() == size(lyt) ); + // BOOST_TEST( lyt.extension() == extension(lyt) ); + + // BOOST_TEST( num_elements(lyt) == 10*20L*30L ); + // BOOST_TEST( size(lyt) == 10 ); + // BOOST_TEST( extension(lyt).first() == 0 ); + // BOOST_TEST( extension(lyt).last() == 10 ); + + // BOOST_TEST( std::get<0>(lyt.extensions()) == lyt.extension() ); + + // boost::multi::extensions_t<2> const exts2; + + // using boost::multi::detail::get; + // using std::get; + + // BOOST_TEST( get<0>(exts2).is_empty() ); + + // // BOOST_TEST( std::get<0>(L.sizes()) == L.size(0) ); + // // BOOST_TEST( std::get<0>(L.extensions()) == L.extension(0) ); + + // BOOST_TEST(( get<0>(lyt.extensions()) == multi::index_extension{0, 10} )); + + // BOOST_TEST( get<0>(lyt.extensions()).first() == 0 ); + // BOOST_TEST( get<0>(lyt.extensions()).last() == 10 ); + + // // BOOST_TEST( L.size(1) == 20 ); + // BOOST_TEST( get<1>(lyt.extensions()).first() == 0 ); + // BOOST_TEST( get<1>(lyt.extensions()).last() == 20 ); + + // // BOOST_TEST( L.size(2) == 30 ); + // BOOST_TEST( get<2>(lyt.extensions()).first() == 0 ); + // BOOST_TEST( get<2>(lyt.extensions()).last() == 30 ); + + // using std::get; + // BOOST_TEST( get<0>(strides(lyt)) == lyt.stride() ); + + // auto const& strides = lyt.strides(); + // BOOST_TEST( get<0>(strides) == lyt.stride() ); + // } + + // BOOST_AUTO_TEST_CASE(continued) { + // { + // multi::layout_t<3> const lyt; + // BOOST_TEST( size(lyt) == 0 ); + // } + // { + // multi::layout_t<3> const lyt({ + // {0, 10}, + // {0, 20}, + // {0, 30}, + // }); + // BOOST_TEST( stride(lyt) == 20*30L ); + // } + // { + // multi::layout_t<1> const lyt({ + // {0, 10}, + // }); + // BOOST_TEST( extension(lyt).first() == 0 ); + // BOOST_TEST( extension(lyt).last() == 10 ); + // } + // { + // multi::layout_t<1> const lyt({ + // {8, 18}, + // }); + // BOOST_TEST( extension(lyt).first() == 8 ); + // BOOST_TEST( extension(lyt).last() == 18 ); + // } + // { + // multi::layout_t<2> const lyt(multi::extensions_t<2>({ + // {0, 10}, + // {0, 20}, + // })); + // BOOST_TEST( extension(lyt).first() == 0 ); + // BOOST_TEST( extension(lyt).last() == 10 ); + // } + // // { // this is ambiguous in nvcc + // // multi::layout_t<2> const lyt({ + // // {0, 10}, + // // {0, 20}, + // // }); + // // BOOST_TEST( extension(lyt).first() == 0 ); + // // BOOST_TEST( extension(lyt).last() == 10 ); + // // } + // { + // multi::layout_t<2> const lyt(multi::extensions_t<2>({ + // { 0, 10}, + // {11, 31}, + // })); + // BOOST_TEST( size(lyt) == 10 ); + // BOOST_TEST( stride(lyt) == 20 ); + // BOOST_TEST( offset(lyt) == 0 ); + // } + // { // this is ambiguous in nvcc + // multi::layout_t<2> const lyt(multi::extensions_t<2>({ + // { 0, 10}, + // {11, 31}, + // })); + // BOOST_TEST( size(lyt) == 10 ); + // BOOST_TEST( stride(lyt) == 20 ); + // BOOST_TEST( offset(lyt) == 0 ); + // } + // { + // multi::layout_t<2> const lyt(multi::extensions_t<2>({ + // {8, 18}, + // {0, 20}, + // })); + // BOOST_TEST( size(lyt) == 10 ); + // BOOST_TEST( stride(lyt) == 20 ); + // } + // // { + // // multi::layout_t<3> const lyt(multi::extensions_t<3>({ + // // { 0, 3}, + // // { 0, 5}, + // // {10, 17}, + // // })); + // // BOOST_TEST( stride(lyt) == 5*7L ); + // // BOOST_TEST( stride(lyt.sub().sub()) == 1 ); + // // } + // { + // multi::layout_t<3> const lyt({ + // {0, 10}, + // {0, 20}, + // {0, 30}, + // }); + // BOOST_TEST( size(lyt) == 10 ); + // BOOST_TEST( stride(lyt) == 20*30L ); + // BOOST_TEST( offset(lyt) == 0 ); + // BOOST_TEST( nelems(lyt) == 10*20L*30L ); + // } + // { + // multi::layout_t<3> const lyt({ + // {10, 20}, + // {10, 30}, + // {10, 40}, + // }); + // BOOST_TEST( stride(lyt) == 20*30L ); + // } + // { + // auto const ttt = boost::multi::tuple{1, 2, 3}; + // auto const arr = std::apply([](auto... elems) { return std::array{{elems...}}; }, ttt); + // BOOST_TEST(arr[1] == 2); + // } + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_1d) { + // multi::extensions_t<1> const exts{11}; + + // auto ijk = exts.from_linear(9); + + // using multi::detail::get; + // BOOST_TEST_REQUIRE( get<0>(ijk) == 9 ); + + // multi::layout_t<1> const lyt{exts}; + // BOOST_TEST_REQUIRE( lyt[get<0>(ijk)] == 9 ); + // BOOST_TEST_REQUIRE( lyt(get<0>(ijk)) == 9 ); + + // BOOST_TEST_REQUIRE( lyt(std::get<0>(lyt.extensions().from_linear(9))) == 9 ); + + // BOOST_TEST_REQUIRE( std::apply(lyt, lyt.extensions().from_linear(9)) == 9 ); + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_2d_structured_binding) { + // multi::extensions_t<2> const exts{3, 5}; + // auto [eye, jay] = exts.from_linear(7); + + // BOOST_TEST_REQUIRE( eye == 1 ); + // BOOST_TEST_REQUIRE( jay == 2 ); + // // BOOST_TEST_REQUIRE( std::apply(l, l.extensions().from_linear(9)) == 9 ); + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_2d_std_get) { + // multi::extensions_t<2> const exts{3, 5}; + // auto eye = std::get<0>(exts.from_linear(7)); + // auto jay = std::get<1>(exts.from_linear(7)); + // BOOST_TEST_REQUIRE( eye == 1 ); + // BOOST_TEST_REQUIRE( jay == 2 ); + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_2d_std_get_using) { + // multi::extensions_t<2> const exts{3, 5}; + // using std::get; + // auto fl = exts.from_linear(7L); + // auto const eye = get<0>(fl); + // auto const jay = get<1>(fl); + // BOOST_TEST_REQUIRE( eye == 1 ); + // BOOST_TEST_REQUIRE( jay == 2 ); + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_2d_get_using) { + // multi::extensions_t<2> const exts{3, 5}; + + // using multi::detail::get; + + // auto eye = get<0>(exts.from_linear(7)); + // auto jay = get<1>(exts.from_linear(7)); + // BOOST_TEST_REQUIRE( eye == 1 ); + // BOOST_TEST_REQUIRE( jay == 2 ); + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_2d) { + // multi::extensions_t<2> const exts{3, 5}; + + // auto ij = exts.from_linear(7); + + // using multi::detail::get; + + // BOOST_TEST_REQUIRE( get<0>(ij) == 1 ); + // BOOST_TEST_REQUIRE( get<1>(ij) == 2 ); + + // multi::layout_t<2> const lyt{exts}; + // BOOST_TEST_REQUIRE( lyt[get<0>(ij)][get<1>(ij)] == 7 ); + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_3d_std_get) { + // multi::extensions_t<3> const exts{11, 13, 17}; + + // BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear( 0)) == 0 ); + // BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear( 0)) == 0 ); + // BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear( 0)) == 0 ); + + // BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear( 1)) == 0 ); + // BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear( 1)) == 0 ); + // BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear( 1)) == 1 ); + + // BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear(16)) == 0 ); + // BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear(16)) == 0 ); + // BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear(16)) == 16 ); + + // BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear(17)) == 0 ); + // BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear(17)) == 1 ); + // BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear(17)) == 0 ); + + // BOOST_TEST_REQUIRE( std::get<0>(exts.from_linear(18)) == 0 ); + // BOOST_TEST_REQUIRE( std::get<1>(exts.from_linear(18)) == 1 ); + // BOOST_TEST_REQUIRE( std::get<2>(exts.from_linear(18)) == 1 ); + + // multi::layout_t<3> const lyt{exts}; + + // using std::get; + // BOOST_TEST_REQUIRE( lyt[get<0>(exts.from_linear(19))][get<1>(exts.from_linear(19))][get<2>(exts.from_linear(19))] == 19 ); + // BOOST_TEST_REQUIRE( lyt(get<0>(exts.from_linear(19)), get<1>(exts.from_linear(19)), get<2>(exts.from_linear(19))) == 19 ); + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_3d_std_get_using) { + // multi::extensions_t<3> const exts{11, 13, 17}; + + // using std::get; + + // BOOST_TEST_REQUIRE( get<0>(exts.from_linear( 0)) == 0 ); + // BOOST_TEST_REQUIRE( get<1>(exts.from_linear( 0)) == 0 ); + // BOOST_TEST_REQUIRE( get<2>(exts.from_linear( 0)) == 0 ); + + // BOOST_TEST_REQUIRE( get<0>(exts.from_linear( 1)) == 0 ); + // BOOST_TEST_REQUIRE( get<1>(exts.from_linear( 1)) == 0 ); + // BOOST_TEST_REQUIRE( get<2>(exts.from_linear( 1)) == 1 ); + + // BOOST_TEST_REQUIRE( get<0>(exts.from_linear(16)) == 0 ); + // BOOST_TEST_REQUIRE( get<1>(exts.from_linear(16)) == 0 ); + // BOOST_TEST_REQUIRE( get<2>(exts.from_linear(16)) == 16 ); + + // BOOST_TEST_REQUIRE( get<0>(exts.from_linear(17)) == 0 ); + // BOOST_TEST_REQUIRE( get<1>(exts.from_linear(17)) == 1 ); + // BOOST_TEST_REQUIRE( get<2>(exts.from_linear(17)) == 0 ); + + // BOOST_TEST_REQUIRE( get<0>(exts.from_linear(18)) == 0 ); + // BOOST_TEST_REQUIRE( get<1>(exts.from_linear(18)) == 1 ); + // BOOST_TEST_REQUIRE( get<2>(exts.from_linear(18)) == 1 ); + + // BOOST_TEST_REQUIRE( get<0>(exts.from_linear(19)) == 0 ); + // BOOST_TEST_REQUIRE( get<1>(exts.from_linear(19)) == 1 ); + // BOOST_TEST_REQUIRE( get<2>(exts.from_linear(19)) == 2 ); + + // multi::layout_t<3> const lyt{exts}; + // BOOST_TEST_REQUIRE( lyt[get<0>(exts.from_linear(19))][get<1>(exts.from_linear(19))][get<2>(exts.from_linear(19))] == 19 ); + // BOOST_TEST_REQUIRE( lyt(get<0>(exts.from_linear(19)), get<1>(exts.from_linear(19)), get<2>(exts.from_linear(19))) == 19 ); + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_3d_struct_bind) { + // multi::extensions_t<3> const exts{11, 13, 17}; + + // using std::get; + // { + // auto [eye, jay, kay] = exts.from_linear(0); + // BOOST_TEST_REQUIRE(eye == 0); + // BOOST_TEST_REQUIRE(jay == 0); + // BOOST_TEST_REQUIRE(kay == 0); + // } + // { + // auto [eye, jay, kay] = exts.from_linear(1); + // BOOST_TEST_REQUIRE(eye == 0); + // BOOST_TEST_REQUIRE(jay == 0); + // BOOST_TEST_REQUIRE(kay == 1); + // } + // { + // auto [eye, jay, kay] = exts.from_linear(16); + // BOOST_TEST_REQUIRE(eye == 0); + // BOOST_TEST_REQUIRE(jay == 0); + // BOOST_TEST_REQUIRE(kay == 16); + // } + // { + // auto [eye, jay, kay] = exts.from_linear(17); + // BOOST_TEST_REQUIRE(eye == 0); + // BOOST_TEST_REQUIRE(jay == 1); + // BOOST_TEST_REQUIRE(kay == 0); + // } + // { + // auto [eye, jay, kay] = exts.from_linear(18); + // BOOST_TEST_REQUIRE(eye == 0); + // BOOST_TEST_REQUIRE(jay == 1); + // BOOST_TEST_REQUIRE(kay == 1); + + // multi::layout_t<3> const lyt{exts}; + // BOOST_TEST_REQUIRE(lyt[eye][jay][kay] == 18); + // BOOST_TEST_REQUIRE(lyt(eye, jay, kay) == 18); + // } + // } + + // BOOST_AUTO_TEST_CASE(extensions_from_linear_3d) { + // multi::extensions_t<3> const exts{11, 13, 17}; + + // auto ijk = exts.from_linear(19); + + // { + // using std::get; + // BOOST_TEST_REQUIRE(get<0>(exts.from_linear(19)) == 0); + // BOOST_TEST_REQUIRE(get<1>(exts.from_linear(19)) == 1); + // BOOST_TEST_REQUIRE(get<2>(exts.from_linear(19)) == 2); + // } + // { + // using std::get; + // // using multi::detail::get; + // BOOST_TEST_REQUIRE(get<0>(ijk) == 0); + // BOOST_TEST_REQUIRE(get<1>(ijk) == 1); + // BOOST_TEST_REQUIRE(get<2>(ijk) == 2); + + // multi::layout_t<3> const lyt{exts}; + + // BOOST_TEST_REQUIRE(lyt[get<0>(ijk)][get<1>(ijk)][get<2>(ijk)] == 19); + // BOOST_TEST_REQUIRE(lyt(get<0>(ijk), get<1>(ijk), get<2>(ijk)) == 19); + // } + // } + + // BOOST_AUTO_TEST_CASE(extension_1D_iteration) { + // multi::extension_t const ext(10); + // BOOST_TEST_REQUIRE(ext[0] == 0); + // BOOST_TEST_REQUIRE(ext[1] == 1); + // } + + // BOOST_AUTO_TEST_CASE(extensionS_1D_iteration) { + // { + // multi::extensions_t<1> const exts(10); + // BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); + // BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); + // } + // { + // multi::extensions_t<1> const exts(multi::iextension{0, 10}); + // BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); + // BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); + // } + // } + + // BOOST_AUTO_TEST_CASE(extensionS_2D_iteration) { + // { + // multi::extensions_t<2> exts({3, 5}); + // BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); + // BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); + // } + // { + // multi::extensions_t<2> exts({multi::iextension{0, 3}, multi::iextension{0, 5}}); + // BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); + // BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); + // } + // } + + // BOOST_AUTO_TEST_CASE(layout_1D_iteration) { + // multi::layout_t<1> const lyt{multi::extensions_t<1>(10)}; + // BOOST_TEST( lyt[0] == 0 ); + // BOOST_TEST( lyt[1] == 1 ); + // BOOST_TEST( lyt[2] == 2 ); + + // // BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); + // // BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); + // } + + // BOOST_AUTO_TEST_CASE(layout_2D_iteration) { + // multi::layout_t<2> const lyt{multi::extensions_t<2>({5, 3})}; + // BOOST_TEST( lyt[0][0] == 0 ); + // BOOST_TEST( lyt[0][1] == 1 ); + // BOOST_TEST( lyt[0][2] == 2 ); + + // BOOST_TEST( lyt[1][0] == 3 ); + // BOOST_TEST( lyt[1][1] == 4 ); + // BOOST_TEST( lyt[1][2] == 5 ); + + // // BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); + // // BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); + // } - using std::get; - { - auto [eye, jay, kay] = exts.from_linear(0); - BOOST_TEST_REQUIRE(eye == 0); - BOOST_TEST_REQUIRE(jay == 0); - BOOST_TEST_REQUIRE(kay == 0); - } - { - auto [eye, jay, kay] = exts.from_linear(1); - BOOST_TEST_REQUIRE(eye == 0); - BOOST_TEST_REQUIRE(jay == 0); - BOOST_TEST_REQUIRE(kay == 1); - } { - auto [eye, jay, kay] = exts.from_linear(16); - BOOST_TEST_REQUIRE(eye == 0); - BOOST_TEST_REQUIRE(jay == 0); - BOOST_TEST_REQUIRE(kay == 16); + multi::array arr({6, 8}); + BOOST_TEST( arr.size() == 6 ); + + auto&& arrp2 = arr.partitioned(2); + BOOST_TEST( arrp2.num_elements() == arr.num_elements() ); + BOOST_TEST( arrp2.size() == 2 ); } { - auto [eye, jay, kay] = exts.from_linear(17); - BOOST_TEST_REQUIRE(eye == 0); - BOOST_TEST_REQUIRE(jay == 1); - BOOST_TEST_REQUIRE(kay == 0); + multi::layout_t<2> const lyt(multi::extensions_t<2>{ + {3, 9}, + {0, 15} + }); + BOOST_TEST( lyt.size() == 6 ); + BOOST_TEST( lyt.extension().front() == 3 ); + BOOST_TEST( lyt.extension().back() == 8 ); } { - auto [eye, jay, kay] = exts.from_linear(18); - BOOST_TEST_REQUIRE(eye == 0); - BOOST_TEST_REQUIRE(jay == 1); - BOOST_TEST_REQUIRE(kay == 1); - - multi::layout_t<3> const lyt{exts}; - BOOST_TEST_REQUIRE(lyt[eye][jay][kay] == 18); - BOOST_TEST_REQUIRE(lyt(eye, jay, kay) == 18); - } -} + multi::extension_t const ext(5); -BOOST_AUTO_TEST_CASE(extensions_from_linear_3d) { - multi::extensions_t<3> const exts{11, 13, 17}; + BOOST_TEST( *ext.begin() == 0 ); + BOOST_TEST( *(ext.end() - 1) == 4 ); - auto ijk = exts.from_linear(19); +#if !defined(__clang_major__) || (__clang_major__ > 16) +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) && !defined(_MSC_VER) + BOOST_TEST( *std::ranges::begin(ext) == 0 ); + BOOST_TEST( *(std::ranges::end(ext)-1) == 4 ); - { - using std::get; - BOOST_TEST_REQUIRE(get<0>(exts.from_linear(19)) == 0); - BOOST_TEST_REQUIRE(get<1>(exts.from_linear(19)) == 1); - BOOST_TEST_REQUIRE(get<2>(exts.from_linear(19)) == 2); - } - { - using std::get; - // using multi::detail::get; - BOOST_TEST_REQUIRE(get<0>(ijk) == 0); - BOOST_TEST_REQUIRE(get<1>(ijk) == 1); - BOOST_TEST_REQUIRE(get<2>(ijk) == 2); + BOOST_TEST( ext[0] == 0 ); + BOOST_TEST( ext[1] == 1 ); + BOOST_TEST( ext[4] == 4 ); - multi::layout_t<3> const lyt{exts}; + static_assert(std::ranges::range>); + static_assert(std::ranges::range const>); - BOOST_TEST_REQUIRE(lyt[get<0>(ijk)][get<1>(ijk)][get<2>(ijk)] == 19); - BOOST_TEST_REQUIRE(lyt(get<0>(ijk), get<1>(ijk), get<2>(ijk)) == 19); - } -} - -BOOST_AUTO_TEST_CASE(extension_1D_iteration) { - multi::extension_t const ext(10); - BOOST_TEST_REQUIRE(ext[0] == 0); - BOOST_TEST_REQUIRE(ext[1] == 1); -} + // std::ranges::ref_view> + auto rext = ext | std::ranges::views::reverse; -BOOST_AUTO_TEST_CASE(extensionS_1D_iteration) { - { - multi::extensions_t<1> const exts(10); - BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); - BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); - } - { - multi::extensions_t<1> const exts(multi::iextension{0, 10}); - BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); - BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); + BOOST_TEST( rext[0] == 4 ); + BOOST_TEST( rext[1] == 3 ); + BOOST_TEST( rext[4] == 0 ); +#endif +#endif } -} - -// BOOST_AUTO_TEST_CASE(extensionS_2D_iteration) { -// { -// multi::extensions_t<2> exts({3, 5}); -// BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); -// BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); -// } -// { -// multi::extensions_t<2> exts({multi::iextension{0, 3}, multi::iextension{0, 5}}); -// BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); -// BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); -// } -// } - -BOOST_AUTO_TEST_CASE(layout_1D_iteration) { - multi::layout_t<1> const lyt{multi::extensions_t<1>(10)}; - BOOST_REQUIRE( lyt[0] == 0 ); - BOOST_REQUIRE( lyt[1] == 1 ); - BOOST_REQUIRE( lyt[2] == 2 ); - - // BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); - // BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); -} - -BOOST_AUTO_TEST_CASE(layout_2D_iteration) { - multi::layout_t<2> const lyt{multi::extensions_t<2>({5, 3})}; - BOOST_REQUIRE( lyt[0][0] == 0 ); - BOOST_REQUIRE( lyt[0][1] == 1 ); - BOOST_REQUIRE( lyt[0][2] == 2 ); - - BOOST_REQUIRE( lyt[1][0] == 3 ); - BOOST_REQUIRE( lyt[1][1] == 4 ); - BOOST_REQUIRE( lyt[1][2] == 5 ); - // BOOST_TEST_REQUIRE(std::get<0>(exts[0]) == 0); - // BOOST_TEST_REQUIRE(std::get<0>(exts[1]) == 1); + return boost::report_errors(); } diff --git a/test/main.cpp b/test/main.cpp index 225ce82d2..294306fcd 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,19 +1,20 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2023 Alfredo A. Correa +// Copyright 2023-2024 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -#include +#include namespace multi = boost::multi; auto main() -> int { - multi::array arr = { - { 0.0, 1.0, 2.0, 3.0, 4.0}, - { 5.0, 6.0, 7.0, 8.0, 9.0}, - {10.0, 11.0, 12.0, 13.0, 14.0}, - {15.0, 16.0, 17.0, 18.0, 19.0}, + multi::array arr = { + { 0, 1, 2, 3, 4}, + { 5, 6, 7, 8, 9}, + {10, 11, 12, 13, 14}, + {15, 16, 17, 18, 19}, }; - if(arr[2][3] != 13.0) { + if(arr[2][3] != 13) { return 1; } return 0; diff --git a/test/member_array_cast.cpp b/test/member_array_cast.cpp index 30d938ef3..0eb1d4cf1 100644 --- a/test/member_array_cast.cpp +++ b/test/member_array_cast.cpp @@ -1,121 +1,293 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi member cast" // test title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array, transform_ptr, static_array -#include +#include -namespace multi = boost::multi; +// IWYU pragma: no_include // for equal // bug in iwyu 14.0.6? with GNU stdlib +// IWYU pragma: no_include // for addressof // bug in iwyu 14.0.6? with GNU stdlib +#include // for array, operator== +#include // for offsetof, size_t +#include // for mem_fn // IWYU pragma: keep +#include // for size +#include // for addressof // IWYU pragma: keep +#include // for operator""s, allocator, char_traits +#include // for tie, operator==, tuple -BOOST_AUTO_TEST_CASE(member_array_cast_soa_aos) { - using v3d = std::array; +#ifdef _MSC_VER +#pragma warning(disable : 4371) +// 'std::_Mem_fn': layout of class may have changed +// from a previous version of the compiler due to better packing of member 'std::_Mem_fn::_Pm' +#endif - // some members might need explicit padding to work well with member_cast - struct particle { - double mass; - v3d position alignas(2 * sizeof(double)); // __attribute__((aligned(2*sizeof(double)))) - }; +namespace multi = boost::multi; - class particles_soa { - multi::array masses_; - multi::array positions_; +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(member_array_cast_soa_aos) + { + using v3d = std::array; - public: // NOLINT(whitespace/indent) nested class - // NOLINTNEXTLINE(runtime/explicit) - particles_soa(multi::array const& AoS) // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : particle_soa can represent a particles' AoS - : masses_{AoS.member_cast(&particle::mass)}, positions_{AoS.member_cast(&particle::position)} {} + // some members might need explicit padding to work well with member_cast +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // 'main::particle': '12' bytes padding added after data member 'main::particle::mass +#endif - struct reference { // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) - double& mass; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-avoid-const-or-ref-data-members) exposed by design - v3d& position; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-avoid-const-or-ref-data-members) exposed by design +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + struct particle { + int mass; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winvalid-offsetof" +#endif +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4324) // 'main::particle': structure was padded due to alignment specifier +#pragma warning(disable : 4371) +// ^^^ 'std::_Mem_fn': layout of class may have changed +// from a previous version of the compiler due to better packing of member 'std::_Mem_fn::_Pm ' +#endif + v3d position alignas(2 * sizeof(double)); // __attribute__((aligned(2*sizeof(double)))) +#ifdef _MSC_VER +#pragma warning(pop) +#endif - operator particle() const { return {mass, position}; } // NOLINT(google-explicit-constructor, hicpp-explicit-conversions): allow equal assignment - auto operator+() const { return operator particle(); } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif - reference(double& mss, v3d& pos) : mass{mss}, position{pos} {} // NOLINT(google-runtime-references) +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // 'main::particle': '12' bytes padding added after data member 'main::particle::mass +#endif - private: // NOLINT(whitespace/indent) nested class - friend class particles_soa; - reference(reference const&) = default; + class particles_soa { + multi::array masses_; + multi::array positions_; public: // NOLINT(whitespace/indent) nested class - auto operator=(reference const& other) -> reference& { // NOLINT(cert-oop54-cpp) - std::tie(mass, position) = std::tie(other.mass, other.position); - return *this; - } - auto operator=(reference&& other) noexcept -> reference& { - operator=(other); - return *this; - } - - auto operator==(reference const& other) const { return std::tie(mass, position) == std::tie(other.mass, other.position); } - auto operator!=(reference const& other) const { return std::tie(mass, position) != std::tie(other.mass, other.position); } + // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) : particle_soa can represent a particles' AoS + explicit particles_soa(multi::array const& AoS) // NOLINTNEXTLINE(runtime/explicit) + : masses_{AoS.member_cast(&particle::mass)}, positions_{AoS.member_cast(&particle::position)} {} + + // NOLINTNEXTLINE(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) // NOSONAR + struct reference { + int& mass; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-avoid-const-or-ref-data-members) exposed by design + v3d& position; // NOLINT(misc-non-private-member-variables-in-classes,cppcoreguidelines-avoid-const-or-ref-data-members) exposed by design + + // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions,modernize-use-designated-initializers) for C++20 + operator particle() const { return {mass, position}; } // NOSONAR(cpp:S1709) allow direct assignment + auto operator+() const { return operator particle(); } + + reference(int& mss, v3d& pos) : mass{mss}, position{pos} {} // NOLINT(google-runtime-references) + // unused: explicit reference(particle& other) : reference{other.mass, other.position} {} + + private: // NOLINT(whitespace/indent) nested class + friend class particles_soa; + + public: // NOLINT(whitespace/indent) nested class + auto operator=(reference const& other) && -> reference& { + if(this == std::addressof(other)) { + return *this; + } + std::tie(mass, position) = std::tie(other.mass, other.position); + return *this; + } + + auto operator==(reference const& other) const { return std::tie(mass, position) == std::tie(other.mass, other.position); } + auto operator!=(reference const& other) const { return std::tie(mass, position) != std::tie(other.mass, other.position); } + }; + + auto operator()(int eye, int jay) { return reference{masses_[eye][jay], positions_[eye][jay]}; } }; - auto operator()(int eye, int jay) { return reference{masses_[eye][jay], positions_[eye][jay]}; } - }; + multi::array AoS({2, 2}, particle{}); + AoS[1][1] = particle{99, v3d{{1.0, 2.0}}}; // NOLINT(modernize-use-designated-initializers) for C++20 - multi::array AoS({2, 2}, particle{}); - AoS[1][1] = particle{99.0, v3d{{1.0, 2.0}}}; + auto&& masses = AoS.member_cast(&particle::mass); + BOOST_TEST(size(masses) == 2); + BOOST_TEST(masses[1][1] == 99 ); - auto&& masses = AoS.member_cast(&particle::mass); - BOOST_REQUIRE(size(masses) == 2); - BOOST_REQUIRE(masses[1][1] == 99.0); + multi::array masses_copy{masses}; + BOOST_TEST(&masses_copy[1][1] != &masses[1][1]); - multi::array masses_copy = masses; - BOOST_REQUIRE(&masses_copy[1][1] != &masses[1][1]); + particles_soa SoA{AoS}; - particles_soa SoA{AoS}; + BOOST_TEST( SoA(1, 1).mass == 99 ); - BOOST_REQUIRE(SoA(1, 1).mass == 99.0); + particle const p11 = SoA(1, 1); + BOOST_TEST(p11.mass == 99 ); - particle const p11 = SoA(1, 1); - BOOST_REQUIRE(p11.mass == 99.0); + auto autop11 = +SoA(1, 1); + BOOST_TEST(autop11.mass == 99 ); - auto autop11 = +SoA(1, 1); - BOOST_REQUIRE(autop11.mass == 99.0); + SoA(1, 1).mass = 88; + BOOST_TEST( SoA(1, 1).mass == 88 ); - SoA(1, 1).mass = 88.0; - BOOST_REQUIRE(SoA(1, 1).mass == 88.0); + SoA(1, 1) = SoA(0, 0); + BOOST_TEST( SoA(1, 1).mass == SoA(0, 0).mass); + BOOST_TEST( SoA(1, 1) == SoA(0, 0)); + BOOST_TEST( !(SoA(1, 1) != SoA(0, 0))); + } - SoA(1, 1) = SoA(0, 0); - BOOST_REQUIRE(SoA(1, 1).mass == SoA(0, 0).mass); - BOOST_REQUIRE(SoA(1, 1) == SoA(0, 0)); - BOOST_REQUIRE(not(SoA(1, 1) != SoA(0, 0))); -} + struct employee_dummy { + std::string name; + // NOLINTNEXTLINE(runtime/int) + short salary; // NOLINT(google-runtime-int) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif -struct alignas(32) employee { - std::string name; - int16_t salary; - std::size_t age; - // private: // char padding_[9];// std::array padding_; // use alignment or padding to allow member_cast -}; - -BOOST_AUTO_TEST_CASE(member_array_cast_soa_aos_employee) { - using namespace std::string_literals; // NOLINT(build/namespaces) ""s - multi::array d1D = { - { "Al"s, 1430, 35}, - {"Bob"s, 3212, 34}, + std::size_t age; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif }; - auto&& d1D_names = d1D.member_cast(&employee::name); - BOOST_REQUIRE(size(d1D_names) == size(d1D)); - BOOST_REQUIRE(d1D_names[1] == d1D[1].name); - BOOST_REQUIRE(&d1D_names[1] == &d1D[1].name); - - multi::array d2D = { - { {"Al"s, 1430, 35}, {"Bob"s, 3212, 34}}, - {{"Carl"s, 1589, 32}, {"David"s, 2300, 38}}, + + struct employee { + std::string name; + + // NOLINTNEXTLINE(runtime/int) + short salary; // NOLINT(google-runtime-int) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + + std::size_t age; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __NVCC__ +#pragma nv_diagnostic push +#pragma nv_diag_suppress = 1427 // offsetof applied to a type other than a standard layout (this happens with NVCC+MSVC) +#endif + // clang-format off + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + char padding_[ + (((offsetof(employee_dummy, age) + sizeof(age)) / sizeof(std::string) + 1) * sizeof(std::string)) - (offsetof(employee_dummy, age) + sizeof(age)) + ] = {}; + // clang-format on +#ifdef __NVCC__ +#pragma nv_diagnostic pop +#endif }; - BOOST_REQUIRE(d2D[0][0].name == "Al"); - BOOST_REQUIRE(d2D[0][0].salary == 1430); - BOOST_REQUIRE(d2D[0][0].age == 35); - auto&& d2D_names = d2D.member_cast(&employee::name); - BOOST_REQUIRE(size(d2D_names) == size(d2D)); - BOOST_REQUIRE(d2D_names[1][1] == "David"); +// TODO(correaa) this doesn't work with NVCC (triggered by adl fill) +#if !(defined(__NVCC__) || defined(__HIPCC__)) + // BOOST_AUTO_TEST_CASE(member_array_cast_soa_aos_employee) + { + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + + // NOLINTBEGIN(misc-include-cleaner,modernize-use-designated-initializers) bug in clang-tidy 18 + multi::array d1D = { + { "Al"s, 1430, 35}, + {"Bob"s, 3212, 34}, + }; + // NOLINTEND(misc-include-cleaner,modernize-use-designated-initializers) bug in clang-tidy 18 + + auto&& d1D_names = d1D.member_cast(&employee::name); + BOOST_TEST(size(d1D_names) == size(d1D)); + BOOST_TEST(d1D_names[1] == d1D[1].name); + BOOST_TEST(&d1D_names[1] == &d1D[1].name); + + // NOLINTBEGIN(modernize-use-designated-initializers) for C++20 + multi::array d2D = { + { {"Al"s, 1430, 35}, {"Bob"s, 3212, 34}}, + {{"Carl"s, 1589, 32}, {"David"s, 2300, 38}}, + }; + // NOLINTEND(modernize-use-designated-initializers) for C++20 + + BOOST_TEST(d2D[0][0].name == "Al"); + BOOST_TEST(d2D[0][0].salary == 1430); + BOOST_TEST(d2D[0][0].age == 35); + + auto&& d2D_names = d2D.member_cast(&employee::name); + BOOST_TEST(size(d2D_names) == size(d2D)); + BOOST_TEST(d2D_names[1][1] == "David"); + +#if !(defined(__clang__) && defined(__CUDACC__)) + multi::array d2D_names_copy_members{d2D.element_transformed(&employee::name)}; + BOOST_TEST(d2D_names_copy_members[1][1] == "David"); + BOOST_TEST(d2D_names_copy_members == d2D_names); + + multi::array d2D_names_copy{d2D_names}; + BOOST_TEST( d2D_names == d2D_names_copy); + BOOST_TEST( d2D_names.base() != d2D_names_copy.base() ); +#endif + } +#endif + +#if !(defined(__NVCC__) || defined(__HIPCC__)) // this doesn't work on cuda 13, triggered by adl uninitialized_copy_n + // BOOST_AUTO_TEST_CASE(element_transformed_from_member) + { + struct record { + int id; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + + double data; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + }; + + // NOLINTBEGIN(modernize-use-designated-initializers) for C++20 + multi::array const recs = { + {{1, 1.1}, {2, 2.2}}, + {{3, 3.3}, {4, 4.4}}, + }; + // NOLINTEND(modernize-use-designated-initializers) for C++20 + + // multi::array ids = recs.element_transformed(std::mem_fn(& A::id)); + multi::array ids{recs.element_transformed(&record::id)}; + + BOOST_TEST( ids[1][1] == 4 ); + BOOST_TEST( ids == recs.member_cast(&record::id) ); + + // recs.element_transformed(std::mem_fn(& A::id) )[1][1] = 5; // not assignable, ok + // BOOST_TEST( recs[1][1].id == 5 ); + } +#endif + +// TODO(correaa) this doesn't work with NVCC (triggered by adl fill) +#if !(defined(__NVCC__) || defined(__HIPCC__)) + // BOOST_AUTO_TEST_CASE(element_transformed_from_member_no_amp) + { + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + + // NOLINTBEGIN(modernize-use-designated-initializers) for C++20 + multi::array d2D = { + { {"Al"s, 1430, 35}, {"Bob"s, 3212, 34}}, + {{"Carl"s, 1589, 32}, {"David"s, 2300, 38}}, + }; + // NOLINTEND(modernize-use-designated-initializers) for C++20 + + // multi::array d2D_ages_copy = + d2D.element_transformed(std::mem_fn(&employee::age)); + BOOST_TEST( d2D.element_transformed(std::mem_fn(&employee::age)) == d2D.element_transformed(&employee::age) ); + } +#endif - multi::array d2D_names_copy{d2D_names}; - BOOST_REQUIRE(d2D_names == d2D_names_copy); - BOOST_REQUIRE(base(d2D_names) != base(d2D_names_copy)); + return boost::report_errors(); } diff --git a/test/minimalistic_ptr.cpp b/test/minimalistic_ptr.cpp index c7c586695..9a491a138 100644 --- a/test/minimalistic_ptr.cpp +++ b/test/minimalistic_ptr.cpp @@ -1,12 +1,16 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi minimalistic pointer" // title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array_ptr, array_ref, subarray -#include +#include -#include +#include // for array +#include // for iterator_traits +#include // for allocator +#include // for is_same, is_convertible, enable... namespace multi = boost::multi; @@ -19,25 +23,40 @@ class ptr : public std::iterator_traits { // minimalistic pointer template friend class ptr; public: - ptr() = default; + ptr() = default; // cppcheck-suppress uninitMemberVar ; + constexpr explicit ptr(T* impl) : impl_{impl} {} - template{}> > - // cppcheck-suppress [noExplicitConstructor,unmatchedSuppression] - ptr(ptr const& other) : impl_{other.impl_} {} // NOLINT(google-explicit-constructor, hicpp-explicit-conversions): ptr -> ptr + + template>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 + // cppcheck-suppress noExplicitConstructor ; + ptr(ptr const& other) : impl_{other.impl_} {} // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) // NOSONAR(cpp:S1709) using typename std::iterator_traits::reference; using typename std::iterator_traits::difference_type; + // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): operator* used because this class simulates a pointer, trailing return helps - constexpr auto operator*() const -> reference {return *impl_;} + constexpr auto operator*() const -> reference { return *impl_; } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif // NOLINTNEXTLINE(fuchsia-overloaded-operator, cppcoreguidelines-pro-bounds-pointer-arithmetic): operator+ is overloaded to simulate a pointer - constexpr auto operator+(difference_type n) const {return ptr{impl_ + n};} + constexpr auto operator+(difference_type n) const { return ptr{impl_ + n}; } // NOLINTNEXTLINE(fuchsia-overloaded-operator, cppcoreguidelines-pro-bounds-pointer-arithmetic): operator+ is overloaded to simulate a pointer - constexpr auto operator-(difference_type n) const {return ptr{impl_ - n};} + constexpr auto operator-(difference_type n) const { return ptr{impl_ - n}; } + + friend constexpr auto operator+(difference_type n, ptr const& self) { return self + n; } -// T& operator[](difference_type n) const{return impl_[n];} // optional +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + // T& operator[](difference_type n) const{return impl_[n];} // optional using default_allocator_type = std::allocator; - template auto operator==(ptr const& other) const& {return impl_ == other.impl_;} + template auto operator==(ptr const& other) const& { return impl_ == other.impl_; } template friend class ptr2; }; @@ -48,53 +67,75 @@ class ptr2 : public std::iterator_traits { // minimalistic pointer public: constexpr explicit ptr2(T* impl) : impl_{impl} {} constexpr explicit ptr2(ptr const& other) : impl_{other.impl_} {} - template>> + template>> // NOLINT(modernize-use-constraints) TODO(correaa) for C++20 // cppcheck-suppress [noExplicitConstructor, unmatchedSuppression] - ptr2(ptr2 const& other) : impl_{other.impl_} {} // NOLINT(google-explicit-constructor, hicpp-explicit-conversions): ptr -> ptr + ptr2(ptr2 const& other) : impl_{other.impl_} {} // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) // NOSONAR(cpp:S1709) using typename std::iterator_traits::reference; using typename std::iterator_traits::difference_type; // NOLINTNEXTLINE(fuchsia-overloaded-operator, fuchsia-trailing-return): operator* used because this class simulates a pointer, trailing return helps - constexpr auto operator*() const -> reference {return *impl_;} + constexpr auto operator*() const -> reference { return *impl_; } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif // NOLINTNEXTLINE(fuchsia-overloaded-operator, cppcoreguidelines-pro-bounds-pointer-arithmetic): operator+ is overloaded to simulate a pointer - constexpr auto operator+(difference_type n) const {return ptr2{impl_ + n};} + constexpr auto operator+(difference_type n) const { return ptr2{impl_ + n}; } // NOLINTNEXTLINE(fuchsia-overloaded-operator, cppcoreguidelines-pro-bounds-pointer-arithmetic): operator+ is overloaded to simulate a pointer - constexpr auto operator-(difference_type n) const {return ptr2{impl_ - n};} + constexpr auto operator-(difference_type n) const { return ptr2{impl_ - n}; } + + friend constexpr auto operator+(difference_type n, ptr2 const& self) { return self + n; } + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif -// T& operator[](std::ptrdiff_t n) const{return impl_[n];} // optional + // T& operator[](std::ptrdiff_t n) const{return impl_[n];} // optional using default_allocator_type = std::allocator; }; } // end namespace minimalistic -BOOST_AUTO_TEST_CASE(test_minimalistic_ptr) { - std::array buffer{}; - BOOST_REQUIRE( buffer.size() == 400 ); - - using pointer_type = minimalistic::ptr; - multi::array_ptr const CCP(pointer_type{buffer.data()}, {20, 20}); - (*CCP)[2]; // requires operator+ - (*CCP)[1][1]; - (*CCP)[1][1] = 9; - BOOST_REQUIRE( &(*CCP)[1][1] == &buffer[21] ); - - auto&& CC2 = CCP->static_array_cast>(); - BOOST_REQUIRE( &CC2[1][1] == &(*CCP)[1][1] ); - - static_assert( std::is_convertible{}, "!"); - - minimalistic::ptr const pd{nullptr}; - minimalistic::ptr const pcd = pd; - BOOST_REQUIRE( pcd == pd ); - - { - auto&& REF = *CCP; (void)REF; - static_assert( std::is_same>{}, "!" ); - } +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(test_minimalistic_ptr) { - auto const& REF = *CCP; (void)REF; - static_assert( std::is_same>{}, "!" ); + std::array buffer{}; + BOOST_TEST( buffer.size() == 400 ); // cppcheck-suppress knownConditionTrueFalse ; for test + + using pointer_type = minimalistic::ptr; + multi::array_ptr const CCP(pointer_type{buffer.data()}, {20, 20}); + (*CCP)[2]; // requires operator+ + (*CCP)[1][1]; + (*CCP)[1][1] = 9; + + BOOST_TEST( (*CCP)[1][1] == 9 ); // cppcheck-suppress knownConditionTrueFalse ; for test + BOOST_TEST( &(*CCP)[1][1] == &buffer[21] ); + + // auto&& CC2 = (*CCP).static_array_cast>(); + auto&& CC2 = CCP->static_array_cast>(); + BOOST_TEST( &CC2[1][1] == &(*CCP)[1][1] ); + + static_assert(std::is_convertible{}, "!"); + + minimalistic::ptr const pd{nullptr}; + minimalistic::ptr const pcd = pd; + BOOST_TEST( pcd == pd ); + + { + auto&& REF = *CCP; + (void)REF; + static_assert(std::is_same_v>); + } + { + auto const& REF = *CCP; + (void)REF; + static_assert(std::is_same_v>); + } } + + return boost::report_errors(); } diff --git a/test/move.cpp b/test/move.cpp index 46c53a1ef..e8589c76d 100644 --- a/test/move.cpp +++ b/test/move.cpp @@ -1,39 +1,191 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2020-2023 Alfredo A. Correa +// Copyright 2020-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 10. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi move" // title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array, apply, array_types<>::ele... -#include +#include -#include +#include // for copy, equal, fill_n, move +#include // for size, back_insert_iterator, back... +#include // for unique_ptr, make_unique, allocat... +// IWYU pragma: no_include // for remove_reference<>::type +// IWYU pragma: no_include +// IWYU pragma: no_include +// IWYU pragma: no_include +#include // for move, swap +#include // for vector, operator==, vector<>::va... -#include // for std::move -#include -#include +// IWYU pragma: no_include // for move namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(move_unique_ptr_1D) { +namespace { + +void move_element_1d_array() { + { + std::vector> varr(3, multi::array({5}, 99), {}); + multi::array marr({3, 5}, 99); + marr[0] = std::move(varr[0]); + marr[1] = std::move(varr[1]); + marr[2] = std::move(varr[2]); + + BOOST_TEST( marr[0][0] == 99 ); + BOOST_TEST( !varr[0].empty() ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + multi::array, 1> brr(10, {}, {}); + + std::copy_n(std::move(arr).begin(), brr.size(), brr.begin()); + BOOST_TEST( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + BOOST_TEST( brr[0].size() == 5 ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + multi::array, 1> brr(10, {}, {}); + + std::copy_n(arr.mbegin(), arr.size(), brr.begin()); + BOOST_TEST( arr[0].empty() ); + BOOST_TEST( brr[0].size() == 5 ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + using std::move; // not necessary, just testing if it works + auto vec = move(arr({2, 6}))[0]; + BOOST_TEST( vec.size() == 5 ); + BOOST_TEST( arr[2].empty() ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + auto mbeg = arr({2, 6}).mbegin(); + auto vec = *mbeg; + BOOST_TEST( vec.size() == 5 ); + BOOST_TEST( arr[2].empty() ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + auto mbeg = arr({2, 6}).mbegin(); + auto vec = *mbeg; + BOOST_TEST( vec.size() == 5 ); + BOOST_TEST( arr[2].empty() ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + std::vector> out_vec(4, {}, {}); + std::copy(arr({2, 6}).mbegin(), arr({2, 6}).mend(), out_vec.begin()); + BOOST_TEST( out_vec[0].size() == 5 ); + BOOST_TEST( arr[2].empty() ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + std::vector> out_vec(4, {}, {}); + + // cppcheck-suppress mismatchingContainerExpression ; + std::copy(multi::move(arr({2, 6})).begin(), multi::move(arr({2, 6})).end(), out_vec.begin()); + BOOST_TEST( out_vec[0].size() == 5 ); + BOOST_TEST( arr[2].empty() ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + std::vector> out_vec(4, {}, {}); + + auto&& marr62 = multi::move(arr({2, 6})); + + // NOLINTNEXTLINE(modernize-use-ranges,bugprone-use-after-move,hicpp-invalid-access-moved) + std::copy(std::move(marr62).begin(), std::move(marr62).end(), out_vec.begin()); + + BOOST_TEST( out_vec[0].size() == 5 ); + BOOST_TEST( arr[2].empty() ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + std::vector> out_vec(4, {}, {}); + auto&& marr62 = arr({2, 6}); + std::copy(multi::move(marr62).begin(), multi::move(marr62).end(), out_vec.begin()); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + BOOST_TEST( out_vec[0].size() == 5 ); + BOOST_TEST( out_vec[1].size() == 5 ); + BOOST_TEST( arr[2].empty() ); + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + std::vector> out_vec(4, {}, {}); + + auto&& marr62 = multi::move(arr({2, 6})); + + std::copy(marr62.begin(), marr62.end(), out_vec.begin()); // NOLINT(modernize-use-ranges) for C++20 + + BOOST_TEST( out_vec[0].size() == 5 ); + BOOST_TEST( !arr[2].empty() ); + } +} + +void move_element_2d_array() { + multi::array, 2> arr({10, 10}, std::vector(5, {}, {})); + + using std::move; + auto vec = move(arr({2, 6}, {2, 6}))[0][0]; + BOOST_TEST( vec.size() == 5 ); + BOOST_TEST( arr[2][2].empty() ); +} + +void move_element_1d_total_array() { + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + auto vec = std::move(arr)[2]; // cppcheck-suppress accessMoved ; + BOOST_TEST( vec.size() == 5 ); + + // cppcheck-suppress accessMoved ; + BOOST_TEST( arr[2].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) + } + { + multi::array, 1> arr(10, std::vector(5, {}, {})); + + using std::move; + auto vec = move(arr)[2]; + BOOST_TEST( vec.size() == 5 ); + + BOOST_TEST( arr[2].empty() ); // cppcheck-suppress accessMoved ; + } +} +} // namespace + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + move_element_1d_array(); + move_element_2d_array(); + + move_element_1d_total_array(); + { multi::array, 1> arr(multi::extensions_t<1>{10}); arr[1] = std::make_unique(42); multi::array, 1> arr2(multi::extensions_t<1>{10}); - std::move(arr.begin(), arr.end(), arr2.begin()); + std::move(arr.begin(), arr.end(), arr2.begin()); // NOLINT(modernize-use-ranges) for C++20 - BOOST_REQUIRE( !arr[1] ); - BOOST_REQUIRE( arr2[1] ); - BOOST_REQUIRE( *arr2[1] == 42 ); + BOOST_TEST( !arr[1] ); + BOOST_TEST( arr2[1] ); + BOOST_TEST( *arr2[1] == 42 ); } + { multi::array, 1> arr(multi::extensions_t<1>{10}); arr[1] = std::make_unique(42); multi::array, 1> arr2 = std::move(arr); - BOOST_REQUIRE( arr.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) - BOOST_REQUIRE( arr2[1] ); - BOOST_REQUIRE( *arr2[1] == 42 ); + BOOST_TEST( arr.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + BOOST_TEST( arr2[1] ); + BOOST_TEST( *arr2[1] == 42 ); } { multi::array, 1> arr(multi::extensions_t<1>{10}); @@ -41,457 +193,558 @@ BOOST_AUTO_TEST_CASE(move_unique_ptr_1D) { multi::array, 1> arr2; // (multi::extensions_t<1>{10}); arr2 = std::move(arr); - BOOST_REQUIRE( arr.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) - BOOST_REQUIRE( arr2[1] ); - BOOST_REQUIRE( *arr2[1] == 42 ); + BOOST_TEST( arr.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + BOOST_TEST( arr2[1] ); + BOOST_TEST( *arr2[1] == 42 ); } + + // BOOST_AUTO_TEST_CASE(move_unique_ptr_1D) { - multi::array, 1> arr(multi::extensions_t<1>{10}); - arr[1] = std::make_unique(42); + // { + // multi::array, 1> arr(multi::extensions_t<1>{10}); + // arr[1] = std::make_unique(42); + + // multi::array, 1> arr2(multi::extensions_t<1>{10}); + // // arr2() = arr(); // fails to compile, elements are not copy assignable + // arr2() = arr().element_moved(); + // BOOST_TEST( !arr[1] ); + // BOOST_TEST( arr2[1] ); + // BOOST_TEST( *arr2[1] == 42 ); + // } + } - multi::array, 1> arr2(multi::extensions_t<1>{10}); - // arr2() = arr(); // fails to compile, elements are not copy assignable - arr2() = arr().moved(); - BOOST_REQUIRE( !arr[1] ); - BOOST_REQUIRE( arr2[1] ); - BOOST_REQUIRE( *arr2[1] == 42 ); + // BOOST_AUTO_TEST_CASE(multi_swap) + { +#ifndef _MSC_VER // TODO(correaa) problems with 14.3 c++17 + multi::array arr({3, 5}, 990); + multi::array arr2({7, 11}, 880); +#else + multi::array arr(multi::extensions_t<2>{3, 5}, 990); + multi::array arr2(multi::extensions_t<2>{7, 11}, 880); +#endif + + swap(arr, arr2); + + BOOST_TEST( size(arr) == 7 ); + BOOST_TEST( arr[1][2] == 880 ); + BOOST_TEST( arr2[1][2] == 990 ); } -} -BOOST_AUTO_TEST_CASE(multi_swap) { - multi::array arr({3, 5}, 99.); - multi::array arr2({7, 11}, 88.); - swap(arr, arr2); - BOOST_REQUIRE( size(arr) == 7 ); - BOOST_REQUIRE( arr[1][2] == 88. ); - BOOST_REQUIRE( arr2[1][2] == 99. ); -} + // BOOST_AUTO_TEST_CASE(multi_std_swap) + { +#ifndef _MSC_VER // problems with 14.3 c++17 + multi::array arr({3, 5}, 990); + multi::array arr2({7, 11}, 880); +#else + multi::array arr(multi::extensions_t<2>{3, 5}, 990); + multi::array arr2(multi::extensions_t<2>{7, 11}, 880); +#endif + + using std::swap; + swap(arr, arr2); + + BOOST_TEST( size(arr) == 7 ); + BOOST_TEST( arr[1][2] == 880 ); + BOOST_TEST( arr2[1][2] == 990 ); + } -BOOST_AUTO_TEST_CASE(multi_std_swap) { - multi::array arr({3, 5}, 99.); - multi::array arr2({7, 11}, 88.); - using std::swap; - swap(arr, arr2); - BOOST_REQUIRE( size(arr) == 7 ); - BOOST_REQUIRE( arr[1][2] == 88. ); - BOOST_REQUIRE( arr2[1][2] == 99. ); -} + // BOOST_AUTO_TEST_CASE(multi_array_clear) + { + multi::array arr({10, 10}, 990); -BOOST_AUTO_TEST_CASE(multi_array_clear) { - multi::array arr({10, 10}, 99.); - arr.clear(); - BOOST_REQUIRE(arr.is_empty()); - arr.reextent({20, 20}, 99.); - BOOST_REQUIRE(not arr.is_empty()); - clear(arr).reextent({30, 30}, 88.); - BOOST_REQUIRE(arr[15][15] == 88.); -} + arr.clear(); -BOOST_AUTO_TEST_CASE(multi_array_move) { - std::vector > Av(10, multi::array({4, 5}, 99.0)); // std::vector NOLINT(fuchsia-default-arguments-calls) - multi::array arr2(std::move(Av[0]), std::allocator{}); + BOOST_TEST(arr.is_empty()); - BOOST_REQUIRE( is_empty(Av[0]) ); - BOOST_REQUIRE( size(arr2) == 4 ); - BOOST_REQUIRE( arr2[1][2] == 99. ); -} + arr.reextent({20, 20}, 990); + // BOOST_TEST(! arr.is_empty()); -BOOST_AUTO_TEST_CASE(multi_array_move_into_vector) { - std::vector > Av(10, multi::array({4, 5}, 99.0)); // NOLINT(fuchsia-default-arguments-calls) - std::vector > Bv; Bv.reserve(Av.size()); // NOLINT(fuchsia-default-arguments-calls) + // clear(arr).reextent({30, 30}, 88.0); + // BOOST_TEST(arr[15][15] == 88.0); + } - std::move( begin(Av), end(Av), std::back_inserter(Bv) ); + // BOOST_AUTO_TEST_CASE(multi_array_move) + { + std::vector> Av(10, multi::array({4, 5}, 990)); // std::vector NOLINT(fuchsia-default-arguments-calls) + multi::array arr2(std::move(Av[0]), std::allocator{}); - BOOST_REQUIRE( size(Bv) == size(Av) ); - BOOST_REQUIRE( is_empty(Av[4]) ); - BOOST_REQUIRE( size(Bv[5]) == 4 ); - BOOST_REQUIRE( Bv[5][1][2] == 99.0 ); -} + BOOST_TEST( is_empty(Av[0]) ); + BOOST_TEST( size(arr2) == 4 ); + BOOST_TEST( arr2[1][2] == 990 ); + } -BOOST_AUTO_TEST_CASE(multi_array_move_into_vector_reserve) { - std::vector > Av(10, multi::array({4, 5}, 99.0)); // NOLINT(fuchsia-default-arguments-calls) - std::vector > Bv; Bv.reserve(Av.size()); + // BOOST_AUTO_TEST_CASE(multi_array_move_into_vector) + { + std::vector> Av(10, multi::array({4, 5}, 990)); // NOLINT(fuchsia-default-arguments-calls) + std::vector> Bv; + Bv.reserve(Av.size()); // NOLINT(fuchsia-default-arguments-calls) -// for(auto& v: Av) Bv.emplace_back(std::move(v), std::allocator{}); // segfaults nvcc 11.0 but not nvcc 11.1 - std::move(begin(Av), end(Av), std::back_inserter(Bv)); + std::move(begin(Av), end(Av), std::back_inserter(Bv)); // NOLINT(modernize-use-ranges) for C++20 - BOOST_REQUIRE( size(Bv) == size(Av) ); - BOOST_REQUIRE( is_empty(Av[4]) ); - BOOST_REQUIRE( size(Bv[5]) == 4 ); - BOOST_REQUIRE( Bv[5][1][2] == 99.0 ); -} + BOOST_TEST( size(Bv) == size(Av) ); + BOOST_TEST( is_empty(Av[4]) ); + BOOST_TEST( size(Bv[5]) == 4 ); + BOOST_TEST( Bv[5][1][2] == 990 ); + } -BOOST_AUTO_TEST_CASE(multi_array_move_into_vector_move) { - std::vector > Av(10, multi::array({4, 5}, 99.0)); // std::vector NOLINT(fuchsia-default-arguments-calls) - std::vector > Bv = std::move(Av); + // BOOST_AUTO_TEST_CASE(multi_array_move_into_vector_reserve) + { + std::vector> Av(10, multi::array({4, 5}, 990)); // NOLINT(fuchsia-default-arguments-calls) + std::vector> Bv; + Bv.reserve(Av.size()); - Av.clear(); - BOOST_REQUIRE( size(Av) == 0 ); - BOOST_REQUIRE( size(Bv) == 10 ); - BOOST_REQUIRE( size(Bv[5]) == 4 ); - BOOST_REQUIRE( Bv[5][1][2] == 99.0 ); -} + // for(auto& v: Av) Bv.emplace_back(std::move(v), std::allocator{}); // segfaults nvcc 11.0 but not nvcc 11.1 + std::move(begin(Av), end(Av), std::back_inserter(Bv)); // NOLINT(modernize-use-ranges) for C++20 -BOOST_AUTO_TEST_CASE(multi_array_move_array) { - multi::array, 2> arr({10, 10}, std::vector(5) ); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = std::move(arr); - BOOST_REQUIRE( arr . empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) test deterministic moved from state - BOOST_REQUIRE( arr .is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) test deterministic moved from state - BOOST_REQUIRE( arr2.size() == 10 ); -} + BOOST_TEST( size(Bv) == size(Av) ); + BOOST_TEST( is_empty(Av[4]) ); + BOOST_TEST( size(Bv[5]) == 4 ); + BOOST_TEST( Bv[5][1][2] == 990 ); + } -BOOST_AUTO_TEST_CASE(multi_array_move_elements) { - multi::array, 1> arr({10}, std::vector(5) ); // std::vector NOLINT(fuchsia-default-arguments-calls) + // BOOST_AUTO_TEST_CASE(multi_array_move_into_vector_move) + { + std::vector> Av(10, multi::array({4, 5}, 990)); // std::vector NOLINT(fuchsia-default-arguments-calls) + std::vector> Bv = std::move(Av); - std::vector> sink(5); // std::vector NOLINT(fuchsia-default-arguments-calls) + Av.clear(); - auto* ptr1 = arr[1].data(); + BOOST_TEST( size(Av) == 0 ); + BOOST_TEST( size(Bv) == 10 ); + BOOST_TEST( size(Bv[5]) == 4 ); + BOOST_TEST( Bv[5][1][2] == 990 ); + } - std::copy( arr({0, 5}).moved().begin(), arr({0, 5}).moved().end(), sink.begin() ); - BOOST_REQUIRE( arr[1].empty() ); - BOOST_REQUIRE( not arr[5].empty() ); + // BOOST_AUTO_TEST_CASE(multi_array_move_array) + { + multi::array, 2> arr({10, 10}, std::vector(5)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = std::move(arr); - BOOST_REQUIRE( sink[1].data() == ptr1 ); -} + // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) test deterministic moved from state + BOOST_TEST( arr . empty() ); -BOOST_AUTO_TEST_CASE(multi_array_move_elements_range) { - multi::array, 1> arr({10}, std::vector(5) ); // std::vector NOLINT(fuchsia-default-arguments-calls) + // NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) test deterministic moved from state + BOOST_TEST( arr .is_empty() ); + BOOST_TEST( arr2.size() == 10 ); + } - std::vector> sink(5); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_AUTO_TEST_CASE(multi_array_move_elements) + { + multi::array, 1> arr({10}, std::vector(5)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto* ptr1 = arr[1].data(); + std::vector> sink(5); // std::vector NOLINT(fuchsia-default-arguments-calls) - std::copy( arr({0, 5}).moved().elements().begin(), arr({0, 5}).moved().elements().end(), sink.begin() ); - BOOST_REQUIRE( arr[1].empty() ); - BOOST_REQUIRE( not arr[5].empty() ); + auto* ptr1 = arr[1].data(); - BOOST_REQUIRE( sink[1].data() == ptr1 ); -} + // cppcheck-suppress mismatchingContainerExpression ; + std::copy(arr({0, 5}).element_moved().begin(), arr({0, 5}).element_moved().end(), sink.begin()); + BOOST_TEST( arr[1].empty() ); + BOOST_TEST( !arr[5].empty() ); -BOOST_AUTO_TEST_CASE(multi_array_move_elements_to_array) { - multi::array, 1> arr({10}, std::vector(5, 99.0) ); // std::vector NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr.size() == 10 ); - multi::array, 1> arr2({ 5}, {}, {}); // std::vector NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( sink[1].data() == ptr1 ); + } - auto* ptr1 = arr[1].data(); + // BOOST_AUTO_TEST_CASE(multi_array_move_elements_range) + { + multi::array, 1> arr({10}, std::vector(5)); // std::vector NOLINT(fuchsia-default-arguments-calls) - arr2().elements() = arr({0, 5}).moved().elements(); + std::vector> sink(5); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1].size() == 5 ); - BOOST_REQUIRE( arr2[1][4] == 99.0 ); + auto* ptr1 = arr[1].data(); - BOOST_REQUIRE( arr[1].empty() ); - BOOST_REQUIRE( not arr[5].empty() ); + // cppcheck-suppress mismatchingContainerExpression ; + std::copy(arr({0, 5}).element_moved().elements().begin(), arr({0, 5}).element_moved().elements().end(), sink.begin()); + BOOST_TEST( arr[1].empty() ); + BOOST_TEST( !arr[5].empty() ); - BOOST_REQUIRE( arr2[1].data() == ptr1 ); -} + BOOST_TEST( sink[1].data() == ptr1 ); + } -BOOST_AUTO_TEST_CASE(move_range_vector_1D) { - std::vector> arr(10, std::vector{1.0, 2.0, 3.0}); // NOLINT(fuchsia-default-arguments-calls) - std::vector> arr2(10); // NOLINT(fuchsia-default-arguments-calls) - std::move(arr.begin(), arr.end(), arr2.begin()); + // BOOST_AUTO_TEST_CASE(multi_array_move_elements_to_array) + { + multi::array, 1> arr({10}, std::vector(5, 990)); // std::vector NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr.size() == 10 ); + multi::array, 1> arr2({5}, {}, {}); // std::vector NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + auto* ptr1 = arr[1].data(); - BOOST_REQUIRE( arr[0].empty() ); - BOOST_REQUIRE( arr[1].empty() ); -} + arr2().elements() = arr({0, 5}).element_moved().elements(); -BOOST_AUTO_TEST_CASE(copy_range_1D) { - multi::array, 1> arr({3}, std::vector{1.0, 2.0, 3.0}); // std::vector NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr.size() == 3 ); - multi::array, 1> arr2({3}, std::vector{}); - std::copy(arr.begin(), arr.end(), arr2.begin()); + BOOST_TEST( arr2[1].size() == 5 ); + BOOST_TEST( arr2[1][4] == 990 ); - BOOST_REQUIRE( arr2[0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr[1].empty() ); + BOOST_TEST( !arr[5].empty() ); - BOOST_REQUIRE( arr [0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr [1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) -} + BOOST_TEST( arr2[1].data() == ptr1 ); + } -BOOST_AUTO_TEST_CASE(move_range_1D) { - multi::array, 1> arr({3}, std::vector{1.0, 2.0, 3.0}); // std::vector NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr.size() == 3 ); - multi::array, 1> arr2({3}, std::vector{}); // std::vector NOLINT(fuchsia-default-arguments-calls) - std::move(arr.begin(), arr.end(), arr2.begin()); + // BOOST_AUTO_TEST_CASE(move_range_vector_1D) + { + std::vector> arr(10, std::vector{10, 20, 30}); // NOLINT(fuchsia-default-arguments-calls) + std::vector> arr2(10); // NOLINT(fuchsia-default-arguments-calls) + std::move(arr.begin(), arr.end(), arr2.begin()); // NOLINT(modernize-use-ranges) for C++20 - BOOST_REQUIRE( arr2[0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr[0].empty() ); - BOOST_REQUIRE( arr[1].empty() ); -} + BOOST_TEST( arr[0].empty() ); + BOOST_TEST( arr[1].empty() ); + } -BOOST_AUTO_TEST_CASE(move_range_1D_moved_begin) { - multi::array, 1> arr({3}, std::vector{1.0, 2.0, 3.0}); // std::vector NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr.size() == 3 ); - multi::array, 1> arr2({3}, std::vector{}); // std::vector NOLINT(fuchsia-default-arguments-calls) - std::copy(arr.mbegin(), arr.mend(), arr2.begin()); + // BOOST_AUTO_TEST_CASE(copy_range_1D) + { + multi::array, 1> arr({3}, std::vector{10, 20, 30}); // std::vector NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr.size() == 3 ); + multi::array, 1> arr2({3}, std::vector{}); + std::copy(arr.begin(), arr.end(), arr2.begin()); // NOLINT(modernize-use-ranges) - BOOST_REQUIRE( arr2[0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr[0].empty() ); - BOOST_REQUIRE( arr[1].empty() ); -} + BOOST_TEST( arr [0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr [1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + } -BOOST_AUTO_TEST_CASE(copy_move_range) { - multi::array, 2> arr ({10, 20}, std::vector{1.0, 2.0, 3.0}); // std::vector NOLINT(fuchsia-default-arguments-calls) - multi::array, 2> arr2({10, 20}, std::vector{} ); // std::vector NOLINT(fuchsia-default-arguments-calls) + // BOOST_AUTO_TEST_CASE(move_range_1D) + { + multi::array, 1> arr({3}, std::vector{10, 20, 30}); // std::vector NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr.size() == 3 ); + multi::array, 1> arr2({3}, std::vector{}); // std::vector NOLINT(fuchsia-default-arguments-calls) + std::move(arr.begin(), arr.end(), arr2.begin()); // NOLINT(modernize-use-ranges) - std::copy(arr.mbegin(), arr.mend(), arr2.begin()); + BOOST_TEST( arr2[0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[0][0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[0][1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr[0].empty() ); + BOOST_TEST( arr[1].empty() ); + } - BOOST_REQUIRE( arr2[1][0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1][1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_AUTO_TEST_CASE(move_range_1D_moved_begin) { + // multi::array, 1> arr({3}, std::vector{10, 20, 30}); // std::vector NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( arr.size() == 3 ); + // multi::array, 1> arr2({3}, std::vector{}); // std::vector NOLINT(fuchsia-default-arguments-calls) + // std::copy(arr.mbegin(), arr.mend(), arr2.begin()); - BOOST_REQUIRE( arr[0][0].empty() ); - BOOST_REQUIRE( arr[0][1].empty() ); + // BOOST_TEST( arr2[0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( arr2[1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr[1][0].empty() ); - BOOST_REQUIRE( arr[1][1].empty() ); -} + // BOOST_TEST( arr[0].empty() ); + // BOOST_TEST( arr[1].empty() ); + // } -BOOST_AUTO_TEST_CASE(copy_move_range_moved_begin) { - multi::array, 2> arr ({10, 20}, std::vector{1.0, 2.0, 3.0}); // NOLINT(fuchsia-default-arguments-calls) - multi::array, 2> arr2({10, 20}, std::vector{} ); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_AUTO_TEST_CASE(copy_move_range) { + // multi::array, 2> arr({10, 20}, std::vector{10, 20, 30}); // std::vector NOLINT(fuchsia-default-arguments-calls) + // multi::array, 2> arr2({10, 20}, std::vector{}); // std::vector NOLINT(fuchsia-default-arguments-calls) - std::copy(arr.moved().begin(), arr.moved().end(), arr2.begin()); + // std::copy(arr.mbegin(), arr.mend(), arr2.begin()); - BOOST_REQUIRE( arr2[0][0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[0][1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( arr2[0][0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( arr2[0][1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1][0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1][1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( arr2[1][0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( arr2[1][1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr[0][0].empty() ); - BOOST_REQUIRE( arr[0][1].empty() ); + // BOOST_TEST( arr[0][0].empty() ); + // BOOST_TEST( arr[0][1].empty() ); - BOOST_REQUIRE( arr[1][0].empty() ); - BOOST_REQUIRE( arr[1][1].empty() ); -} + // BOOST_TEST( arr[1][0].empty() ); + // BOOST_TEST( arr[1][1].empty() ); + // } -BOOST_AUTO_TEST_CASE(copy_move_range_moved_begin_block) { - multi::array, 2> arr ({10, 20}, std::vector{1.0, 2.0, 3.0}); // NOLINT(fuchsia-default-arguments-calls) - multi::array, 2> arr2({ 3, 5}, std::vector{} ); + // BOOST_AUTO_TEST_CASE(copy_move_range_moved_begin) + { + multi::array, 2> arr({10, 20}, std::vector{10, 20, 30}); // NOLINT(fuchsia-default-arguments-calls) + multi::array, 2> arr2({10, 20}, std::vector{}); // NOLINT(fuchsia-default-arguments-calls) - std::copy(arr({5, 8}, {10, 15}).moved().begin(), arr({5, 8}, {10, 15}).moved().end(), arr2.begin()); + std::copy(arr.element_moved().begin(), arr.element_moved().end(), arr2.begin()); - BOOST_REQUIRE( arr2[0][0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[0][1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[0][0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[0][1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1][0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1][1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[1][0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[1][1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr[5][10].empty() ); - BOOST_REQUIRE( arr[5][11].empty() ); + // BOOST_TEST( arr[0][0].size() == 0); + // BOOST_TEST( arr[0][0].empty() ); + // BOOST_TEST( arr[0][1].empty() ); - BOOST_REQUIRE( arr[6][10].empty() ); - BOOST_REQUIRE( arr[6][11].empty() ); -} + // BOOST_TEST( arr[1][0].empty() ); + // BOOST_TEST( arr[1][1].empty() ); + } + // BOOST_AUTO_TEST_CASE(copy_move_range_moved_begin_block) + { + multi::array, 2> arr({10, 20}, std::vector{10, 20, 30}); // NOLINT(fuchsia-default-arguments-calls) + multi::array, 2> arr2({3, 5}, std::vector{}); -BOOST_AUTO_TEST_CASE(move_reference_range) { - multi::array, 2> arr ({10, 20}, std::vector{1.0, 2.0, 3.0}); // std::vector NOLINT(fuchsia-default-arguments-calls) - multi::array, 2> arr2({10, 20}, std::vector{} ); // std::vector NOLINT(fuchsia-default-arguments-calls) + // cppcheck-suppress mismatchingContainerExpression ; + std::copy(arr({5, 8}, {10, 15}).element_moved().begin(), arr({5, 8}, {10, 15}).element_moved().end(), arr2.begin()); -// arr2() = arr().moved(); - std::copy(arr().moved().begin(), arr().moved().end(), arr2().begin()); + BOOST_TEST( arr2[0][0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[0][1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[0][0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[0][1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[1][0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[1][1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1][0] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( arr2[1][1] == std::vector({1.0, 2.0, 3.0}) ); // NOLINT(fuchsia-default-arguments-calls) + // BOOST_TEST( arr[5][10].empty() ); + // BOOST_TEST( arr[5][11].empty() ); - BOOST_REQUIRE( arr[0][0].empty() ); - BOOST_REQUIRE( arr[0][1].empty() ); + // BOOST_TEST( arr[6][10].empty() ); + // BOOST_TEST( arr[6][11].empty() ); + } - BOOST_REQUIRE( arr[1][0].empty() ); - BOOST_REQUIRE( arr[1][1].empty() ); -} + // BOOST_AUTO_TEST_CASE(move_reference_range) + { + multi::array, 2> arr({10, 20}, std::vector{10, 20, 30}); // std::vector NOLINT(fuchsia-default-arguments-calls) + multi::array, 2> arr2({10, 20}, std::vector{}); // std::vector NOLINT(fuchsia-default-arguments-calls) + + // arr2() = arr().element_moved(); + std::copy(arr().element_moved().begin(), arr().element_moved().end(), arr2().begin()); + + BOOST_TEST( arr2[0][0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[0][1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( arr2[1][0] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( arr2[1][1] == std::vector({10, 20, 30}) ); // NOLINT(fuchsia-default-arguments-calls) + + // BOOST_TEST( arr[0][0].empty() ); + // BOOST_TEST( arr[0][1].empty() ); + + // BOOST_TEST( arr[1][0].empty() ); + // BOOST_TEST( arr[1][1].empty() ); + } -BOOST_AUTO_TEST_CASE(move_array_elements) { // NOLINT(readability-function-cognitive-complexity) + // BOOST_AUTO_TEST_CASE(move_array_elements) { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) auto arr2 = std::move(arr); - BOOST_REQUIRE( arr2.size() == 5 ); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + BOOST_TEST( arr2.size() == 5 ); + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr.is_empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - std::vector const v0 = std::move(arr[0]); - BOOST_REQUIRE( v0.size() == 7 ); - BOOST_REQUIRE( arr[0].empty() ); + std::vector const v0 = std::move(arr[0]); + BOOST_TEST( v0.size() == 7 ); // cppcheck-suppress accessMoved ; + BOOST_TEST( arr[0].empty() ); - std::vector const v1 = std::move(arr)[1]; - BOOST_REQUIRE( v1.size() == 7 ); - BOOST_REQUIRE( arr[1].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) for test + std::vector const v1 = std::move(arr)[1]; + BOOST_TEST( v1.size() == 7 ); - auto arr2 = multi::array, 1>({ 1}, std::vector{}); + // cppcheck-suppress accessMoved ; + BOOST_TEST( arr[1].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) for test + + auto arr2 = multi::array, 1>({1}, std::vector{}); arr2({0, 1}) = arr({2, 3}); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [2].size() == 7 ); + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr [2].size() == 7 ); } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); arr2() = arr(); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [2].size() == 7 ); + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr [2].size() == 7 ); arr2() = std::move(arr)(); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [2].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + BOOST_TEST( arr2[0].size() == 7 ); + // BOOST_TEST( arr [2].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); arr2() = arr(); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].size() == 7 ); + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr [0].size() == 7 ); } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); arr2() = std::move(arr)(); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + BOOST_TEST( arr2[0].size() == 7 ); + // BOOST_TEST( arr [0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); auto&& mAp = std::move(arr)(); + arr2() = mAp; - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + + BOOST_TEST( arr2[0].size() == 7 ); + // cppcheck-suppress accessMoved ; + BOOST_TEST( arr [0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); arr2({0, 5}) = std::move(arr)(); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + BOOST_TEST( arr2[0].size() == 7 ); + // BOOST_TEST( arr [0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); - arr2() = arr.take(5); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].size() == 7); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + arr2() = arr.taked(5); + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr [0].size() == 7); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); - arr2() = std::move(arr).take(5); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + arr2() = std::move(arr).taked(5); + BOOST_TEST( arr2[0].size() == 7 ); + // BOOST_TEST( arr [0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); - auto&& mAt5 = std::move(arr).take(5); - arr2() = mAt5; - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); + auto&& mAt5 = std::move(arr).taked(5); + arr2() = mAt5; + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr [0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); - auto&& mAt5 = std::move(arr).take(5); - arr2() = mAt5; - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); + auto&& mAt5 = std::move(arr).taked(5); + arr2() = mAt5; + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr [0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); - auto&& mAt5 = std::move(arr).take(5); - arr2() = std::move(mAt5); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); + auto&& mAt5 = std::move(arr).taked(5); + arr2() = std::move(mAt5); // NOLINT(hicpp-move-const-arg,performance-move-const-arg) just testing + BOOST_TEST( arr2[0].size() == 7 ); + // BOOST_TEST( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); - auto&& mAt5 = std::move(arr).take(5); - arr2() = std::move(mAt5).take(5); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); + auto&& mAt5 = std::move(arr).taked(5); + arr2() = std::move(mAt5).taked(5); + BOOST_TEST( arr2[0].size() == 7 ); + // BOOST_TEST( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); - auto&& mAt5 = std::move(arr).take(5); - auto&& mAt5t5 = std::move(mAt5).take(5); - arr2() = mAt5t5; - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr[0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); + auto&& mAt5 = std::move(arr).taked(5); + auto&& mAt5t5 = std::move(mAt5).taked(5); + arr2() = mAt5t5; + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr[0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 5}, std::vector{} ); - auto&& mAt5 = std::move(arr).take(5); - arr2() = std::move(mAt5).drop(0); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({5}, std::vector{}); + auto&& mAt5 = std::move(arr).taked(5); + arr2() = std::move(mAt5).dropped(0); + BOOST_TEST( arr2[0].size() == 7 ); + // BOOST_TEST( arr[0].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } { - auto arr = multi::array, 1>({ 5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) - auto arr2 = multi::array, 1>({ 4}, std::vector{} ); // std::vector NOLINT(fuchsia-default-arguments-calls) - arr2() = std::move(arr).drop(1); - BOOST_REQUIRE( arr2[0].size() == 7 ); - BOOST_REQUIRE( arr [0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing - BOOST_REQUIRE( arr [1].empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing + auto arr = multi::array, 1>({5}, std::vector(7)); // std::vector NOLINT(fuchsia-default-arguments-calls) + auto arr2 = multi::array, 1>({4}, std::vector{}); // std::vector NOLINT(fuchsia-default-arguments-calls) + arr2() = std::move(arr).dropped(1); + BOOST_TEST( arr2[0].size() == 7 ); + BOOST_TEST( arr [0].size() == 7 ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved) for testing } -} -BOOST_AUTO_TEST_CASE(multi_array_view_swap) { - multi::array arrA({4, 5}, 99); - multi::array arrB({4, 5}, 88); + // BOOST_AUTO_TEST_CASE(multi_array_view_swap) + { + multi::array arrA({4, 5}, 99); + multi::array arrB({4, 5}, 88); - arrA().swap(arrB()); + arrA().swap(arrB()); - BOOST_REQUIRE( arrA[0][0] == 88 ); - BOOST_REQUIRE( arrB[0][0] == 99 ); -} + BOOST_TEST( arrA[0][0] == 88 ); + BOOST_TEST( arrB[0][0] == 99 ); + } -BOOST_AUTO_TEST_CASE(multi_array_view_swap_dimension_1) { - multi::array arrA({4, 5}, 99); - multi::array arrB({4, 5}, 88); + // BOOST_AUTO_TEST_CASE(multi_array_view_swap_dimension_1) + { + multi::array arrA({4, 5}, 99); + multi::array arrB({4, 5}, 88); - arrA[0].swap(arrB[0]); + arrA[0].swap(arrB[0]); - BOOST_REQUIRE( arrA[0][0] == 88 ); - BOOST_REQUIRE( arrB[0][0] == 99 ); + BOOST_TEST( arrA[0][0] == 88 ); + BOOST_TEST( arrB[0][0] == 99 ); - BOOST_REQUIRE( arrA[1][0] == 99 ); - BOOST_REQUIRE( arrB[1][0] == 88 ); -} + BOOST_TEST( arrA[1][0] == 99 ); + BOOST_TEST( arrB[1][0] == 88 ); + } + + // BOOST_AUTO_TEST_CASE(multi_array_view_swap_dimension_1_free) + { + multi::array arrA({4, 5}, 99); + multi::array arrB({4, 5}, 88); + + swap(arrA[0], arrB[0]); + + BOOST_TEST( arrA[0][0] == 88 ); + BOOST_TEST( arrB[0][0] == 99 ); + + BOOST_TEST( arrA[1][0] == 99 ); + BOOST_TEST( arrB[1][0] == 88 ); + } + + // BOOST_AUTO_TEST_CASE(move_array_vector_1d) + { + multi::array, 1> arrA(10, std::vector(5, 0.0, {})); -BOOST_AUTO_TEST_CASE(multi_array_view_swap_dimension_1_free) { - multi::array arrA({4, 5}, 99); - multi::array arrB({4, 5}, 88); + BOOST_TEST( arrA[2].size() == 5 ); + { + multi::array, 1> arrB = std::move(arrA); - swap(arrA[0], arrB[0]); + BOOST_TEST( arrA.empty() ); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved,clang-analyzer-cplusplus.Move) + BOOST_TEST( arrB.size() == 10 ); + BOOST_TEST( arrB[2].size() == 5 ); + } + } + + // BOOST_AUTO_TEST_CASE(move_subarray_vector_1d) + { + multi::array, 1> arrA(10, std::vector(5, 0.0)); // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( arrA[2].size() == 5 ); + { + // this is not wrong but it is misleading since std::move is not doing anything on a reference-like type + // using std::move; + // multi::array, 1> arrB = move(arrA()); // NOLINT(clang-diagnostic-unqualified-std-cast-call) + + multi::array, 1> arrB = arrA(); // NOLINT(clang-diagnostic-unqualified-std-cast-call) - BOOST_REQUIRE( arrA[0][0] == 88 ); - BOOST_REQUIRE( arrB[0][0] == 99 ); + BOOST_TEST( arrA.size() == 10 ); + BOOST_TEST( arrB.size() == 10 ); + BOOST_TEST( arrA[2].size() == 5 ); + BOOST_TEST( arrB[2].size() == 5 ); + } + } + + { + multi::array arr = { + {1, 2}, + {3, 4}, + }; + auto const* arr_base = arr.base(); + + multi::array brr = std::move(arr); + BOOST_TEST( brr.base() == arr_base ); + } - BOOST_REQUIRE( arrA[1][0] == 99 ); - BOOST_REQUIRE( arrB[1][0] == 88 ); + return boost::report_errors(); } diff --git a/test/nico_const_correctness.cpp b/test/nico_const_correctness.cpp index 368b1d976..ead0c9383 100644 --- a/test/nico_const_correctness.cpp +++ b/test/nico_const_correctness.cpp @@ -1,111 +1,146 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2022 Alfredo A. Correa +// Copyright 2022-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi views constness" // test title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for subarray, array, range, operator!= -#include "multi/array.hpp" +#include -#include +#include // for fill, copy, for_each +#include // for operator<<, basic_ostream::opera... +#include // for begin, end, ostream_iterator +#include // for decay_t +#include // for forward namespace multi = boost::multi; +namespace { template void print(Array1D const& coll) { // *coll.begin() = 99; // doesn't compile "assignment of read-only location" - std::copy(coll.begin(), coll.end(), std::ostream_iterator{std::cout, ", "}); - std::cout<(std::cout, ", ")); + std::cout << '\n'; } -BOOST_AUTO_TEST_CASE(const_views) { - multi::array coll1 = {0, 8, 15, 47, 11, 42}; - print(coll1); // prints "0, 8, 15, 47, 11, 42" +template +auto fill_99(Array1D&& col) -> Array1D&& { + std::fill(std::begin(col), std::end(col), 99); + return std::forward(col); +} - print(coll1({0, 3})); // similar to coll1 | take(3) // prints "0, 8, 15" +template +void print_2d(Array2D const& coll) { + // *(coll.begin()->begin()) = 99; // doesn't compile "assignment of read-only location" - auto&& coll1_take3 = coll1({0, 3}); - print(coll1_take3); // prints "0, 8, 15" + std::for_each(std::begin(coll), std::end(coll), [](auto const& row) { + std::copy(std::begin(row), std::end(row), std::ostream_iterator(std::cout, ", ")); + std::cout << '\n'; + }); } template -void fill_99(Array1D&& coll) { - std::fill(coll.begin(), coll.end(), 99); +auto fill_2d_99(Array1D&& coll) -> Array1D&& { + // for(auto const& row : coll) { // does not work because it would make it const + std::for_each(std::begin(coll), std::end(coll), [](typename std::decay_t::reference row) { + std::fill(std::begin(row), std::end(row), 99); + }); + // std::transform(coll.begin(), coll.end(), coll.begin(), [](auto&& row) { + // std::fill(row.begin(), row.end(), 99); + // return std::forward(row); + // }); + return std::forward(coll); } +} // end unnamed namespace -BOOST_AUTO_TEST_CASE(mutating_views) { - multi::array coll1 = {0, 8, 15, 47, 11, 42}; +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(const_views) + { + multi::array coll1 = {0, 8, 15, 47, 11, 42}; + print(coll1); // prints "0, 8, 15, 47, 11, 42" - fill_99(coll1); - fill_99(coll1({0, 3})); + print(coll1({0, 3})); // similar to coll1 | take(3) // prints "0, 8, 15" - auto&& coll1_take3 = coll1({0, 3}); - fill_99(coll1_take3); + auto&& coll1_take3 = coll1({0, 3}); + print(coll1_take3); // prints "0, 8, 15" + } - auto const& coll2 = coll1; - // fill_99( coll2 ); // doesn't compile because coll2 is const ("assignment of read-only" inside fill_99) - // fill_99( coll2({0, 3}) ); // similar to coll2 | take(3) doesn't compile ("assignment of read-only") + // BOOST_AUTO_TEST_CASE(mutating_views) + { + multi::array coll1 = {0, 8, 15, 47, 11, 42}; - auto const& coll1_take3_const = coll1({0, 3}); - // fill_99( coll1_take3_const ); // doesn't compile because coll1_take3_const is const ("assignment of read-only") + fill_99(coll1); + fill_99(coll1({0, 3})); - (void)coll2, (void)coll1_take3_const, (void)coll1_take3; -} + auto&& coll1_take3 = coll1({0, 3}); + fill_99(coll1_take3); -template -void print_2d(Array2D const& coll) { - // *(coll.begin()->begin()) = 99; // doesn't compile "assignment of read-only location" + auto const& coll2 = coll1; + // fill_99( coll2 ); // doesn't compile because coll2 is const ("assignment of read-only" inside fill_99) + // fill_99( coll2({0, 3}) ); // similar to coll2 | take(3) doesn't compile ("assignment of read-only") - std::for_each(coll.begin(), coll.end(), [](auto const& row) { - std::copy(row.begin(), row.end(), std::ostream_iterator(std::cout, ", ")); - std::cout< coll1 = { - {0, 8, 15, 47, 11, 42}, - {0, 8, 15, 47, 11, 42} - }; + (void)coll2, (void)coll1_take3_const, (void)coll1_take3; + } - print_2d(coll1); // prints "0, 8, 15, 47, 11, 42" + // BOOST_AUTO_TEST_CASE(const_views_2d) + { + multi::array coll1 = { + {0, 8, 15, 47, 11, 42}, + {0, 8, 15, 47, 11, 42}, + }; - print_2d(coll1({0, 2}, {0, 3})); // similar to coll1 | take(3) // prints "0, 8, 15" + print_2d(coll1); // prints "0, 8, 15, 47, 11, 42" - auto&& coll1_take3 = coll1({0, 2}, {0, 3}); - print_2d(coll1_take3); // prints "0, 8, 15" -} + print_2d(coll1({0, 2}, {0, 3})); // similar to coll1 | take(3) // prints "0, 8, 15" -template -void fill_2d_99(Array1D&& coll) { - // for(auto const& row : coll) { // does not work because it would make it const - std::for_each(coll.begin(), coll.end(), [](auto&& row) { - std::fill(row.begin(), row.end(), 99); - }); - // std::transform(coll.begin(), coll.end(), coll.begin(), [](auto&& row) { - // std::fill(row.begin(), row.end(), 99); - // return std::forward(row); - // }); -} + auto&& coll1_take3 = coll1({0, 2}, {0, 3}); + print_2d(coll1_take3); // prints "0, 8, 15" + } + + // BOOST_AUTO_TEST_CASE(mutating_views_2d) + { + multi::array coll1 = { + {0, 8, 15, 47, 11, 42}, + {0, 8, 15, 47, 11, 42}, + }; + + fill_2d_99(coll1); + fill_2d_99(coll1({0, 2}, {0, 3})); + + auto&& coll1_take3 = coll1({0, 2}, {0, 3}); + fill_2d_99(coll1_take3); + + auto const& coll2 = coll1; + // fill_99( coll2 ); // doesn't compile because coll2 is const ("assignment of read-only" inside fill_99) + // fill_99( coll2({0, 3}) ); // similar to coll2 | take(3) doesn't compile ("assignment of read-only") + + auto const& coll1_take3_const = coll1({0, 2}, {0, 3}); + // fill_99( coll1_take3_const ); // doesn't compile because coll1_take3_const is const ("assignment of read-only") + + (void)coll2, (void)coll1_take3_const, (void)coll1_take3; + } -BOOST_AUTO_TEST_CASE(mutating_views_2d) { - multi::array coll1 = { - {0, 8, 15, 47, 11, 42}, - {0, 8, 15, 47, 11, 42} - }; + { + multi::array arr1d = {1, 2, 3}; - fill_2d_99(coll1); - fill_2d_99(coll1({0, 2}, {0, 3})); + // multi::array::const_iterator cfirst = arr1d.cbegin(); + // *cfirst.base() = 5; + // *cfirst = 5; // correctly fails to compile + // cfirst[0] = 5; // correctly fails to compile - auto&& coll1_take3 = coll1({0, 2}, {0, 3}); - fill_2d_99(coll1_take3); + BOOST_TEST( arr1d[0] == 1 ); + } + { + multi::array const arr1d = {1, 2, 3}; - auto const& coll2 = coll1; - // fill_99( coll2 ); // doesn't compile because coll2 is const ("assignment of read-only" inside fill_99) - // fill_99( coll2({0, 3}) ); // similar to coll2 | take(3) doesn't compile ("assignment of read-only") + // multi::array::iterator cfirst = arr1d.begin(); // correctly fails to compile - auto const& coll1_take3_const = coll1({0, 2}, {0, 3}); - // fill_99( coll1_take3_const ); // doesn't compile because coll1_take3_const is const ("assignment of read-only") + BOOST_TEST( arr1d[0] == 1 ); + } - (void)coll2, (void)coll1_take3_const, (void)coll1_take3; + return boost::report_errors(); } diff --git a/test/one_based.cpp b/test/one_based.cpp index c800d4a5d..00ef4f62f 100644 --- a/test/one_based.cpp +++ b/test/one_based.cpp @@ -1,110 +1,204 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi one-based" // title NOLINT(cppcoreguidelines-macro-usage) -#include +#if defined(__GNUC__) && !defined(__clang__) // for gcc 14 +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif -#include +#include -namespace multi = boost::multi; +#include -BOOST_AUTO_TEST_CASE(one_based_1D) { - multi::array const Ac({{0, 10}}, 0.0); - BOOST_REQUIRE( Ac.size() == 10 ); +#include // for equal +#include // for array +#include // for size, begin, end -// multi::array Af({{1, 1 + 10}}, 0.); -// Af[1] = 1.; -// Af[2] = 2.; -// Af[3] = 3.; +namespace multi = boost::multi; -// BOOST_REQUIRE( Af[1] = 1. ); -// BOOST_REQUIRE( *Af.data_elements() == 1. ); -// BOOST_REQUIRE( size(Af) == 10 ); -// BOOST_REQUIRE( extension(Af).start() == 1 ); -// BOOST_REQUIRE( extension(Af).finish() == 11 ); +// NOLINTNEXTLINE(readability-function-cognitive-complexity,bugprone-exception-escape) +auto main() -> int { + // one_based_1D + { + // clang-format off + multi::array const Ac({{0, 10}}, 0.0); + // clang-format on + + BOOST_TEST( Ac.size() == 10 ); + + // multi::array Af({{1, 1 + 10}}, 0.); + // Af[1] = 1.; + // Af[2] = 2.; + // Af[3] = 3.; + + // BOOST_TEST( Af[1] = 1. ); + // BOOST_TEST( *Af.data_elements() == 1. ); + // BOOST_TEST( size(Af) == 10 ); + // BOOST_TEST( extension(Af).start() == 1 ); + // BOOST_TEST( extension(Af).finish() == 11 ); + + // auto Af1 = multi::array(multi::extensions_t<1>{multi::iextension{10}}, 0.).reindex(1); + + // BOOST_TEST( size(Af1) == 10 ); + // BOOST_TEST( Af1[10] == 0. ); + + // multi::array B({{0, 10}}, 0.); + // B[0] = 1.; + // B[1] = 2.; + // B[2] = 3.; + + // BOOST_TEST( size(B) == 10 ); + // BOOST_TEST( B != Af ); + // BOOST_TEST( std::equal(begin(Af), end(Af), begin(B), end(B) ) ); + + // BOOST_TEST( Af.reindexed(0) == B ); + } + + // BOOST_AUTO_TEST_CASE(reindex_from_0_to_indexed_from_1) + { + multi::array A1({ + {1, 1 + 10}, + {1, 1 + 20} + }, + 0); + + BOOST_TEST( A1.extension().front() == 1); + BOOST_TEST( A1.extension().back() == 10); + + A1[1][1] = 10; + A1[2][2] = 20; + A1[3][3] = 30; + A1[10][20] = 990; + + auto const&& A0 = A1.reindexed(0, 0); + + BOOST_TEST( A0.extension().front() == 0); + BOOST_TEST( A0.extension().back() == 9); + + BOOST_TEST( A0[0][0] == 10 ); + BOOST_TEST( A0[1][1] == 20 ); + BOOST_TEST( A0[2][2] == 30 ); + BOOST_TEST( A0[9][19] == 990); + } + + // BOOST_AUTO_TEST_CASE(one_based_2D) + { + multi::array const Ac({ + {0, 10}, + {0, 20} + }, + 0); + BOOST_TEST( Ac.size() == 10 ); + + multi::array Af({ + {1, 1 + 10}, + {1, 1 + 20} + }, + 0); + + Af[1][1] = 10; + Af[2][2] = 20; + Af[3][3] = 30; + Af[10][20] = 990; + + BOOST_TEST( Af[1][1] == 10 ); + BOOST_TEST( Af[10][20] == 990 ); // cppcheck-suppress knownConditionTrueFalse ; for test + BOOST_TEST( *Af.elements().begin() == 10 ); + BOOST_TEST( Af.elements()[Af.num_elements()-1] == 990 ); + BOOST_TEST( size(Af) == 10 ); + + BOOST_TEST( extension(Af).first() == 1 ); + BOOST_TEST( extension(Af).last() == 11 ); + + multi::array BB({ + {0, 10}, + {0, 20} + }, + 0); + BB[0][0] = 10; + BB[1][1] = 20; + BB[2][2] = 30; + BB[9][19] = 990; + + BOOST_TEST( BB.size() == 10 ); + BOOST_TEST( BB != Af ); + BOOST_TEST( Af.reindexed(0, 0) == BB ); + + BOOST_TEST( Af.reindexed(0, 0)[0][0] == BB[0][0] ); + + BOOST_TEST( std::equal(begin(Af.reindexed(0, 0)), end(Af.reindexed(0, 0)), BB.begin(), BB.end()) ); + // BOOST_TEST( std::equal(begin(Af), end(Af), begin(B.reindexed(1, 1)), end(B.reindexed(1, 1)) ) ); + // BOOST_TEST( std::equal(begin(Af), end(Af), begin(B.reindexed(0, 1)), end(B.reindexed(0, 1)) ) ); + + // BOOST_TEST( Af.reindexed(0, 0) == B ); + + // B = Af; // TODO(correaa) implement assignment for 1-based arrays + // BOOST_TEST( B[1][1] = 1. ); + // BOOST_TEST( B[10][20] == 99.0 ); + // BOOST_TEST( B == Af ); + } + + // BOOST_AUTO_TEST_CASE(one_base_2D_ref) + { + // clang-format off + std::array, 3> arr = {{ + {{ 10, 20, 30, 40, 50 }}, + {{ 60, 70, 80, 90, 100 }}, + {{ 110, 120, 130, 140, 150 }}, + }}; + // clang-format on -// auto Af1 = multi::array(multi::extensions_t<1>{multi::iextension{10}}, 0.).reindex(1); + BOOST_TEST( arr[0][0] == 10 ); -// BOOST_REQUIRE( size(Af1) == 10 ); -// BOOST_REQUIRE( Af1[10] == 0. ); + multi::array_ref const& Ar = *multi::array_ptr(arr[0].data(), {3, 5}); + BOOST_TEST( &Ar[1][3] == &arr[1][3] ); -// multi::array B({{0, 10}}, 0.); -// B[0] = 1.; -// B[1] = 2.; -// B[2] = 3.; + multi::array_ref const& Ar2 = *multi::array_ptr( + arr[0].data(), + { + {1, 1 + 3}, + {1, 1 + 5}, + } + ); -// BOOST_REQUIRE( size(B) == 10 ); -// BOOST_REQUIRE( B != Af ); -// BOOST_REQUIRE( std::equal(begin(Af), end(Af), begin(B), end(B) ) ); + BOOST_TEST( sizes(Ar) == sizes(Ar2) ); + BOOST_TEST( &Ar2[1][1] == arr[0].data() ); + BOOST_TEST( &Ar2[2][4] == &arr[1][3] ); -// BOOST_REQUIRE( Af.reindexed(0) == B ); -} + BOOST_TEST( Ar2.extensions() != Ar.extensions() ); + BOOST_TEST( !(Ar2 == Ar) ); + BOOST_TEST( Ar2 != Ar ); + } -BOOST_AUTO_TEST_CASE(one_based_2D) { - multi::array const Ac({{0, 10}, {0, 20}}, 0.0); - BOOST_REQUIRE( Ac.size() == 10 ); - -// multi::array Af({{1, 1 + 10}, {1, 1 + 20}}, 0.); -// Af[1][1] = 1.; -// Af[2][2] = 2.; -// Af[3][3] = 3.; -// Af[10][20] = 99.; - -// BOOST_REQUIRE( Af[1][1] = 1. ); -// BOOST_REQUIRE( Af[10][20] == 99. ); -// BOOST_REQUIRE( *Af.data_elements() == 1. ); -// BOOST_REQUIRE( Af.data_elements()[Af.num_elements()-1] == 99. ); -// BOOST_REQUIRE( size(Af) == 10 ); -// BOOST_REQUIRE( extension(Af).start() == 1 ); -// BOOST_REQUIRE( extension(Af).finish() == 11 ); - -// auto Af1 = multi::array({10, 10}, 0.).reindex(1, 1); - -// BOOST_REQUIRE( size(Af1) == 10 ); -// BOOST_REQUIRE( Af1[10][10] == 0. ); - -// multi::array B({{0, 10}, {0, 20}}, 0.); -// B[0][0] = 1.; -// B[1][1] = 2.; -// B[2][2] = 3.; -// B[9][19] = 99.; - -// BOOST_REQUIRE( size(B) == 10 ); -// BOOST_REQUIRE( B != Af ); -// BOOST_REQUIRE( std::equal(begin(Af.reindexed(0, 0)), end(Af.reindexed(0, 0)), begin(B), end(B)) ); -// BOOST_REQUIRE( std::equal(begin(Af), end(Af), begin(B.reindexed(1, 1)), end(B.reindexed(1, 1)) ) ); -// BOOST_REQUIRE( std::equal(begin(Af), end(Af), begin(B.reindexed(0, 1)), end(B.reindexed(0, 1)) ) ); - -// BOOST_REQUIRE( Af.reindexed(0, 0) == B ); - -// B = Af; // TODO(correaa) implement assignment for 1-based arrays -// BOOST_REQUIRE( B[1][1] = 1. ); -// BOOST_REQUIRE( B[10][20] == 99. ); -// BOOST_REQUIRE( B == Af ); -} + { + multi::array AA = { + {'a', 'b'}, + {'c', 'd'}, + }; -BOOST_AUTO_TEST_CASE(one_base_2D_ref) { - std::array, 3> arr = {{ - {{ 1.0, 2.0, 3.0, 4.0, 5.0}}, - {{ 6.0, 7.0, 8.0, 9.0, 10.0}}, - {{11.0, 12.0, 13.0, 14.0, 15.0}}, - }}; + BOOST_TEST(AA[0][0] == 'a' ); + BOOST_TEST(AA[1][1] == 'd' ); + + auto&& Aone = AA.reindexed(1); - BOOST_REQUIRE( arr[0][0] == 1.0 ); + BOOST_TEST(Aone[1][0] == 'a' ); + BOOST_TEST(Aone[2][1] == 'd' ); + } + { + multi::array AA = { + {'a', 'b'}, + {'c', 'd'}, + }; -// multi::array_ref const& Ar = *multi::array_ptr(&arr[0][0], {3, 5}); -// BOOST_REQUIRE( &Ar[1][3] == &arr[1][3] ); + BOOST_TEST(AA[0][0] == 'a' ); + BOOST_TEST(AA[1][1] == 'd' ); -// multi::array_ref const& Ar2 = *multi::array_ptr(&arr[0][0], {{1, 1+3}, {1, 1+5}}); -// BOOST_REQUIRE( sizes(Ar) == sizes(Ar2) ); -// BOOST_REQUIRE( &Ar2[1][1] == &arr[0][0] ); -// BOOST_REQUIRE( &Ar2[2][4] == &arr[1][3] ); + auto&& Aone = AA.reindexed(1, 1); -// BOOST_REQUIRE( Ar2.extensions() != Ar.extensions() ); -// BOOST_REQUIRE( not(Ar2 == Ar) ); -// BOOST_REQUIRE( Ar2 != Ar ); -// BOOST_REQUIRE( extensions(Ar2.reindexed(0, 0)) == extensions(Ar) ); -// BOOST_REQUIRE( Ar2.reindexed(0, 0) == Ar ); + BOOST_TEST(Aone[1][1] == 'a' ); + BOOST_TEST(Aone[2][2] == 'd' ); + } -// static_assert( not std::is_assignable{}, "!" ); + return boost::report_errors(); } diff --git a/test/overload.cpp b/test/overload.cpp index 0c16a5eae..ef14f4fc5 100644 --- a/test/overload.cpp +++ b/test/overload.cpp @@ -1,25 +1,33 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi overload resolution" // title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array -#include +#include -#include +#include // for complex +#include // for operator==, string namespace multi = boost::multi; -inline auto what_is(multi::array const& /*arr*/) { return std::string{"real"}; } // std::string NOLINT(fuchsia-default-arguments-calls) -inline auto what_is(multi::array, 2> const& /*arr*/) { return std::string{"complex"}; } // std::string NOLINT(fuchsia-default-arguments-calls) +namespace { +inline auto what_is(multi::array const& /*arr*/) { return std::string{"real"}; } // NOLINT(fuchsia-default-arguments-calls) +inline auto what_is(multi::array, 2> const& /*arr*/) { return std::string{"complex"}; } // NOLINT(fuchsia-default-arguments-calls) +} // end unnamed namespace -BOOST_AUTO_TEST_CASE(multi_array_overload) { - multi::array const real_A({10, 20}); - multi::array, 2> const cplx_A({10, 20}); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_array_overload) + { + multi::array const real_A({10, 20}); + multi::array, 2> const cplx_A({10, 20}); - std::string const real_str = what_is(real_A); - std::string const complex_str = what_is(cplx_A); + std::string const real_str = what_is(real_A); + std::string const complex_str = what_is(cplx_A); - BOOST_REQUIRE( real_str == "real" ); - BOOST_REQUIRE( complex_str == "complex" ); + BOOST_TEST( real_str == "real" ); + BOOST_TEST( complex_str == "complex" ); + } + return boost::report_errors(); } diff --git a/test/partitioned.cpp b/test/partitioned.cpp index b1f49928d..ba5f607d0 100644 --- a/test/partitioned.cpp +++ b/test/partitioned.cpp @@ -1,231 +1,843 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array, apply, subarray, operator== + +#include + +#include // for is_sorted +#include // for array +#include // for ptrdiff_t +#include // for size +#include // for operator""s, string, string_lite... +#include // for apply // IWYU pragma: keep +#include // for declval, decay_t, decay, decay<>... +#include // for move +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4625) // copy constructor was implicitly defined as deleted +#pragma warning(disable : 4626) // assignment operator was implicitly defined as deleted +#pragma warning(disable : 5026) // move constructor was implicitly defined as deleted +#endif -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi partitioned operation" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include +namespace multi = boost::multi; -#include +template class propagate_const; -namespace multi = boost::multi; +template class propagate_const { + T& r_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) -BOOST_AUTO_TEST_CASE(array_partitioned_1d) { - multi::array A1 = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}; + public: + explicit propagate_const(T& other) : r_{other} {} + propagate_const(propagate_const const&) = delete; + propagate_const(propagate_const&&) = delete; - auto&& A2_ref = A1.partitioned(2); + auto operator=(propagate_const const&) -> propagate_const& = default; + auto operator=(propagate_const&&) noexcept -> propagate_const& = default; - static_assert(std::decay::type::rank{} == decltype(A1)::rank{} + 1); - static_assert(std::decay_t::rank_v == decltype(A1)::rank_v + 1); + auto operator=(T const& other) -> propagate_const& { + r_ = other; + return *this; + } - BOOST_REQUIRE( size(A2_ref ) == 2 ); - BOOST_REQUIRE( size(A2_ref[0]) == 3 ); + ~propagate_const() noexcept = default; - BOOST_REQUIRE( &A2_ref[1][0] == &A1[3] ); + explicit operator T const&() const noexcept { return r_; } + explicit operator T&() noexcept { return r_; } +}; - BOOST_REQUIRE(( A2_ref == multi::array{ {0, 1, 2}, {3, 4, 5} } )); -} +template class propagate_const { + T const& r_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) -BOOST_AUTO_TEST_CASE(array_partitioned_2d) { - multi::array A2 = { - { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, - { 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}, + public: + explicit propagate_const(T const& other) : r_{other} {} + auto operator=(T const& other) -> propagate_const& = delete; + explicit operator T const&() const noexcept { return r_; } +}; - {12.0, 13.0, 14.0, 15.0, 16.0, 17.0}, - {18.0, 19.0, 20.0, 21.0, 22.0, 23.0}, - }; - auto&& A3_ref = A2.partitioned(2); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(halved_1d) + { + multi::array A1 = {0, 10, 20, 30, 40, 50}; - static_assert(std::decay_t::rank{} == decltype(A2)::rank{} + 1); - static_assert(std::decay_t::rank_v == decltype(A2)::rank_v + 1); + auto&& A2_ref = A1.halved(); - BOOST_REQUIRE( num_elements(A3_ref) == num_elements(A2) ); - BOOST_REQUIRE( size(A3_ref)==2 ); - BOOST_REQUIRE( size(A3_ref[0])==2 ); - BOOST_REQUIRE( size(A3_ref[0][0])==6 ); - BOOST_REQUIRE( &A3_ref[1][1][0] == &A2[3][0] ); -} + static_assert(std::decay::type::rank{} == decltype(A1)::rank{} + 1); + static_assert(std::decay_t::rank_v == decltype(A1)::rank_v + 1); -BOOST_AUTO_TEST_CASE(array_partitioned) { - using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + BOOST_TEST( size(A2_ref ) == 2 ); + BOOST_TEST( size(A2_ref[0]) == 3 ); - multi::array A2 = { - {"s0P0"s, "s1P0"s}, - {"s0P1"s, "s1P1"s}, - {"s0P2"s, "s1P2"s}, - {"s0P3"s, "s1P3"s}, - {"s0P4"s, "s1P4"s}, - {"s0P5"s, "s1P5"s}, - }; + BOOST_TEST( &A2_ref[1][0] == &A1[3] ); - BOOST_REQUIRE( size(A2) == 6 ); + BOOST_TEST(( A2_ref == multi::array{ {0, 10, 20}, {30, 40, 50} } )); + } - BOOST_REQUIRE( std::get<0>(A2.sizes()) == 6 ); - BOOST_REQUIRE( std::get<1>(A2.sizes()) == 2 ); + // BOOST_AUTO_TEST_CASE(halved_2d) + { + multi::array const A2 = { + { 00, 10, 20, 30, 40, 50}, + { 60, 70, 80, 90, 100, 110}, + + {120, 130, 140, 150, 160, 170}, + {180, 190, 200, 210, 220, 230}, + }; + + BOOST_TEST(( + A2.halved() == multi::array{ + { + { 00, 10, 20, 30, 40, 50}, + { 60, 70, 80, 90, 100, 110}, + }, + { + {120, 130, 140, 150, 160, 170}, + {180, 190, 200, 210, 220, 230}, + }, + } + )); + } - BOOST_REQUIRE(( A2.sizes() == decltype(A2.sizes()){6, 2} )); + // BOOST_AUTO_TEST_CASE(halved_ref_2d) + { + std::vector buff({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, {}); + auto const& arr = multi::array_ref({4, 4}, buff.data()); + + BOOST_TEST(( arr == multi::array{ + { 1, 2, 3, 4}, + { 5, 6, 7, 8}, + { 9, 10, 11, 12}, + {13, 14, 15, 16} + })); + + BOOST_TEST(( arr.halved().extensions() == multi::extensions_t{2, 2, 4} )); + BOOST_TEST(( arr.halved() == multi::array{ + { + { 1, 2, 3, 4}, + { 5, 6, 7, 8} + }, + { + { 9, 10, 11, 12}, + {13, 14, 15, 16} + } + })); + + BOOST_TEST(( arr.halved().flatted() == multi::array{ + { 1, 2, 3, 4}, + { 5, 6, 7, 8}, + { 9, 10, 11, 12}, + {13, 14, 15, 16} + })); + + BOOST_TEST(( arr.halved().transposed() == multi::array{ + { + { 1, 2, 3, 4}, + { 9, 10, 11, 12}, + }, + { + { 5, 6, 7, 8}, + {13, 14, 15, 16} + } + })); + + BOOST_TEST(( arr.halved().rotated() == multi::array{ + { + {1, 9}, + {2, 10}, + {3, 11}, + {4, 12} + }, + { + { 5, 13}, + { 6, 14}, + { 7, 15}, + { 8, 16} + } + })); + + BOOST_TEST(( arr.halved().rotated().rotated() == multi::array{ + { + {1, 5}, + {9, 13} + }, + { + {2, 6}, + {10, 14} + }, + { + {3, 7}, + {11, 15} + }, + { + {4, 8}, + {12, 16} + } + })); + + BOOST_TEST(( arr.halved().rotated().rotated().halved() == multi::array{ + { + { + { 1, 5}, + { 9, 13} + }, + { + { 2, 6}, + {10, 14} + } + }, + { + { + { 3, 7}, + {11, 15} + }, + { + { 4, 8}, + {12, 16} + } + } + })); + + BOOST_TEST(( arr.halved().rotated().rotated().halved().unrotated() == multi::array{ + { + { + { 1, 9}, + { 2, 10} + }, + { + { 3, 11}, + { 4, 12} + } + }, + { + { + { 5, 13}, + { 6, 14} + }, + { + { 7, 15}, + { 8, 16} + } + } + })); + + BOOST_TEST( arr.halved().rotated().rotated().halved().unrotated().unrotated()[0][0][0][0] == 1 ); + BOOST_TEST( arr.halved().rotated().rotated().halved().unrotated().unrotated()[0][0][0][1] == 2 ); + BOOST_TEST( arr.halved().rotated().rotated().halved().unrotated().unrotated()[0][0][1][0] == 3 ); + BOOST_TEST( arr.halved().rotated().rotated().halved().unrotated().unrotated()[0][0][1][1] == 4 ); + BOOST_TEST( arr.halved().rotated().rotated().halved().unrotated().unrotated()[0][1][0][0] == 5 ); + BOOST_TEST( arr.halved().rotated().rotated().halved().unrotated().unrotated()[0][1][0][1] == 6 ); + + BOOST_TEST(( arr.halved().rotated().rotated().halved().unrotated().unrotated() == multi::array{ + { + { + { 1, 2}, + { 3, 4} + }, + { + { 5, 6}, + { 7, 8} + } + }, + { + { + { 9, 10}, + {11, 12} + }, + { + {13, 14}, + {15, 16} + } + } + })); + + BOOST_TEST(( arr.halved().rotated().rotated().halved().unrotated().unrotated().unrotated() == multi::array{ + { + { + { 1, 3}, + { 5, 7} + }, + { + { 9, 11}, + {13, 15} + } + }, + { + { + { 2, 4}, + { 6, 8} + }, + { + {10, 12}, + {14, 16} + } + } + })); + + BOOST_TEST(( arr.halved().rotated().rotated().halved().unrotated().unrotated().unrotated().unrotated() == multi::array{ + { + { + { 1, 5}, + { 9, 13} + }, + { + { 2, 6}, + {10, 14} + } + }, + { + { + { 3, 7}, + {11, 15} + }, + { + { 4, 8}, + {12, 16} + } + } + })); + + // BOOST_TEST(( arr.halved().rotated().rotated().halved().unrotated().unrotated().unrotated() == multi::array{ + // { + // {{ 1, 2}, + // { 9, 10}}, + // {{ 3, 4}, + // {11, 12}}, + // }, + // { + // {{ 5, 6}, + // {13, 14}}, + // {{7, 8}, + // {15, 16}} + // } + // })); + + // BOOST_TEST(( arr.halved().rotated().rotated().halved().unr == multi::array{ + // { + // { 1, 2, 3, 4}, + // { 5, 6, 7, 8} + // }, + // { + // { 9, 10, 11, 12}, + // {13, 14, 15, 16} + // } + // })); + } - BOOST_REQUIRE( std::get<0>(sizes(A2)) == 6 ); - BOOST_REQUIRE( std::get<1>(sizes(A2)) == 2 ); + // BOOST_AUTO_TEST_CASE(array_partitioned_1d) + { + multi::array A1 = {0, 10, 20, 30, 40, 50}; - BOOST_REQUIRE( size(A2.partitioned(3)) == 3 ); + auto&& A2_ref = A1.partitioned(2); - static_assert(decltype(A2.partitioned(3))::rank{} == 3); - static_assert(decltype(A2.partitioned(3))::rank::value == 3); - static_assert(decltype(A2.partitioned(3))::rank_v == 3); + auto [is, js] = A2_ref.extensions(); + BOOST_TEST( is.size() == 2 ); + BOOST_TEST( js.size() == 3 ); - BOOST_REQUIRE(( sizes(A2.partitioned(3)) == decltype(sizes(A2.partitioned(3))){3, 2, 2} )); + static_assert(std::decay::type::rank{} == decltype(A1)::rank{} + 1); + static_assert(std::decay_t::rank_v == decltype(A1)::rank_v + 1); - BOOST_REQUIRE( std::get<0>(sizes(A2.partitioned(3))) == 3 ); - BOOST_REQUIRE( std::get<1>(sizes(A2.partitioned(3))) == 2 ); - BOOST_REQUIRE( std::get<2>(sizes(A2.partitioned(3))) == 2 ); + BOOST_TEST( size(A2_ref ) == 2 ); + BOOST_TEST( size(A2_ref[0]) == 3 ); - BOOST_REQUIRE( size(A2.partitioned(1)) == 1 ); + BOOST_TEST( &A2_ref[1][0] == &A1[3] ); - static_assert(decltype(A2.partitioned(1))::rank{} == 3); - static_assert(decltype(A2.partitioned(1))::rank::value == 3); - static_assert(decltype(A2.partitioned(1))::rank_v == 3); + BOOST_TEST(( A2_ref == multi::array{ {0, 10, 20}, {30, 40, 50} } )); + } - BOOST_REQUIRE( &A2.partitioned(1).rotated()[3][1][0] == &A2[3][1] ); -} + // BOOST_AUTO_TEST_CASE(array_partitioned_2d) + { + multi::array A2 = { + { 00, 10, 20, 30, 40, 50}, + { 60, 70, 80, 90, 100, 110}, + + {120, 130, 140, 150, 160, 170}, + {180, 190, 200, 210, 220, 230}, + }; + + BOOST_TEST(( + A2.partitioned(2) == multi::array{ + { + { 00, 10, 20, 30, 40, 50}, + { 60, 70, 80, 90, 100, 110}, + }, + { + {120, 130, 140, 150, 160, 170}, + {180, 190, 200, 210, 220, 230}, + }, + } + )); + + auto&& A3_ref = A2.partitioned(2); + + static_assert(std::decay_t::rank{} == decltype(A2)::rank{} + 1); + static_assert(std::decay_t::rank_v == decltype(A2)::rank_v + 1); + + BOOST_TEST( num_elements(A3_ref) == num_elements(A2) ); + BOOST_TEST( size(A3_ref) == 2 ); + BOOST_TEST( size(A3_ref[0]) == 2 ); + BOOST_TEST( size(A3_ref[0][0]) == 6 ); + BOOST_TEST( &A3_ref[1][1][0] == &A2[3][0] ); + + A3_ref[0][0][0] = 99; + BOOST_TEST( A3_ref[0][0][0] == 99 ); // cppcheck-suppress knownConditionTrueFalse ; + } -template class propagate_const; + // BOOST_AUTO_TEST_CASE(array_partitioned) + { + using namespace std::string_literals; // NOLINT(build/namespaces) for ""s + using std::get; // workaround: function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] -template class propagate_const { - T& r_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + // NOLINTBEGIN(misc-include-cleaner) bug in clang-tidy 18 + multi::array A2 = { + {"s0P0"s, "s1P0"s}, + {"s0P1"s, "s1P1"s}, + {"s0P2"s, "s1P2"s}, + {"s0P3"s, "s1P3"s}, + {"s0P4"s, "s1P4"s}, + {"s0P5"s, "s1P5"s}, + }; + // NOLINTEND(misc-include-cleaner) - public: - explicit propagate_const(T& other) : r_{other} {} - propagate_const(propagate_const const&) = delete; - propagate_const(propagate_const&&) = delete; + BOOST_TEST( size(A2) == 6 ); - auto operator=(propagate_const const&) -> propagate_const& = default; - auto operator=(propagate_const&&) noexcept -> propagate_const& = default; + BOOST_TEST( get<0>(A2.sizes()) == 6 ); + BOOST_TEST( get<1>(A2.sizes()) == 2 ); - auto operator=(T const& other) -> propagate_const& { - r_ = other; - return *this; + BOOST_TEST(( A2.sizes() == decltype(A2.sizes()){6, 2} )); + + BOOST_TEST( get<0>(A2.sizes()) == 6 ); + BOOST_TEST( get<1>(A2.sizes()) == 2 ); + + BOOST_TEST( size(A2.partitioned(3)) == 3 ); + + static_assert(decltype(A2.partitioned(3))::rank{} == 3); + static_assert(decltype(A2.partitioned(3))::rank::value == 3); + static_assert(decltype(A2.partitioned(3))::rank_v == 3); + + BOOST_TEST(( sizes(A2.partitioned(3)) == decltype(sizes(A2.partitioned(3))){3, 2, 2} )); + + BOOST_TEST( get<0>(sizes(A2.partitioned(3))) == 3 ); + BOOST_TEST( get<1>(sizes(A2.partitioned(3))) == 2 ); + BOOST_TEST( get<2>(sizes(A2.partitioned(3))) == 2 ); + + BOOST_TEST( size(A2.partitioned(1)) == 1 ); + + static_assert(decltype(A2.partitioned(1))::rank{} == 3); + static_assert(decltype(A2.partitioned(1))::rank::value == 3); + static_assert(decltype(A2.partitioned(1))::rank_v == 3); + + BOOST_TEST( &A2.partitioned(1).rotated()[3][1][0] == &A2[3][1] ); } - ~propagate_const() noexcept = default; + // BOOST_AUTO_TEST_CASE(array_encoded_subarray) + { + using std::get; // workaround: function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] - explicit operator T const&() const noexcept { return r_; } - explicit operator T&() noexcept { return r_; } -}; + // arr[walker][encoded_property] // 7 walkers + multi::array arr = { + {990, 990, 1000, 001, 10, 11, 20, 21, 990}, + {990, 990, 100, 101, 110, 111, 120, 121, 990}, + {990, 990, 200, 201, 210, 211, 220, 221, 990}, + {990, 990, 300, 301, 310, 311, 320, 321, 990}, + {990, 990, 400, 401, 410, 411, 420, 421, 990}, + {990, 990, 500, 501, 510, 511, 520, 521, 990}, + {990, 990, 600, 601, 610, 611, 620, 621, 990}, + }; -template class propagate_const { - T const& r_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + // multi::iextension const encoded_3x2_range = {2, 8}; - public: - explicit propagate_const(T const& other) : r_{other} {} - auto operator=(T const& other) -> propagate_const& = delete; - explicit operator T const&() const noexcept { return r_; } -}; + auto&& arrRPU = arr.rotated().sliced(2, 8).partitioned(3).unrotated(); + + static_assert(decltype(+arrRPU)::rank::value == 3); + static_assert(decltype(+arrRPU)::rank{} == 3); + static_assert(decltype(+arrRPU)::rank_v == 3); + + BOOST_TEST( get<0>(arrRPU.sizes()) == 7 ); + BOOST_TEST( get<1>(arrRPU.sizes()) == 3 ); + BOOST_TEST( get<2>(arrRPU.sizes()) == 2 ); + + BOOST_TEST(( arrRPU.sizes() == decltype(arrRPU.sizes()){7, 3, 2} )); + BOOST_TEST(( sizes(arrRPU) == decltype(sizes(arrRPU)){7, 3, 2} )); + BOOST_TEST( arrRPU[4].num_elements() == 3*2L ); + + BOOST_TEST( &arrRPU[4][1][0] == &arr[4][4] ); + BOOST_TEST( arrRPU[4][1][0] == 410 ); + + BOOST_TEST(( + arrRPU[4] == multi::array{ + {400, 401}, + {410, 411}, + {420, 421}, + } + )); + + arrRPU[4][1][0] = 11110; + BOOST_TEST( arr[4][4] == 11110 ); + + class walker_ref { + using raw_source_reference = decltype(std::declval&>()[0]); + using internal_array_type = decltype(std::declval().sliced(2, 8).partitioned(3)); + + public: // NOLINT(whitespace/indent) bug in cpplint + propagate_const prop1; // NOLINT(misc-non-private-member-variables-in-classes) + propagate_const prop2; // NOLINT(misc-non-private-member-variables-in-classes) + internal_array_type slater_array; // NOLINT(misc-non-private-member-variables-in-classes) + propagate_const prop3; // NOLINT(misc-non-private-member-variables-in-classes) + + explicit walker_ref(raw_source_reference&& row) + : prop1{row[0]}, prop2{row[1]}, slater_array{row.sliced(2, 8).partitioned(3)}, prop3{std::move(row)[8]} {} + }; + + auto&& wr = walker_ref(arr[5]); + + wr.prop1 = 88; + + BOOST_TEST( wr.slater_array[2][1] == 521 ); + + wr.slater_array[2][1] = 99990; + BOOST_TEST( wr.slater_array[2][1] == 99990 ); + } + + // BOOST_AUTO_TEST_CASE(array_partitioned_add_to_last) + { + multi::array arr = { + { + {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, + {6.0, 7.0, 8.0, 9.0, 10.0, 11.0}, + {12.0, 13.0, 14.0, 15.0, 16.0, 17.0}, + {18.0, 19.0, 20.0, 21.0, 22.0, 23.0}, + }, + { + {0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, + {6.0, 7.0, 8.0, 9.0, 10.0, 11.0}, + {12.0, 13.0, 14.0, 15.0, 16.0, 17.0}, + {18.0, 19.0, 20.0, 21.0, 22.0, 23.0}, + }, + }; + + auto strides = std::apply([](auto... strds) { return std::array{{strds...}}; }, arr.layout().strides()); + + // NOLINTNEXTLINE(modernize-use-ranges) for C++20 + BOOST_TEST( std::is_sorted(strides.rbegin(), strides.rend()) && arr.num_elements() == arr.nelems() ); // contiguous c-ordering + + // #ifndef _MSC_VER // problem with MSVC 14.3 c++17 + auto&& A4 = arr.reinterpret_array_cast(1); + + BOOST_TEST(( arr.extensions() == decltype(arr.extensions()){2, 4, 6} )); + BOOST_TEST(( A4.extensions() == decltype(A4.extensions()){2, 4, 6, 1} )); + + // BOOST_TEST( A4.is_flattable() ); + // BOOST_TEST( A4.flatted().is_flattable() ); + + BOOST_TEST( &A4[1][2][3][0] == &arr[1][2][3] ); + // #endif + } + + // BOOST_AUTO_TEST_CASE(array_partitioned_vs_chunked_1D) + { + multi::array arr = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}; + BOOST_TEST( size(arr.partitioned(3)) == 3 ); + BOOST_TEST(( arr.partitioned(3)[1] == multi::array{4.0, 5.0, 6.0, 7.0} )); + BOOST_TEST( &arr.partitioned(3)[1][2] == &arr[6] ); + + BOOST_TEST( size(arr.chunked(3)) == 4 ); + BOOST_TEST(( arr.chunked(3)[1] == multi::array({3.0, 4.0, 5.0}) )); + BOOST_TEST( &arr.chunked(3)[1][2] == &arr[5] ); + } + + // BOOST_AUTO_TEST_CASE(array_partitioned_vs_chunked_2D) + { + multi::array arr({100, 53}); + BOOST_TEST( arr.partitioned(20).size() == 20 ); + BOOST_TEST( &arr.partitioned(20)[1][2] == &arr[7] ); + + BOOST_TEST( size(arr.chunked(5)) == 20 ); + BOOST_TEST( &arr.chunked(5)[1][2] == &arr[7] ); + } + + // BOOST_AUTO_TEST_CASE(chunked_subarrays) + { + using std::get; // workaround: function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + multi::array const arr = { + { 0, 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} + }; + + BOOST_TEST( arr.dimensionality == 2 ); + BOOST_TEST( arr.size() == 8 ); + BOOST_TEST( get<1>(arr.sizes()) == 6 ); + + BOOST_TEST( arr.chunked(2).dimensionality == 3 ); + BOOST_TEST( arr.chunked(2).size() == 4 ); + BOOST_TEST( get<1>(arr.chunked(2).sizes()) == 2 ); + BOOST_TEST( get<2>(arr.chunked(2).sizes()) == 6 ); + + auto const&& block_arr = arr.chunked(2).rotated().rotated().chunked(2).transposed().rotated().transposed(); + BOOST_TEST( block_arr.dimensionality == 4 ); + + BOOST_TEST( block_arr.size() == 4 ); + BOOST_TEST( get<1>(block_arr.sizes()) == 3 ); + BOOST_TEST( get<2>(block_arr.sizes()) == 2 ); + BOOST_TEST( get<3>(block_arr.sizes()) == 2 ); + + BOOST_TEST(( + block_arr[2][1] + == + multi::array{ + {26, 27}, + {32, 33} + } + )); + } + + // BOOST_AUTO_TEST_CASE(partitined_subarrays) + { + multi::array const arr = { + { 0, 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}, + }; + + BOOST_TEST( arr.dimensionality == 2 ); + BOOST_TEST( arr.partitioned(4).dimensionality == 3 ); + + BOOST_TEST( arr.partitioned(4).size() == 4 ); + + using std::get; // workaround: function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + BOOST_TEST( get<1>(arr.partitioned(4).sizes()) == 2 ); + BOOST_TEST( get<2>(arr.partitioned(4).sizes()) == 6 ); + + auto const&& block_arr = arr.partitioned(4).rotated().rotated().partitioned(3).transposed().rotated().transposed(); + BOOST_TEST( block_arr.dimensionality == 4 ); + + BOOST_TEST( block_arr.size() == 4 ); + BOOST_TEST( get<1>(block_arr.sizes()) == 3 ); + BOOST_TEST( get<2>(block_arr.sizes()) == 2 ); + BOOST_TEST( get<3>(block_arr.sizes()) == 2 ); + + BOOST_TEST(( + block_arr[2][1] + == + multi::array{ + {26, 27}, + {32, 33} + } + )); + } + + // tiled 1D with 3 elements per tile + { + multi::array const arr = {0, 1, 2, /**/ 3, 4, 5, /**/ 6, 7}; + + BOOST_TEST( arr.size() == 8 ); + + BOOST_TEST(( arr.tiled(3).quotient[0].size() == 3 )); + BOOST_TEST(( arr.tiled(3).quotient[1].size() == 3 )); + + BOOST_TEST(( arr.tiled(3).remainder.size() == 2 )); + + auto [tiles, border] = arr.tiled(3); + BOOST_TEST( tiles.size() == 2 ); + BOOST_TEST(( tiles[0].size() == 3 )); + BOOST_TEST(( tiles[1].size() == 3 )); + + BOOST_TEST(( tiles[0] == multi::array{0, 1, 2} )); + BOOST_TEST(( tiles[1] == multi::array{3, 4, 5} )); + + BOOST_TEST( border.size() == 2 ); + BOOST_TEST(( border == multi::array{6, 7} )); + } + + // tiled 1D with 5 elements per tile + { + multi::array const arr = {0, 1, 2, 3, 4, /**/ 5, 6, 7, 8, 9, /**/ 10, 11, 12, 13}; + + BOOST_TEST( arr.size() == 14 ); + + BOOST_TEST(( arr.tiled(5).quotient[0].size() == 5 )); + BOOST_TEST(( arr.tiled(5).quotient[1].size() == 5 )); + + BOOST_TEST(( arr.tiled(5).remainder.size() == 4 )); + + auto [tiles, border] = arr.tiled(5); + BOOST_TEST( tiles.size() == 2 ); + BOOST_TEST(( tiles[0].size() == 5 )); + BOOST_TEST(( tiles[1].size() == 5 )); + + BOOST_TEST(( tiles[0] == multi::array{0, 1, 2, 3, 4} )); + BOOST_TEST(( tiles[1] == multi::array{5, 6, 7, 8, 9} )); + + BOOST_TEST( border.size() == 4 ); + BOOST_TEST(( border == multi::array{10, 11, 12, 13} )); + } + + // put subarray in std::array + { + multi::array arr = {1, 2, 3, /**/ 4, 5}; + BOOST_TEST( arr.size() == 5 ); + + // std::array::subarray, 2> arr2 = {{ arr({0, 3}), arr({3, 5}) }}; + // auto arr2 = std::array::subarray, 2>{{ arr({0, 3}), arr({3, 5}) }}; + // auto&& arr2 = std::array::subarray, 2>{{ arr({0, 3}), arr({3, 5}) }}; + + // CTAD fails: + // auto&& arr2 = std::array{{ arr({0, 3}), arr({3, 5}) }}; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-braces" +#endif + auto&& arr2 = std::array{arr({0, 3}), arr({3, 5})}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + // arr2 is not copyable, good + // auto arr3 = arr2; + [[maybe_unused]] auto&& arr3 = arr2; + + BOOST_TEST( arr2[0].size() == 3 ); + BOOST_TEST( arr2[1].size() == 2 ); + + BOOST_TEST( &arr2[0][1] == &arr[1] ); + BOOST_TEST( &arr2[1][1] == &arr[4] ); + + arr2[0][1] = 99; + BOOST_TEST( arr[1] == 99 ); + } + + // put subarrays in tuple + { + multi::array arr = {1, 2, 3, /**/ 4, 5}; + BOOST_TEST( arr.size() == 5 ); + + // asan complains in these cases: + // std::tuple::subarray&&, multi::array::subarray&&> tup(arr({0, 3}), arr({3, 5})); + // auto tup = std::tuple::subarray&&, multi::array::subarray&&>(arr({0, 3}), arr({3, 5})); + + // invalid syntax: + // std::tuple::subarray&&, multi::array::subarray&&>&& tup(arr({0, 3}), arr({3, 5})); + + // asan complains: + // auto&& tup = std::tuple::subarray&&, multi::array::subarray&&>(arr({0, 3}), arr({3, 5})); + + // asan complains: + // auto&& tup = std::forward_as_tuple(arr({0, 3}), arr({3, 5})); + // std::tuple::subarray, multi::array::subarray> tup(arr({0, 3}), arr({3, 5})); + // auto tup = std::tuple::subarray, multi::array::subarray>(arr({0, 3}), arr({3, 5})); + // auto tup = std::make_tuple(arr({0, 3}), arr({3, 5})); + // auto tup = std::tuple(arr({0, 3}), arr({3, 5})); + // auto&& tup = std::tuple(arr({0, 3}), arr({3, 5})); + + auto&& tup = std::tuple{arr({0, 3}), arr({3, 5})}; + + // tup is not copyable, good + // auto tup2 = tup; + [[maybe_unused]] auto&& tup2 = tup; + + using std::get; // for C++17 + BOOST_TEST( get<0>(tup).size() == 3 ); + BOOST_TEST( get<1>(tup).size() == 2 ); + + BOOST_TEST( &get<0>(tup)[1] == &arr[1] ); + BOOST_TEST( &get<1>(tup)[1] == &arr[4] ); + + get<0>(tup)[1] = 99; + BOOST_TEST( arr[1] == 99 ); + } + + // put 2D subarrays in 2D tuple + { + multi::array arr = { + {1, 2, 3, /**/ 4, 5}, + {6, 7, 8, /**/ 9, 10}, + /*******************/ + {1, 2, 3, /**/ 4, 5}, + {6, 7, 8, /**/ 9, 10}, + }; + + BOOST_TEST( arr.elements().size() == 20 ); + + auto&& tup = std::tuple{ + arr({0, 2}, {0, 3}), + arr({0, 2}, {3, 5}), + arr({2, 4}, {0, 3}), + arr({2, 4}, {3, 5}), + }; + + using std::get; // for C++17 + BOOST_TEST( &get<0>(tup)[1][1] == &arr[1][1] ); + BOOST_TEST( &get<2>(tup)[0][2] == &arr[2][2] ); + + auto&& tup2d = std::tuple{ + std::tuple{arr({0, 2}, {0, 3}), arr({0, 2}, {3, 5})}, + std::tuple{arr({2, 4}, {0, 3}), arr({2, 4}, {3, 5})}, + }; + + BOOST_TEST( &get<0>(get<0>(tup2d))[1][1] == &arr[1][1] ); + BOOST_TEST( &get<0>(get<1>(tup2d))[0][2] == &arr[2][2] ); + } + + // put 2D subarrays in 2D tuple + { + multi::array arr = { + {1, 2, 3, /**/ 4, 5}, + {6, 7, 8, /**/ 9, 10}, + /*******************/ + {1, 2, 3, /**/ 4, 5}, + {6, 7, 8, /**/ 9, 10}, + }; + + BOOST_TEST( arr.elements().size() == 20 ); + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-braces" +#endif + auto&& tup = std::array{ + arr({0, 2}, {0, 3}), + arr({0, 2}, {3, 5}), + arr({2, 4}, {0, 3}), + arr({2, 4}, {3, 5}), + }; + + using std::get; // for C++17 + BOOST_TEST( &get<0>(tup)[1][1] == &arr[1][1] ); + BOOST_TEST( &get<2>(tup)[0][2] == &arr[2][2] ); + +#if defined(__clang__) || (!defined(__GNUC__) || (__GNUC__ > 9)) // gcc 9 gets confused with CTAD + auto&& tup2d = std::array{ + std::array{arr({0, 2}, {0, 3}), arr({0, 2}, {3, 5})}, + std::array{arr({2, 4}, {0, 3}), arr({2, 4}, {3, 5})}, + }; + BOOST_TEST( &tup2d[0][0] [1][1] == &arr[1][1] ); + BOOST_TEST( &tup2d[1][0] [0][2] == &arr[2][2] ); +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } -BOOST_AUTO_TEST_CASE(array_encoded_subarray) { - // arr[walker][encoded_property] // 7 walkers - multi::array arr = { - {99., 99., 0.00, 0.01, 0.10, 0.11, 0.20, 0.21, 99.}, - {99., 99., 1.00, 1.01, 1.10, 1.11, 1.20, 1.21, 99.}, - {99., 99., 2.00, 2.01, 2.10, 2.11, 2.20, 2.21, 99.}, - {99., 99., 3.00, 3.01, 3.10, 3.11, 3.20, 3.21, 99.}, - {99., 99., 4.00, 4.01, 4.10, 4.11, 4.20, 4.21, 99.}, - {99., 99., 5.00, 5.01, 5.10, 5.11, 5.20, 5.21, 99.}, - {99., 99., 6.00, 6.01, 6.10, 6.11, 6.20, 6.21, 99.}, - }; - - multi::iextension const encoded_3x2_range = {2, 8}; - - auto&& arrRPU = arr.rotated()(encoded_3x2_range).partitioned(3).unrotated(); - - static_assert(decltype(+arrRPU)::rank::value == 3); - static_assert(decltype(+arrRPU)::rank{} == 3); - static_assert(decltype(+arrRPU)::rank_v == 3); - - BOOST_REQUIRE(( sizes(arrRPU) == decltype(sizes(arrRPU)){7, 3, 2} )); - BOOST_REQUIRE( arrRPU[4].num_elements() == 3*2L ); - - BOOST_REQUIRE( &arrRPU[4][1][0] == &arr[4][4] ); - BOOST_REQUIRE( arrRPU[4][1][0] == 4.10 ); - - BOOST_REQUIRE(( - arrRPU[4] == multi::array{ - {4.00, 4.01}, - {4.10, 4.11}, - {4.20, 4.21}, - } - )); - - arrRPU[4][1][0] = 1111.0; - BOOST_REQUIRE( arr[4][4] == 1111.0 ); - - class walker_ref { - using raw_source_reference = decltype(std::declval&>()[0]); - using internal_array_type = decltype(std::declval()({2, 8}).partitioned(3)); - - public: // NOLINT(whitespace/indent) bug in cpplint - propagate_const prop1; // NOLINT(misc-non-private-member-variables-in-classes) - propagate_const prop2; // NOLINT(misc-non-private-member-variables-in-classes) - internal_array_type slater_array; // NOLINT(misc-non-private-member-variables-in-classes) - propagate_const prop3; // NOLINT(misc-non-private-member-variables-in-classes) - - explicit walker_ref(raw_source_reference&& row) : prop1{row[0]}, prop2{row[1]}, slater_array{row({2, 8}).partitioned(3)}, prop3{row[8]} {} - }; - - auto&& wr = walker_ref(arr[5]); - - wr.prop1 = 88; - - BOOST_REQUIRE( wr.slater_array[2][1] == 5.21 ); - - wr.slater_array[2][1] = 9999.0; -} - -BOOST_AUTO_TEST_CASE(array_partitioned_add_to_last) { - multi::array arr = { - { - { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, - { 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}, - { 12.0, 13.0, 14.0, 15.0, 16.0, 17.0}, - { 18.0, 19.0, 20.0, 21.0, 22.0, 23.0}, - }, - { - { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, - { 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}, - { 12.0, 13.0, 14.0, 15.0, 16.0, 17.0}, - { 18.0, 19.0, 20.0, 21.0, 22.0, 23.0}, - } - }; - - auto strides = std::apply([](auto... strds) { return std::array{{strds...}}; }, arr.strides()); - - BOOST_REQUIRE( std::is_sorted(strides.rbegin(), strides.rend()) and arr.num_elements() == arr.nelems() ); // contiguous c-ordering - - auto&& A4 = arr.reinterpret_array_cast(1); - - BOOST_REQUIRE(( arr.extensions() == decltype(arr.extensions()){2, 4, 6} )); - BOOST_REQUIRE(( A4.extensions() == decltype(A4.extensions()){2, 4, 6, 1} )); - - BOOST_REQUIRE( A4.is_flattable() ); - BOOST_REQUIRE( A4.flatted().is_flattable() ); - - BOOST_REQUIRE( &A4[1][2][3][0] == &arr[1][2][3] ); -} - -BOOST_AUTO_TEST_CASE(array_partitioned_vs_chunked_1D) { - multi::array arr = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}; - BOOST_REQUIRE( size(arr.partitioned(3)) == 3 ); - BOOST_REQUIRE( arr.partitioned(3)[1] == decltype(+arr.partitioned(3)[1])({4.0, 5.0, 6.0, 7.0}) ); - BOOST_REQUIRE( &arr.partitioned(3)[1][2] == &arr[6] ); - - BOOST_REQUIRE( size(arr.chunked(3)) == 4 ); - BOOST_REQUIRE( arr.chunked(3)[1] == decltype(+arr.chunked(3)[1])({3.0, 4.0, 5.0}) ); - BOOST_REQUIRE( &arr.chunked(3)[1][2] == &arr[5] ); -} - -BOOST_AUTO_TEST_CASE(array_partitioned_vs_chunked_2D) { - multi::array arr({100, 53}); - BOOST_REQUIRE( size(arr.partitioned(20)) == 20 ); - BOOST_REQUIRE( &arr.partitioned(20)[1][2] == &arr[7] ); - - BOOST_REQUIRE( size(arr.chunked(5)) == 20 ); - BOOST_REQUIRE( &arr.chunked(5)[1][2] == &arr[7] ); -} + return boost::report_errors(); +} // NOLINT(readability/fn_size) diff --git a/test/pmr.cpp b/test/pmr.cpp index 1cf915712..ab5a6dadf 100644 --- a/test/pmr.cpp +++ b/test/pmr.cpp @@ -1,97 +1,121 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi pmr allocators" // title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array, extension_t, static_array -#include +#include -#if(MULTI_PROVIDES_PMR_ARRAY) -#include // for polymorphic memory resource, monotonic buffer -#endif +#include // for abs // IWYU pragma: keep +// IWYU pragma: no_include // for abs +#include // for size, data -#include +#if __has_include() +#include // for polymorphic_allocator, monotonic... +#endif namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(pmr_partially_formed) { -#if(MULTI_PROVIDES_PMR_ARRAY) +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) +#ifdef BOOST_MULTI_HAS_MEMORY_RESOURCE + // BOOST_AUTO_TEST_CASE(pmr_partially_formed) { - char buffer[] = "0123456789012345678901234567890123456789012345678901234567890123456789"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) use raw memory + { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif - std::pmr::monotonic_buffer_resource mbr{std::data(buffer), std::size(buffer)}; - static_assert( std::size(buffer) > 6*sizeof(double) ); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) use raw memory + char buffer[] = "0123456789012345678901234567890123456789012345678901234567890123456789"; - multi::array> const A({2, 3}, &mbr); // NOLINT(readability-identifier-length) - BOOST_TEST( buffer[ 0] == '0' ); // buffer is intact when initializing without value - BOOST_TEST( buffer[13] == '3' ); + std::pmr::monotonic_buffer_resource mbr{std::data(buffer), std::size(buffer)}; + static_assert(std::size(buffer) > 6 * sizeof(double)); - BOOST_TEST( A.num_elements() == 2*3 ); - // BOOST_TEST( A[0][0] != 0. ); - // BOOST_TEST( A[1][2] != 0. ); - } - { - char buffer[] = "0123456789012345678901234567890123456789012345678901234567890123456789"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) use raw memory + multi::array> const arr({2, 3}, &mbr); + BOOST_TEST( buffer[ 0] == '0' ); // buffer is intact when initializing without value + BOOST_TEST( buffer[13] == '3' ); - std::pmr::monotonic_buffer_resource mbr{std::data(buffer), std::size(buffer)}; - static_assert( std::size(buffer) > 6*sizeof(double) ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif - multi::array> A({2, 3}, 0., &mbr); // NOLINT(readability-identifier-length) - // BOOST_TEST( buffer[ 0] != '0' ); // buffer not is intact when initializing with value - // BOOST_TEST( buffer[13] != '3' ); + BOOST_TEST( arr.num_elements() == 2*3L ); + // BOOST_TEST( arr[0][0] != 0.0 ); + // BOOST_TEST( arr[1][2] != 0.0 ); + } + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) use raw memory + char buffer[] = "0123456789012345678901234567890123456789012345678901234567890123456789"; - BOOST_TEST( A[0][0] == 0. ); - BOOST_TEST( A[1][2] == 0. ); - } - { - char buffer[] = "0123456789012345678901234567890123456789012345678901234567890123456789"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) use raw memory + std::pmr::monotonic_buffer_resource mbr(std::data(buffer), std::size(buffer)); + static_assert(std::size(buffer) > 6 * sizeof(double)); + + multi::array> A({2, 3}, 0.0, &mbr); // NOLINT(readability-identifier-length) - std::pmr::monotonic_buffer_resource mbr{std::data(buffer), std::size(buffer)}; - static_assert( std::size(buffer) > 6*sizeof(double) ); + BOOST_TEST( std::abs( A[0][0] - 0.0 ) < 1E-6 ); + BOOST_TEST( std::abs( A[1][2] - 0.0 ) < 1E-6 ); + } + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) use raw memory + char buffer[] = "0123456789012345678901234567890123456789012345678901234567890123456789"; - multi::array> A({2, 3}, {}, &mbr); // NOLINT(readability-identifier-length) - // BOOST_TEST( buffer[ 0] != '0' ); // buffer not is intact when initializing with value - // BOOST_TEST( buffer[13] != '3' ); + std::pmr::monotonic_buffer_resource mbr(std::data(buffer), std::size(buffer)); + static_assert(std::size(buffer) > 6 * sizeof(double)); - BOOST_TEST( A[0][0] == double{} ); - BOOST_TEST( A[1][2] == double{} ); - } - { - char buffer[] = "0123456789012345678901234567890123456789012345678901234567890123456789"; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) use raw memory + multi::array> arr({2, 3}, {}, &mbr); - std::pmr::monotonic_buffer_resource mbr{std::data(buffer), std::size(buffer)}; - static_assert( std::size(buffer) > 6*sizeof(double) ); + BOOST_TEST( std::abs( arr[0][0] - double{} ) < 1E-6 ); + BOOST_TEST( std::abs( arr[1][2] - double{} ) < 1E-6 ); + } + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) use raw memory + char buffer[] = "0123456789012345678901234567890123456789012345678901234567890123456789"; - multi::array> A({2, 3}, 666., &mbr); // NOLINT(readability-identifier-length) - // BOOST_TEST( buffer[ 0] != '0' ); // buffer not is intact when initializing with value - // BOOST_TEST( buffer[13] != '3' ); + std::pmr::monotonic_buffer_resource mbr{std::data(buffer), std::size(buffer)}; + static_assert(std::size(buffer) > 6 * sizeof(double)); - BOOST_TEST( A[0][0] == 666. ); - BOOST_TEST( A[1][2] == 666. ); - } -#endif -} + multi::array> arr({2, 3}, 666.0, &mbr); -BOOST_AUTO_TEST_CASE(pmr_benchmark) { -#if(MULTI_PROVIDES_PMR_ARRAY) -// auto* resp = std::pmr::unsynchronized_pool_resource(std::pmr::get_default_resource()); - auto* resp = std::pmr::get_default_resource(); - - auto count = 50; - auto start_time = std::chrono::high_resolution_clock::now(); - - multi::extension_t const exts{0, count}; - auto acc = std::transform_reduce( - exts.begin(), exts.end(), int64_t{0}, - std::plus<>{}, - [&resp](auto idx) { - multi::pmr::array arr({1000 - idx%10, 1000 + idx%10}, resp); - std::fill_n(arr.data_elements(), arr.num_elements(), 1); - return std::accumulate(arr.data_elements(), arr.data_elements() + arr.num_elements(), 0L); + BOOST_TEST( std::abs( arr[0][0] - 666.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr[1][2] - 666.0 ) < 1E-6 ); } - ); + } + + // BOOST_AUTO_TEST_CASE(pmr_benchmark) { + // // auto* resp = std::pmr::unsynchronized_pool_resource(std::pmr::get_default_resource()); + // auto* resp = std::pmr::get_default_resource(); + + // auto count = 50; + + // auto start_time = std::chrono::high_resolution_clock::now(); + + // multi::extension_t const exts{0, count}; + + // auto acc = std::transform_reduce( + // exts.begin(), exts.end(), int64_t{0}, + // std::plus<>{}, + // [&resp](auto idx) { + // multi::array> arr( + // multi::extensions_t<2>{1000 - (idx % 10), 1000 + (idx % 10)}, // MSVC needs multi::extensions_t<2> + // resp + // ); + // std::fill_n(arr.data_elements(), arr.num_elements(), 1); + + // auto const be = arr.elements().begin(); + // auto const en = arr.elements().end(); + // return std::accumulate(be, en, int64_t{0}, std::plus{}); + // } + // ); + + // BOOST_TEST( acc == 49998575 ); + + // auto time = std::chrono::high_resolution_clock::now() - start_time; + // std::cout << time.count() / count << " " << acc << '\n'; + // } - auto time = std::chrono::high_resolution_clock::now() - start_time; - std::cout<< time.count() / count <<" "<< acc < // for array, subarray, static_array // IWYU pragma: keep + +#include + +#include // for std::ran // IWYU pragma: keep // NOLINT(misc-include-cleaner) +#include // for plus<> // IWYU pragma: keep // NOLINT(misc-include-cleaner) +#include // for allocator // IWYU pragma: keep // NOLINT(misc-include-cleaner) + +#if defined(__cplusplus) && (__cplusplus >= 202002L) && __has_include() +#if !defined(__clang_major__) || (__clang_major__ != 16) +#include // IWYU pragma: keep +#endif +#endif + +#if defined(__cpp_lib_ranges_fold) && (__cpp_lib_ranges_fold >= 202207L) +#include // for complex, real, operator==, imag // IWYU pragma: keep +#include // for size, begin, end // IWYU pragma: keep +#include // for iota // IWYU pragma: keep +#include // for is_same_v // IWYU pragma: keep +#include // for pair // IWYU pragma: keep +#endif + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + { + multi::array const arr({10}, 99); + for(auto const& elem : arr) { // NOLINT(altera-unroll-loops) test plain loop + BOOST_TEST( elem == 99 ); + } + } + + // range accumulate + { +#if defined(__cpp_lib_ranges_fold) && (__cpp_lib_ranges_fold >= 202207L) + namespace multi = boost::multi; + + static constexpr auto accumulate = [](auto const& R) { + return std::ranges::fold_left(R, 0, std::plus<>{}); + }; + + auto const values = multi::array{ + {2, 0, 2, 2}, + {2, 2, 0, 4}, + {2, 2, 0, 4}, + {2, 2, 0, 0}, + {2, 7, 0, 2}, + {2, 2, 4, 4}, + }; + + boost::multi::array> aaa = {1, 2, 3}; + + constexpr auto rowOddSum = [](auto const& arr) { + return std::ranges::find_if(arr, [](auto const& row) { return (accumulate(row) & 1) == 1; }); + }; + + auto const result = rowOddSum(values); + + BOOST_TEST( result - values.begin() == 4 ); + + { + auto const lazy1D = [](auto ii) noexcept { return static_cast(ii); } ^ multi::extensions_t(6); + auto const sum = std::ranges::fold_left(lazy1D, float{}, std::plus<>{}); + BOOST_TEST( std::abs(sum - (0.0F + 1.0F + 2.0F + 3.0F + 4.0F + 5.0F)) < 1e-12F ); + } +#endif + } + + // BOOST_AUTO_TEST_CASE(range_find) + { +#if defined(__cpp_lib_ranges_fold) && (__cpp_lib_ranges_fold >= 202207L) + using Array2D = multi::array; + + Array2D const a = { + {1, 2}, + {3, 4}, + }; + { + auto const needle = std::ranges::find_if(a, [](auto const& row) { return row[0] == 9; }); + BOOST_TEST(needle == a.end()); + } + { + std::ranges::equal_to eto; + + auto a1 = a[1]; + auto a1_val = +a[1]; + + bool const res = eto(a1_val, a1); + BOOST_TEST( res ); + } + { + auto&& a1 = a[1]; + + auto const needle = std::ranges::find(a, a1); + BOOST_TEST(needle != a.end()); + BOOST_TEST( *needle == a1 ); + BOOST_TEST( *needle == a[1] ); + } + [&] { + auto const needle = std::ranges::find(a, a[1]); + BOOST_TEST(needle != a.end()); + BOOST_TEST( *needle == a[1] ); + }(); +#endif + } + + // #if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) + // BOOST_AUTO_TEST_CASE(range_copy_n_1D) { + // namespace multi = boost::multi; + + // multi::array const X1 = {1, 2, 3}; + // multi::array X2(X1.extensions()); + + // std::ranges::copy_n(X1.begin(), 10, X2.begin()); + + // BOOST_TEST( X1 == X2 ); + // } + + // BOOST_AUTO_TEST_CASE(range_copy_n) { + // namespace multi = boost::multi; + + // multi::array const X1({ 10, 10 }, 99); + // multi::array X2(X1.extensions()); + + // std::ranges::copy_n(X1.begin(), 10, X2.begin()); + // BOOST_TEST( X1 == X2 ); + // } + // #endif + +#if defined(__cpp_lib_ranges_zip) && (__cpp_lib_ranges_zip >= 202110L) && !defined(_MSC_VER) && !defined(__NVCOMPILER) +#if !defined(__clang_major__) || (__clang_major__ != 16) + { + multi::array A = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + }; + + multi::array const V = {10, 11, 12}; + + multi::array const R{std::ranges::views::zip_transform(std::plus<>{}, A[0], V)}; + + BOOST_TEST( R[0] == 11 ); + BOOST_TEST( R[1] == 13 ); + BOOST_TEST( R[2] == 15 ); + } + { + multi::array A = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + }; + + multi::array const V = {10, 11, 12}; + + multi::array const R = std::ranges::views::zip_transform(std::plus<>{}, A[0], V); + + BOOST_TEST( R[0] == 11 ); + BOOST_TEST( R[1] == 13 ); + BOOST_TEST( R[2] == 15 ); + } + { + static_assert(std::ranges::viewable_range>); + multi::array const A = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + }; + + BOOST_TEST( A.begin() == std::ranges::begin(A) ); + BOOST_TEST( A.end() == std::ranges::end(A) ); + + BOOST_TEST( A().begin() == std::ranges::begin(A()) ); + BOOST_TEST( A().end() == std::ranges::end(A()) ); + + BOOST_TEST( A[1].begin() == std::ranges::begin(A[1]) ); + BOOST_TEST( A[1].end() == std::ranges::end(A[1]) ); + + multi::array const V = {10, 11, 12}; + multi::array const R = std::ranges::views::zip_transform(std::plus<>{}, A[0], V); + + BOOST_TEST( R[0] == 11 ); + BOOST_TEST( R[1] == 13 ); + BOOST_TEST( R[2] == 15 ); + + // R = std::ranges::views::zip_transform(std::plus<>{}, A[0], V); + } +#endif +#endif + + return boost::report_errors(); +} diff --git a/test/reextent.cpp b/test/reextent.cpp index 748c2c18d..43f73d4e1 100644 --- a/test/reextent.cpp +++ b/test/reextent.cpp @@ -1,270 +1,357 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi reextent" // test title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array, static_array, num_elements -#include "multi/array.hpp" +#include + +// IWYU pragma: no_include // for fill_n // bug in iwyu 14.0.6? with GNU stdlib +#include // for initializer_list +#include // for size +#include // for get // NOLINT(misc-include-cleaner) +#include // for make_unsigned_t +#include // for move +#include // for vector namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(array_reextent) { - multi::array arr({2, 3}); - BOOST_REQUIRE( num_elements(arr) == 6 ); +namespace { +template +constexpr auto comp_equal(T left, U right) noexcept -> bool { + using UT = std::make_unsigned_t; + using UU = std::make_unsigned_t; + if constexpr(std::is_signed_v == std::is_signed_v) { + return left == right; + } else if constexpr(std::is_signed_v) { + return left < 0 ? false : static_cast(left) == right; + } else { + return right < 0 ? false : left == UU(right); + } +#if !defined(__INTEL_COMPILER) && !defined(__NVCOMPILER) && !defined(_MSC_VER) + __builtin_unreachable(); +#endif +} +} // end unnamed namespace - arr[1][2] = 6.0; - BOOST_REQUIRE( arr[1][2] == 6.0 ); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(array_reextent) + { + multi::array arr({2, 3}); + BOOST_TEST( num_elements(arr) == 6 ); - multi::array arr3({2, 3}); - BOOST_REQUIRE(size(arr3) == 2); - BOOST_REQUIRE(size(arr3[0]) == 3); + arr[1][2] = 60; + BOOST_TEST( arr[1][2] == 60 ); // cppcheck-suppress knownConditionTrueFalse ; - arr.reextent({5, 4}, 99.); - BOOST_REQUIRE( num_elements(arr)== 5L*4L ); - BOOST_REQUIRE( arr[1][2] == 6. ); // reextent preserves values when it can... - BOOST_REQUIRE( arr[4][3] == 99. ); // ...and gives selected value to the rest -} + multi::array arr3({2, 3}); + BOOST_TEST(size(arr3) == 2); + BOOST_TEST(size(arr3[0]) == 3); -BOOST_AUTO_TEST_CASE(array_reextent_noop) { - multi::array arr({2, 3}); - BOOST_REQUIRE( num_elements(arr) == 6 ); + arr.reextent({5, 4}, 990); + BOOST_TEST( num_elements(arr)== 5L*4L ); + BOOST_TEST( arr[1][2] == 60 ); // reextent preserves values when it can... + BOOST_TEST( arr[4][3] == 990 ); // ...and gives selected value to the rest + } + { + multi::array arr({2, 3}); - arr[1][2] = 6.; - BOOST_REQUIRE( arr[1][2] == 6. ); + BOOST_TEST( arr.size() == 2 ); - multi::array arr3({2, 3}); - BOOST_REQUIRE(size(arr3) == 2); - BOOST_REQUIRE(size(arr3[0]) == 3); + arr = multi::array{}; - auto* const A_base = arr.base(); - arr.reextent({2, 3}); - BOOST_REQUIRE( num_elements(arr)== 2L*3L ); - BOOST_REQUIRE( arr[1][2] == 6.0 ); // reextent preserves values when it can... + BOOST_TEST( arr.size() == 0 ); + } - BOOST_REQUIRE( A_base == arr.base() ); -} +#ifndef __circle_build__ + { + multi::array const arr({2, 3}); -BOOST_AUTO_TEST_CASE(array_reextent_noop_with_init) { - multi::array arr({2, 3}); - BOOST_REQUIRE( num_elements(arr) == 6 ); + BOOST_TEST( arr.size() == 2 ); - arr[1][2] = 6.0; - BOOST_REQUIRE( arr[1][2] == 6.0 ); + multi::array const brr({}); - multi::array arr3({2, 3}); - BOOST_REQUIRE(size(arr3) == 2); - BOOST_REQUIRE(size(arr3[0]) == 3); + BOOST_TEST( brr.size() == 0 ); + } - auto* const A_base = arr.base(); - arr.reextent({2, 3}, 99.0); - BOOST_REQUIRE( num_elements(arr)== 2L*3L ); - BOOST_REQUIRE( arr[1][2] == 6.0 ); // reextent preserves values when it can... + { + multi::array const arr({2, 3}); - BOOST_REQUIRE( A_base == arr.base() ); -} + BOOST_TEST( arr.size() == 2 ); -BOOST_AUTO_TEST_CASE(array_reextent_moved) { - multi::array arr({2, 3}); - BOOST_REQUIRE( num_elements(arr) == 6 ); + multi::array const brr = {}; - arr[1][2] = 6.; - BOOST_REQUIRE( arr[1][2] == 6.0 ); + BOOST_TEST( brr.size() == 0 ); + } - auto* const A_base = arr.base(); + { + multi::array arr({2, 3}); - arr = std::move(arr).reextent({2, 3}); // "arr = ..." suppresses linter bugprone-use-after-move,hicpp-invalid-access-moved + BOOST_TEST( arr.size() == 2 ); - BOOST_TEST_REQUIRE( arr.size() == 2 ); - BOOST_REQUIRE( arr.num_elements() == 2L*3L ); - BOOST_REQUIRE( num_elements(arr)== 2L*3L ); - BOOST_TEST(arr[1][2] == 6.0); // after move the original elments might not be the same + arr = {}; - BOOST_REQUIRE( A_base == arr.base() ); -} + BOOST_TEST( arr.size() == 0 ); + } + { + // multi::array arr({2, 3}); -BOOST_AUTO_TEST_CASE(array_reextent_moved_trivial) { - multi::array arr({2, 3}); - BOOST_REQUIRE( num_elements(arr) == 6 ); + // BOOST_TEST( arr.size() == 2 ); - arr[1][2] = 6.0; - BOOST_REQUIRE( arr[1][2] == 6.0 ); + // arr = {{}}; // TODO(correaa) this syntax produces UB in clang - auto* const A_base = arr.base(); - arr = std::move(arr).reextent({2, 3}); // "arr = ..." suppresses linter bugprone-use-after-move,hicpp-invalid-access-moved - BOOST_REQUIRE( num_elements(arr)== 2L*3L ); - BOOST_REQUIRE( arr[1][2] == 6.0 ); // after move the original elments might not be the same + // BOOST_TEST( arr.size() == 0 ); + } +#endif - BOOST_REQUIRE( A_base == arr.base() ); -} + // BOOST_AUTO_TEST_CASE(array_reextent_noop) + { + multi::array arr({2, 3}); + BOOST_TEST( num_elements(arr) == 6 ); -BOOST_AUTO_TEST_CASE(array_reextent_moved_trivial_change_extents) { - multi::array arr({2, 3}); - BOOST_REQUIRE( num_elements(arr) == 6 ); + arr[1][2] = 60; + BOOST_TEST( arr[1][2] == 60 ); // cppcheck-suppress knownConditionTrueFalse ; - arr[1][2] = 6.0; - BOOST_REQUIRE( arr[1][2] == 6.0 ); + multi::array arr3({2, 3}); + BOOST_TEST(size(arr3) == 2); + BOOST_TEST(size(arr3[0]) == 3); - auto* const A_base = arr.base(); - arr = std::move(arr).reextent({4, 5}); - BOOST_REQUIRE( num_elements(arr)== 4L*5L ); - // BOOST_REQUIRE( arr[1][2] != 6.0 ); // after move the original elements might not be the same, but it is not 100% possible to check + auto* const A_base = arr.base(); + arr.reextent({2, 3}); + BOOST_TEST( num_elements(arr)== 2L*3L ); + BOOST_TEST( arr[1][2] == 60 ); // reextent preserves values when it can... - BOOST_REQUIRE( A_base != arr.base() ); -} + BOOST_TEST( A_base == arr.base() ); + } -BOOST_AUTO_TEST_CASE(array_move_clear) { - multi::array const iarr; - multi::array arr({2, 3}); + // BOOST_AUTO_TEST_CASE(array_reextent_noop_with_init) + { + multi::array arr({2, 3}); + BOOST_TEST( num_elements(arr) == 6 ); - arr = multi::array(extensions(arr), 123.0); - BOOST_REQUIRE( arr[1][2] == 123.0 ); + arr[1][2] = 60; + BOOST_TEST( arr[1][2] == 60 ); // cppcheck-suppress knownConditionTrueFalse ; - arr.clear(); // clear(arr); - BOOST_REQUIRE( num_elements(arr) == 0 ); - BOOST_REQUIRE( size(arr) == 0 ); + multi::array arr3({2, 3}); + BOOST_TEST(size(arr3) == 2); + BOOST_TEST(size(arr3[0]) == 3); - arr.reextent({5, 4}, 66.0); - BOOST_REQUIRE( arr[4][3] == 66.0 ); + auto* const A_base = arr.base(); + arr.reextent({2, 3}, 990); + BOOST_TEST( num_elements(arr)== 2L*3L ); + BOOST_TEST( arr[1][2] == 60 ); // reextent preserves values when it can... - BOOST_REQUIRE(iarr.is_empty()); -} + BOOST_TEST( A_base == arr.base() ); + } -BOOST_AUTO_TEST_CASE(array_reextent_1d) { - multi::array arr(multi::extensions_t<1>{multi::iextension{10}}, 4.0); - BOOST_REQUIRE( size(arr) == 10 ); - BOOST_REQUIRE( arr[9] == 4.0 ); + // BOOST_AUTO_TEST_CASE(array_reextent_moved) + { + multi::array arr({2, 3}); + BOOST_TEST( num_elements(arr) == 6 ); - arr.reextent(multi::extensions_t<1>{multi::iextension{20}}); - BOOST_REQUIRE( size(arr) == 20 ); - BOOST_REQUIRE( arr[9] == 4.0 ); - // BOOST_REQUIRE( arr[19] == 0.0 ); // impossible to know since it is only sometimes 0.0 + arr[1][2] = 60; + BOOST_TEST( arr[1][2] == 60 ); // cppcheck-suppress knownConditionTrueFalse ; - arr.reextent(boost::multi::tuple(22)); - BOOST_REQUIRE( size(arr) == 22 ); - BOOST_REQUIRE( arr[9] == 4.0 ); + auto* const A_base = arr.base(); - arr.reextent({23}); - BOOST_REQUIRE( size(arr) == 23 ); -} + arr = std::move(arr).reextent({2, 3}); // "arr = ..." suppresses linter bugprone-use-after-move,hicpp-invalid-access-moved -BOOST_AUTO_TEST_CASE(tuple_decomposition) { - boost::multi::tuple const tup{1, 2}; - auto [t0, t1] = tup; - BOOST_REQUIRE( t0 == 1 ); - BOOST_REQUIRE( t1 == 2 ); -} + BOOST_TEST( arr.size() == 2 ); + BOOST_TEST( arr.num_elements() == 2L*3L ); + BOOST_TEST( num_elements(arr)== 2L*3L ); + BOOST_TEST(arr[1][2] == 60); // after move the original elments might not be the same -BOOST_AUTO_TEST_CASE(array_reextent_0D) { - multi::array const arr({}, 4.0); - // arr.reextent(arr.extensions()); // TODO(correaa) : fix unused for D = 0 - BOOST_REQUIRE( *arr.data_elements() == 4.0 ); -} + BOOST_TEST( A_base == arr.base() ); + } -BOOST_AUTO_TEST_CASE(array_reextent_1d_with_initialization) { - multi::array arr(multi::extensions_t<1>{multi::iextension{10}}, 4.0); - BOOST_REQUIRE( size(arr) == 10 ); - BOOST_REQUIRE( arr[9] == 4.0 ); + // BOOST_AUTO_TEST_CASE(array_reextent_moved_trivial) + { + multi::array arr({2, 3}); + BOOST_TEST( num_elements(arr) == 6 ); - arr.reextent(multi::extensions_t<1>{multi::iextension{20}}, 8.0); - BOOST_REQUIRE( size(arr) == 20 ); - BOOST_REQUIRE( arr[9] == 4.0 ); - BOOST_REQUIRE( arr[19] == 8.0 ); -} + arr[1][2] = 60; + BOOST_TEST( arr[1][2] == 60 ); // cppcheck-suppress knownConditionTrueFalse ; -BOOST_AUTO_TEST_CASE(array_reextent_2d) { - multi::array arr({10, 20}, 4.0); - BOOST_REQUIRE( arr[1][2] == 4.0 ); + auto* const A_base = arr.base(); - arr.clear(); - BOOST_REQUIRE( num_elements(arr) == 0 ); - BOOST_REQUIRE( size(arr) == 0 ); + arr = std::move(arr).reextent({2, 3}); // "arr = ..." suppresses linter bugprone-use-after-move,hicpp-invalid-access-moved - arr.reextent({20, 30}, 9.0); - BOOST_REQUIRE( arr[1][2] = 9. ); - BOOST_REQUIRE( arr[11][22] = 9.0 ); -} + BOOST_TEST( num_elements(arr)== 2L*3L ); + BOOST_TEST( arr[1][2] == 60 ); // after move the original elments might not be the same -BOOST_AUTO_TEST_CASE(array_reextent_2d_with_move) { - multi::array arr = { - {1, 2, 3}, - {4, 5, 6}, - }; - BOOST_REQUIRE( arr.size() == 2 ); + BOOST_TEST( A_base == arr.base() ); + } - arr = std::move(arr).reextent({3, 2}); + // BOOST_AUTO_TEST_CASE(array_reextent_moved_trivial_change_extents) + { + multi::array arr({2, 3}); + BOOST_TEST( num_elements(arr) == 6 ); - BOOST_REQUIRE( arr.size() == 3 ); - BOOST_REQUIRE( arr[1][2] = 10 ); -} + arr[1][2] = 60; + BOOST_TEST( arr[1][2] == 60 ); // cppcheck-suppress knownConditionTrueFalse ; -BOOST_AUTO_TEST_CASE(array_reextent_2d_array) { - multi::array arr({10, 20}, 4.0); - BOOST_REQUIRE( arr[1][2] == 4.0 ); + auto* const A_base = arr.base(); - arr.clear(); - BOOST_REQUIRE( num_elements(arr) == 0 ); - BOOST_REQUIRE( size(arr) == 0 ); -} + arr = std::move(arr).reextent({4, 5}); -template -constexpr auto comp_equal(T left, U right) noexcept -> bool { - using UT = std::make_unsigned_t; - using UU = std::make_unsigned_t; - if constexpr(std::is_signed_v == std::is_signed_v) { - return left == right; - } else if constexpr(std::is_signed_v) { - return left < 0 ? false : static_cast(left) == right; - } else { - return right < 0 ? false : left == UU(right); + BOOST_TEST( num_elements(arr)== 4L*5L ); + // BOOST_TEST( arr[1][2] != 6.0 ); // after move the original elements might not be the same, but it is not 100% possible to check + + BOOST_TEST( A_base != arr.base() ); } -#if not defined(__INTEL_COMPILER) and not defined(__NVCOMPILER) - __builtin_unreachable(); -#endif -} -BOOST_AUTO_TEST_CASE(array_vector_size) { - std::vector const vec(100); // std::vector NOLINT(fuchsia-default-arguments-calls) + // BOOST_AUTO_TEST_CASE(array_move_clear) { - // multi::array a( vec.size() ); // warning: sign-conversion - multi::array const arr(static_cast(vec.size())); - BOOST_REQUIRE( comp_equal(arr.size(), vec.size()) ); + multi::array arr({2, 3}); + + arr = multi::array(extensions(arr), 1230); + BOOST_TEST( arr[1][2] == 1230 ); + + arr.clear(); + BOOST_TEST( num_elements(arr) == 0 ); + BOOST_TEST( size(arr) == 0 ); + + arr.reextent({5, 4}, 660); + BOOST_TEST( arr[4][3] == 660 ); } + + // BOOST_AUTO_TEST_CASE(array_reextent_1d) { - multi::array const arr(multi::iextensions<1>(static_cast(vec.size()))); // warning: sign-conversion - // multi::array a(static_cast(v.size())); - BOOST_REQUIRE( comp_equal(arr.size(), vec.size()) ); + multi::array arr(multi::extensions_t<1>{multi::iextension{10}}, 40); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[9] == 40 ); + + arr.reextent(multi::extensions_t<1>{multi::iextension{20}}); + BOOST_TEST( size(arr) == 20 ); + BOOST_TEST( arr[9] == 40 ); + // BOOST_TEST( arr[19] == 0.0 ); // impossible to know since it is only sometimes 0.0 + + arr.reextent(boost::multi::tuple(22)); + BOOST_TEST( size(arr) == 22 ); + BOOST_TEST( arr[9] == 40 ); + + arr.reextent({23}); + BOOST_TEST( size(arr) == 23 ); } -} -BOOST_AUTO_TEST_CASE(array_iota) { - multi::array const Aarr(10); - multi::array Barr(Aarr.extension().begin(), Aarr.extension().end()); - BOOST_REQUIRE( Barr[0] == 0 ); - BOOST_REQUIRE( Barr[1] == 1 ); - BOOST_REQUIRE( Barr[9] == 9 ); + // BOOST_AUTO_TEST_CASE(tuple_decomposition) + { + boost::multi::tuple const tup{1, 2}; + auto [t0, t1] = tup; + BOOST_TEST( t0 == 1 ); + BOOST_TEST( t1 == 2 ); + } - multi::array Carr(Aarr.extension()); - BOOST_REQUIRE( Carr[0] == 0 ); - BOOST_REQUIRE( Carr[1] == 1 ); - BOOST_REQUIRE( Carr[9] == 9 ); + // BOOST_AUTO_TEST_CASE(array_reextent_0D) + { + multi::array arr({}, 40); + arr.reextent(arr.extensions()); + BOOST_TEST( *arr.data_elements() == 40 ); + } - multi::array const Darr(Aarr.extensions()); - BOOST_REQUIRE( Darr.extensions() == Aarr.extensions() ); -} + // BOOST_AUTO_TEST_CASE(array_reextent_1d_with_initialization) + { + multi::array arr(multi::extensions_t<1>{multi::iextension{10}}, 40); + BOOST_TEST( size(arr) == 10 ); + BOOST_TEST( arr[9] == 40 ); + + arr.reextent(multi::extensions_t<1>{multi::iextension{20}}, 80); + BOOST_TEST( size(arr) == 20 ); + BOOST_TEST( arr[9] == 40 ); + BOOST_TEST( arr[19] == 80 ); + } + + // BOOST_AUTO_TEST_CASE(array_reextent_2d) + { + multi::array arr({10, 20}, 40); + BOOST_TEST( arr[1][2] == 40 ); + + arr.clear(); + BOOST_TEST( num_elements(arr) == 0 ); + BOOST_TEST( size(arr) == 0 ); + + arr.reextent({20, 30}, 90); + BOOST_TEST( arr[1][2] == 90 ); + BOOST_TEST( arr[11][22] == 90 ); + } + + // BOOST_AUTO_TEST_CASE(array_reextent_2d_with_move) + { + multi::array arr = { + {1, 2, 3}, + {4, 5, 6}, + }; + BOOST_TEST( arr.size() == 2 ); + + arr = std::move(arr).reextent({3, 2}); + + BOOST_TEST( arr.size() == 3 ); + } + + // BOOST_AUTO_TEST_CASE(array_reextent_2d_array) + { + multi::array arr({10, 20}, 40); + BOOST_TEST( arr[1][2] == 40 ); + + arr.clear(); + BOOST_TEST( num_elements(arr) == 0 ); + BOOST_TEST( size(arr) == 0 ); + } + + // BOOST_AUTO_TEST_CASE(array_vector_size) + { + std::vector const vec(100); // std::vector NOLINT(fuchsia-default-arguments-calls) + { + multi::array const arr(static_cast(vec.size())); + BOOST_TEST( comp_equal(arr.size(), vec.size()) ); + } + { + multi::array const arr(multi::iextensions<1>(static_cast(vec.size()))); // warning: sign-conversion + // multi::array a(static_cast(v.size())); + BOOST_TEST( comp_equal(arr.size(), vec.size()) ); + } + } + + // BOOST_AUTO_TEST_CASE(array_iota) + { + multi::array const Aarr(10); + + multi::array::index, 1> const Barr(Aarr.extension().begin(), Aarr.extension().end()); + + BOOST_TEST( Barr[0] == 0 ); + BOOST_TEST( Barr[1] == 1 ); + BOOST_TEST( Barr[9] == 9 ); + + multi::array::index, 1> const Carr(Aarr.extension()); + BOOST_TEST( Carr[0] == 0 ); + BOOST_TEST( Carr[1] == 1 ); + BOOST_TEST( Carr[9] == 9 ); + + multi::array::index, 1> const Darr(Aarr.extensions()); + BOOST_TEST( Darr.extensions() == Aarr.extensions() ); + } #ifndef __INTEL_COMPILER -BOOST_AUTO_TEST_CASE(extension_index_op) { - multi::array const Aarr({11, 13}); - auto Aext = Aarr.extensions(); - BOOST_REQUIRE( std::get<0>(Aext[3][5]) == 3 ); - BOOST_REQUIRE( std::get<1>(Aext[3][5]) == 5 ); - - for(int i = 0; i != 3; ++i) { - for(int j = 0; j != 5; ++j) { - auto [ip, jp] = Aext[i][j]; - BOOST_REQUIRE(ip == i); - BOOST_REQUIRE(jp == j); + // BOOST_AUTO_TEST_CASE(extension_index_op) + { + multi::array const Aarr({11, 13}); + + auto const Aext = Aarr.extensions(); + + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + BOOST_TEST( get<0>(Aext[3][5]) == 3 ); + BOOST_TEST( get<1>(Aext[3][5]) == 5 ); + + for(int i = 0; i != 3; ++i) { + for(int j = 0; j != 5; ++j) { // NOLINT(altera-unroll-loops) + auto [ip, jp] = Aext[i][j]; + BOOST_TEST(ip == i); + BOOST_TEST(jp == j); + } } } -} #endif + + return boost::report_errors(); +} diff --git a/test/reinterpret_array_cast.cpp b/test/reinterpret_array_cast.cpp index a7e531100..6f47c5de2 100644 --- a/test/reinterpret_array_cast.cpp +++ b/test/reinterpret_array_cast.cpp @@ -1,177 +1,369 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi reinterpret array" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array, subarray, static_array -#include +#include -#include -#include +#include // for get, array +#include // for abs // IWYU pragma: keep +#include // for complex, real, operator==, imag +// IWYU pragma: no_include // for abs +#include // for size, begin, end +// IWYU pragma: no_include // for allocator +#include // for iota +#include // for is_same_v +#include // for pair namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_struct_to_dimension) { - struct vec3 { - double x, y, z; - }; - multi::array arr(multi::extensions_t<1>{multi::iextension{100}}); - arr[8] = {1.0, 2.0, 3.0}; - BOOST_REQUIRE( arr[8].y == 2.0 ); +template struct complex_dummy { + T real; + T imag; +}; - BOOST_REQUIRE( arr.reinterpret_array_cast(3)[8][1] == arr[8].y ); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // simple class reinterpret + { + struct int_class { + int val_; + }; - multi::array A2D = arr.reinterpret_array_cast(3); + multi::array arr1d({3}, int_class{}); + arr1d[0].val_ = 5; + BOOST_TEST( arr1d.reinterpret_array_cast()[0] == 5 ); - BOOST_REQUIRE( decltype(A2D)::dimensionality == decltype(arr)::dimensionality + 1 ); - BOOST_REQUIRE( dimensionality(A2D) == dimensionality(arr) + 1 ); + multi::array arr2d({3, 3}, int_class{}); + arr2d[0][0].val_ = 5; + BOOST_TEST( arr2d.reinterpret_array_cast()[0][0] == 5 ); + } - BOOST_REQUIRE( size(A2D) == size(arr) ); - BOOST_REQUIRE( A2D[8][1] == arr[8].y ); - BOOST_REQUIRE( &A2D[8][1] != &arr[8].y ); + // BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_struct_to_dimension) + { + struct vec3 { + double x; + double y; + double z; + }; + multi::array arr(multi::extensions_t<1>{multi::iextension{100}}); + arr[8] = {1.0, 2.0, 3.0}; // NOLINT(modernize-use-designated-initializers) for C++20 + BOOST_TEST( std::abs( arr[8].y - 2.0 ) < 1E-6 ); - BOOST_REQUIRE( & arr[8].x == & arr.reinterpret_array_cast(3)[8][0] ); - BOOST_REQUIRE( & arr[8].y == & arr.reinterpret_array_cast(3)[8][1] ); - BOOST_REQUIRE( & arr[8].z == & arr.reinterpret_array_cast(3)[8][2] ); -} + BOOST_TEST( std::abs( arr.reinterpret_array_cast(3)[8][1] - arr[8].y ) < 1E-6 ); -BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_complex_to_real_extra_dimension) { - using complex = std::complex; - multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, complex{1., 2.}); - BOOST_REQUIRE( size(arr) == 100 ); + multi::array A2D{arr.reinterpret_array_cast(3)}; + BOOST_TEST( decltype(A2D)::dimensionality == decltype(arr)::dimensionality + 1 ); + BOOST_TEST( dimensionality(A2D) == dimensionality(arr) + 1 ); + + BOOST_TEST( size(A2D) == size(arr) ); + BOOST_TEST( std::abs( A2D[8][1] - arr[8].y ) < 1E-6); + BOOST_TEST( &A2D[8][1] != &arr[8].y ); + + BOOST_TEST( & arr[8].x == & arr.reinterpret_array_cast(3)[8][0] ); + BOOST_TEST( & arr[8].y == & arr.reinterpret_array_cast(3)[8][1] ); + BOOST_TEST( & arr[8].z == & arr.reinterpret_array_cast(3)[8][2] ); + } + + // BOOST_AUTO_TEST_CASE(multi_lower_dimension) { - complex const arr0 = arr[0]; - BOOST_TEST_REQUIRE( arr0.real() == 1.0 ); - BOOST_TEST_REQUIRE( arr0.imag() == 2.0 ); + struct vec3 { + double x; + double y; + double z; + + // [[maybe_unused]] auto operator==(vec3 const& other) const -> bool { return x == other.x && y == other.y && z == other.z; } + }; + + multi::array arr = { + {0.0, 0.1, 0.2}, + {1.0, 1.1, 1.2}, + {2.0, 2.1, 2.2}, + {3.0, 3.1, 3.2}, + }; + { + BOOST_TEST( arr.size() == 4 ); + BOOST_TEST( arr.flatted().size() == 12 ); + BOOST_TEST( arr.flatted().strided(3).size() == 4 ); + BOOST_TEST( arr.flatted().strided(3).reinterpret_array_cast().size() == 4 ); + + auto&& arrvec3 = arr.flatted().strided(3).reinterpret_array_cast(); + + BOOST_TEST( arr.flatted().size() == arrvec3.size()*3 ); + BOOST_TEST( &arrvec3[2].x == &arr[2][0] ); + } } - BOOST_TEST_REQUIRE( arr[0].real() == 1.0 ); - BOOST_TEST_REQUIRE( arr[0].imag() == 2.0 ); + // BOOST_AUTO_TEST_CASE(multi_lower_dimension_2d) + { + struct vec3 { + double x; + double y; + double z; + }; + + multi::array d2 = { + {0.0, 0.1, 0.2, 0.0, 0.1, 0.2, 0.0, 0.1, 0.2}, + {1.0, 1.1, 1.2, 1.0, 1.1, 1.2, 1.0, 1.1, 1.2}, + {2.0, 2.1, 2.2, 2.0, 2.1, 2.2, 2.0, 2.1, 2.2}, + {3.0, 3.1, 3.2, 3.0, 3.1, 3.2, 3.0, 3.1, 3.2}, + }; + + { + auto&& d2strided3 = d2.unrotated().strided(3).rotated(); + BOOST_TEST( d2strided3.size() == 4 ); + BOOST_TEST( d2strided3[0].size() == 3 ); + BOOST_TEST( &d2strided3[1][2] == &d2[1][6] ); + } + { + auto&& v2view = d2.unrotated().strided(3).rotated().reinterpret_array_cast(); + BOOST_TEST( v2view.size() == 4 ); + BOOST_TEST( v2view[0].size() == 3 ); + BOOST_TEST( &v2view[1][2].x == &d2[1][6] ); + } + } - BOOST_TEST_REQUIRE( std::real(arr[0]) == 1.0 ); - BOOST_TEST_REQUIRE( std::imag(arr[0]) == 2.0 ); + // BOOST_AUTO_TEST_CASE(multi_lower_dimension_3d) + { + struct vec3 { + double x; + double y; + double z; + }; + + multi::array d3({4, 15, 9}, 0.0); + + { + auto&& d3strided3 = d3.unrotated().strided(3).rotated(); + BOOST_TEST( d3strided3.size() == 4 ); + BOOST_TEST( d3strided3[0][0].size() == 3 ); + BOOST_TEST( &d3strided3[3][1][2] == &d3[3][1][6] ); + } + { + auto&& v3view = d3.unrotated().strided(3).rotated().reinterpret_array_cast(); + BOOST_TEST( v3view.size() == 4 ); + BOOST_TEST( v3view[0][0].size() == 3 ); + BOOST_TEST( &v3view[3][1][2].x == &d3[3][1][6] ); + BOOST_TEST( &v3view[3][1][2].y == &d3[3][1][7] ); + BOOST_TEST( &v3view[3][1][2].z == &d3[3][1][8] ); + } + } - BOOST_TEST_REQUIRE( real(arr[0]) == 1.0 ); - BOOST_TEST_REQUIRE( imag(arr[0]) == 2.0 ); + // BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_complex_to_real_extra_dimension) + { + using complex = std::complex; + multi::array arr(multi::extensions_t<1>{multi::iextension{100}}, complex{1.0, 2.0}); + BOOST_TEST( size(arr) == 100 ); - BOOST_REQUIRE(( arr[0] == complex{1.0, 2.0} )); + { + complex const arr0 = arr[0]; + BOOST_TEST( std::abs( arr0.real() - 1.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr0.imag() - 2.0 ) < 1E-6 ); + } - multi::array arr2 = arr.reinterpret_array_cast(); - BOOST_REQUIRE( dimensionality(arr2) == dimensionality(arr) ); - BOOST_REQUIRE( arr2[0] == 1 and arr2[1] == 1 ); + BOOST_TEST( std::abs( arr[0].real() - 1.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr[0].imag() - 2.0 ) < 1E-6 ); - multi::array arr3 = arr.reinterpret_array_cast(2); + BOOST_TEST( std::abs( std::real(arr[0]) - 1.0 ) < 1E-6 ); + BOOST_TEST( std::abs( std::imag(arr[0]) - 2.0 ) < 1E-6 ); - BOOST_REQUIRE(( sizes(arr3)==decltype(sizes(arr3)){100, 2} )); - BOOST_REQUIRE( arr3[5][0] == real(arr[5]) ); - BOOST_REQUIRE( arr3[5][1] == imag(arr[5]) ); -} + BOOST_TEST( std::abs( real(arr[0]) - 1.0 ) < 1E-6 ); + BOOST_TEST( std::abs( imag(arr[0]) - 2.0 ) < 1E-6 ); + + BOOST_TEST( std::abs( arr[0] - complex{1.0, 2.0} ) < 1E-6 ); + + multi::array arr2{arr.reinterpret_array_cast()}; + BOOST_TEST( dimensionality(arr2) == dimensionality(arr) ); + BOOST_TEST( std::abs( arr2[0] - 1.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr2[1] - 1.0 ) < 1E-6 ); + + multi::array arr3{arr.reinterpret_array_cast(2)}; + + BOOST_TEST(( arr3.sizes() == decltype(arr3.sizes()){100, 2} )); + BOOST_TEST( std::abs( arr3[5][0] - real(arr[5]) ) < 1E-6 ); + BOOST_TEST( std::abs( arr3[5][1] - imag(arr[5]) ) < 1E-6 ); + + arr.reinterpret_array_cast(2)[0][0] = 99.9; + BOOST_TEST( std::abs( arr.reinterpret_array_cast(2)[0][0] - 99.9) < 1E-6 ); -BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_tuple_as_extra_dimension) { - using vector3 = std::array; -// using vector3 = std::tuple; // for tuples reinterpret_array_cast is implementation dependent!! + arr().reinterpret_array_cast(2)[0][0] = 99.9; + BOOST_TEST( std::abs( arr().reinterpret_array_cast(2)[0][0] - 99.9) < 1E-6 ); + } - vector3 v3d; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast, cppcoreguidelines-avoid-c-arrays, hicpp-avoid-c-arrays, modernize-avoid-c-arrays): test - BOOST_REQUIRE( &reinterpret_cast(v3d)[1] == &std::get<1>(v3d) ); + // BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_tuple_as_extra_dimension) { - multi::array arr(multi::extensions_t<1>{multi::iextension{10}}); - BOOST_REQUIRE( &arr.reinterpret_array_cast(3)[2][1] == &std::get<1>(arr[2]) ); + using vector3 = std::array; + + vector3 v3d; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast, cppcoreguidelines-avoid-c-arrays, hicpp-avoid-c-arrays, modernize-avoid-c-arrays): test + BOOST_TEST( &reinterpret_cast(v3d)[1] == &std::get<1>(v3d) ); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + { + multi::array arr(multi::extensions_t<1>{multi::iextension{10}}); + BOOST_TEST( &arr.reinterpret_array_cast(3)[2][1] == &std::get<1>(arr[2]) ); + } + { + multi::array arr({10, 20}); + BOOST_TEST( &arr.reinterpret_array_cast(3)[5][7][2] == &std::get<2>(arr[5][7]) ); + } + { + multi::array const arr({ + 4, 5 + }, + vector3{{1.0, 2.0, 3.0}}); + + BOOST_TEST( arr.reinterpret_array_cast(3).dimensionality == 3 ); + BOOST_TEST( decltype(arr.reinterpret_array_cast(3))::dimensionality == 3 ); + BOOST_TEST( dimensionality(arr.reinterpret_array_cast(3)) == 3 ); + + BOOST_TEST( arr.reinterpret_array_cast(3).num_elements() == arr.num_elements()*3 ); + BOOST_TEST( arr.reinterpret_array_cast(3).size() == 4 ); + BOOST_TEST( arr.reinterpret_array_cast(3)[0].size() == 5 ); + BOOST_TEST( arr.reinterpret_array_cast(3)[0][0].size() == 3 ); + BOOST_TEST( &arr.reinterpret_array_cast(3)[2][3][0] == &std::get<0>(arr[2][3]) ); + BOOST_TEST( &arr.reinterpret_array_cast(3)[2][3][1] == &std::get<1>(arr[2][3]) ); + BOOST_TEST( &arr.reinterpret_array_cast(3)[2][3][2] == &std::get<2>(arr[2][3]) ); + + multi::array const arr2{arr.reinterpret_array_cast(3)}; + BOOST_TEST( std::abs( arr2[2][3][0] - std::get<0>(arr[2][3]) ) < 1E-6 ); + BOOST_TEST( std::abs( arr2[2][3][1] - std::get<1>(arr[2][3]) ) < 1E-6 ); + BOOST_TEST( std::abs( arr2[2][3][2] - std::get<2>(arr[2][3]) ) < 1E-6 ); + + auto arr3 = +arr.reinterpret_array_cast(3); + BOOST_TEST( arr3 == arr2 ); + } } + + // BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast) { - multi::array arr({10, 20}); - BOOST_REQUIRE( &arr.reinterpret_array_cast(3)[5][7][2] == &std::get<2>(arr[5][7]) ); + std::complex cee{1.0, 2.0}; + auto* ptr = reinterpret_cast*>(&cee); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + ptr->real = 11.0; + BOOST_TEST( std::abs( real(cee) - 11.0 ) < 1E-6 ); + + { + multi::array, 1> arr(multi::extensions_t<1>{multi::iextension{10}}); + std::iota(begin(arr), end(arr), 1.0); + BOOST_TEST( std::abs( real(arr[8]) - 9.0 ) < 1E-6); + auto&& arr2 = arr.reinterpret_array_cast>(); + arr2[8].real = 1000.0; + BOOST_TEST( std::abs( real(arr[8]) - 1000.0 ) < 1E-6 ); + } } + + // BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_realcomplex) { - multi::array const arr({4, 5}, vector3{{1.0, 2.0, 3.0}} ); - - BOOST_REQUIRE( arr.reinterpret_array_cast(3).dimensionality == 3 ); - BOOST_REQUIRE( decltype(arr.reinterpret_array_cast(3))::dimensionality == 3 ); - BOOST_REQUIRE( dimensionality(arr.reinterpret_array_cast(3)) == 3 ); - - BOOST_REQUIRE( arr.reinterpret_array_cast(3).num_elements() == arr.num_elements()*3 ); - BOOST_REQUIRE( arr.reinterpret_array_cast(3).size() == 4 ); - BOOST_REQUIRE( arr.reinterpret_array_cast(3)[0].size() == 5 ); - BOOST_REQUIRE( arr.reinterpret_array_cast(3)[0][0].size() == 3 ); - BOOST_REQUIRE( &arr.reinterpret_array_cast(3)[2][3][0] == &std::get<0>(arr[2][3]) ); - BOOST_REQUIRE( &arr.reinterpret_array_cast(3)[2][3][1] == &std::get<1>(arr[2][3]) ); - BOOST_REQUIRE( &arr.reinterpret_array_cast(3)[2][3][2] == &std::get<2>(arr[2][3]) ); - - multi::array const arr2 = arr.reinterpret_array_cast(3); - BOOST_REQUIRE( arr2[2][3][0] == std::get<0>(arr[2][3]) ); - BOOST_REQUIRE( arr2[2][3][1] == std::get<1>(arr[2][3]) ); - BOOST_REQUIRE( arr2[2][3][2] == std::get<2>(arr[2][3]) ); - - auto arr3 = +arr.reinterpret_array_cast(3); - BOOST_REQUIRE( arr3 == arr2 ); + using complex = std::complex; + { + complex cee{1.0, 2.0}; + auto* conjd_cee = reinterpret_cast*>(&cee); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + (*conjd_cee)[0] = 11.0; + BOOST_TEST( conjd_cee ); + BOOST_TEST( std::abs( real(cee) - 11.0 ) < 1E-6 ); + } + { + complex cee{1.0, 2.0}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast, cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): test purposes + auto* ceePC = reinterpret_cast(&cee); + (*ceePC)[0] = 11.0; + BOOST_TEST( ceePC ); + BOOST_TEST( std::abs( real(cee) - 11.0 ) < 1E-6 ); + } + { + multi::array arr(multi::extensions_t<1>{multi::iextension{10}}); + + auto&& arr2 = arr.reinterpret_array_cast(2); + + arr2[8][0] = 1000.0; + arr2[8][1] = 2000.0; + + BOOST_TEST(( arr[8] == std::complex{1000.0, 2000.0} )); + } } -} -template struct complex_dummy{T real; T imag;}; + // BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_pair_to_complex) + { + using complex = std::complex; + using pair = std::pair; + multi::array arr({10, 10}, complex{3.0, 4.0}); -BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast) { -{ - std::complex cee{1, 2}; - auto *ptr = reinterpret_cast*>(&cee); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - ptr->real = 11; - BOOST_REQUIRE(real(cee)==11); -} -{ - multi::array, 1> arr(multi::extensions_t<1>{multi::iextension{10}}); - std::iota( begin(arr), end(arr), 1.0); - BOOST_REQUIRE( arr[8] == 9.0 ); - auto&& arr2 = arr.reinterpret_array_cast>(); - arr2[8].real = 1000.0; - BOOST_REQUIRE( arr[8] == 1000.0 ); -} -} + multi::array const& Aconst = arr; + auto&& A_block = Aconst({0, 5}, {0, 5}); -BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_realcomplex) { - using complex = std::complex; -{ - complex cee{1, 2}; - auto *conjd_cee = reinterpret_cast*>(&cee); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - (*conjd_cee)[0] = 11; - BOOST_REQUIRE( conjd_cee ); - BOOST_REQUIRE(real(cee)==11); -} -{ - complex cee{1, 2}; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast, cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): test purposes - auto *ceePC = reinterpret_cast(&cee); - (*ceePC)[0] = 11; - BOOST_REQUIRE( ceePC ); - BOOST_REQUIRE(real(cee)==11); -} -{ - multi::array arr(multi::extensions_t<1>{multi::iextension{10}}); - auto&& arr2 = arr.reinterpret_array_cast(2); - arr2[8][0] = 1000.0; - arr2[8][1] = 2000.0; - BOOST_REQUIRE(( arr[8] == std::complex{1000.0, 2000.0} )); -} -} + // const is important below + auto const& Apair_block = A_block.template reinterpret_array_cast(); // cppcheck 1.90 needs `template` to avoid internal bug -BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_pair_to_complex) { - using complex = std::complex; - using pair = std::pair; - multi::array arr({10, 10}, complex{3.0, 4.0}); + BOOST_TEST( &Apair_block[1][2] == static_cast(&arr[1][2]) ); - multi::array const& Aconst = arr; - auto&& A_block = Aconst({0, 5}, {0, 5}); + auto&& Adoubles_block = A_block.reinterpret_array_cast(2); + BOOST_TEST( &Adoubles_block[1][2][0] == static_cast(&arr[1][2]) ); + } - auto const& Apair_block = A_block.template reinterpret_array_cast(); // const is important // cppcheck 1.90 needs `template` to avoid internal bug - BOOST_REQUIRE( &Apair_block[1][2] == static_cast(&arr[1][2]) ); + // BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_pointer) + { + multi::array arr({10, 10}, 5.0); - auto&& Adoubles_block = A_block.reinterpret_array_cast(2); - BOOST_REQUIRE( &Adoubles_block[1][2][0] == static_cast(&arr[1][2]) ); -} + auto&& Aconstcast = arr.reinterpret_array_cast(); + BOOST_TEST( &arr[0][0] == &Aconstcast[0][0] ); + static_assert(std::is_same_v); + } + + // BOOST_AUTO_TEST_CASE(const_array_cast) + { + multi::array arr({10, 10}, 5.0); // NOLINT(misc-const-correctness) test const cast + + multi::array const& carr = arr; -BOOST_AUTO_TEST_CASE(multi_reinterpret_array_cast_pointer) { - multi::array arr({10, 10}, 5.); + auto&& marr = carr.const_array_cast(); + + marr[1][1] = 6.0; + + BOOST_TEST( std::abs( carr[1][1] - 6.0 ) < 1E-6 ); + } + +// test packing 4 doubles +#ifndef _MSC_VER + { + using packed_type = std::array; + multi::array arr_4pc({10, 25}); + + BOOST_TEST( arr_4pc[0].size() == 25 ); + + arr_4pc[0][0] = packed_type{ + {1.0, 2.0, 3.0, 4.0}, + }; + arr_4pc[0][1] = packed_type{ + {5.0, 6.0, 7.0, 8.0}, + }; + + auto&& arr = arr_4pc.reinterpret_array_cast(4).rotated().flatted().unrotated(); + + BOOST_TEST( arr[0].size() == 100 ); + + BOOST_TEST( std::abs( arr[0][0] - 1.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr[0][1] - 2.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr[0][2] - 3.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr[0][3] - 4.0 ) < 1E-6 ); + + BOOST_TEST( std::abs( arr[0][4] - 5.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr[0][5] - 6.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr[0][6] - 7.0 ) < 1E-6 ); + BOOST_TEST( std::abs( arr[0][7] - 8.0 ) < 1E-6 ); + } +#endif - auto&& Aconstcast = arr.reinterpret_array_cast(); - BOOST_REQUIRE( &arr[0][0] == &Aconstcast[0][0] ); - static_assert( std::is_same{}, "!" ); + return boost::report_errors(); } diff --git a/test/restrictions.cpp b/test/restrictions.cpp new file mode 100644 index 000000000..fa2c13d38 --- /dev/null +++ b/test/restrictions.cpp @@ -0,0 +1,123 @@ +// Copyright 2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// vvv this has no effect, needs to be passed directly from compilation line "-Wno-psabi" +// #ifdef __GNUC__ +// #pragma GCC diagnostic ignored "-Wpsabi" // for ranges backwards compatibility message +// #endif + +#include // IWYU pragma: keep + +#if __cplusplus >= 202302L + +#include // IWYU pragma: keep // for std::equal +#include // for std::abs +#include // IWYU pragma: keep +#include // for std::get // NOLINT(misc-include-cleaner) + +#if defined(__cplusplus) && (__cplusplus >= 202002L) +#include // for constructible_from // NOLINT(misc-include-cleaner) // IWYU pragma: keep +#include // for std::plus // NOLINT(misc-include-cleaner) // IWYU pragma: keep +#include // for std::cout // NOLINT(misc-include-cleaner) +#include // for std::numeric_limits // NOLINT(misc-include-cleaner) // IWYU pragma: keep +#include // IWYU pragma: keep +#endif + +#include + +namespace stdr = std::ranges; +namespace stdv = std::views; + +auto printR2(auto const& lbl, auto const& arr2D) { + // return fmt::print("{} = \n[{}]\n\n", lbl, fmt::join(arr2D, ",\n ")); + std::cout << lbl << " = \n"; + for(auto const& row : arr2D) { + for(auto const& elem : row) + std::cout << elem << ", "; + std::cout << '\n'; + } + std::cout << '\n'; +} + +constexpr auto maxR1 = []>(R const& row, V low = std::numeric_limits::lowest()) { + return stdr::fold_left(row, low, stdr::max); +}; + +constexpr auto sumR1 = []>(R const& rng, V zero = {}) { + return stdr::fold_left(rng, zero, std::plus<>{}); +}; + +#define FWD(var) std::forward(var) + +auto softmax(auto&& matrix) noexcept { + return // + FWD(matrix) // + | + stdv::transform([](auto&& row) { + auto max = maxR1(row); + return // + FWD(row) // + | + stdv::transform([=](auto ele) noexcept { return std::exp(ele - max); }); + }) // + | + stdv::transform([](auto&& nums) { + auto den = sumR1(nums); + return // + FWD(nums) // + | + stdv::transform([=](auto num) noexcept { return num / den; }); + }); +} + +namespace multi = boost::multi; + +namespace lazy { + +template +auto operator*(typename A::element scalar, A const& a) { + return [scalar, &a](auto... is) { return scalar * a[is...]; } ^ a.extensions(); +} + +namespace elementwise { + +template +auto operator*(A const& a, B const& b) requires(A::dimensionality == B::dimensionality) { + return [&a, &b](auto... is) { return a[is...] * b[is...]; } ^ a.extensions(); +} + +template +auto operator+(A const& a, B const& b) requires(A::dimensionality == B::dimensionality) { + return [&a, &b](auto... is) { return a[is...] + b[is...]; } ^ a.extensions(); +} + +} // namespace elementwise +} // namespace lazy + +int main() { + auto const A = multi::array{ + {0, 1, 2}, + {3, 4, 5} + }; + auto const B = multi::array{ + { 0, 10, 20}, + {30, 40, 50} + }; + + using lazy::operator*; + using lazy::elementwise::operator+; + using lazy::elementwise::operator*; + + multi::array const C = A + (A * B) + (2.0 * B); + + std::cout << "C11 = " << C[1][1] << std::endl; + BOOST_TEST( C[1][1] == 4 + 4*40 + 2*40 ); + + return boost::report_errors(); +} +#else +auto main() -> int { + return boost::report_errors(); +} +#endif diff --git a/test/reversed.cpp b/test/reversed.cpp index 2febee789..8ece50b39 100644 --- a/test/reversed.cpp +++ b/test/reversed.cpp @@ -1,63 +1,76 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2022 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi reversed" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include +#include -#include +#include -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_reversed_3d) { - multi::array arr({30, 40, 50}); - - BOOST_TEST_REQUIRE( arr.reversed().size() == 50 ); +#include // for get // NOLINT(misc-include-cleaner) +#include // for forward - BOOST_REQUIRE( & arr.reversed()[3][5][7] == &arr[7][5][3] ); -} +namespace multi = boost::multi; +namespace { template auto flatted_last(Array&& arr) { - return reversed(flatted(transposed(reversed(std::forward(arr))))); + return std::forward(arr).reversed().transposed().flatted().reversed(); } template auto partitioned_last(Array&& arr, multi::size_type n) { - return reversed(transposed(partitioned(reversed(std::forward(arr)), n))); + return std::forward(arr).reversed().partitioned(n).transposed().transposed().reversed(); } +} // end unnamed namespace -BOOST_AUTO_TEST_CASE(multi_reversed_4d) { - multi::array arr({13, 5, 7, 11}); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_reversed_3d) + { + multi::array arr({30, 40, 50}); - BOOST_TEST_REQUIRE( arr.reversed().size() == 11 ); + BOOST_TEST( arr.reversed().size() == 50 ); - BOOST_REQUIRE( &arr.reversed()[1][2][3][4] == &arr[4][3][2][1] ); + BOOST_TEST( & arr.reversed()[3][5][7] == &arr[7][5][3] ); + } + // BOOST_AUTO_TEST_CASE(multi_reversed_4d) + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] - BOOST_REQUIRE( std::get<0>( arr.reversed().transposed().flatted().reversed().sizes() ) == 13 ); - BOOST_REQUIRE( std::get<1>( arr.reversed().transposed().flatted().reversed().sizes() ) == 5 ); - BOOST_REQUIRE( std::get<2>( arr.reversed().transposed().flatted().reversed().sizes() ) == 77 ); + multi::array arr({13, 5, 7, 11}); - BOOST_REQUIRE(( sizes(arr.reversed().transposed().flatted().reversed()) == decltype(sizes(arr.reversed().transposed().flatted().reversed())){13, 5, 77} )); + BOOST_TEST( arr.reversed().size() == 11 ); - BOOST_REQUIRE( &arr.reversed().transposed().flatted().reversed()[1][2][ 5] == & arr[1][2][0][ 5] ); - BOOST_REQUIRE( &arr.reversed().transposed().flatted().reversed()[1][2][10] == & arr[1][2][0][10] ); - BOOST_REQUIRE( &arr.reversed().transposed().flatted().reversed()[1][2][11] == & arr[1][2][1][ 0] ); - BOOST_REQUIRE( &arr.reversed().transposed().flatted().reversed()[1][2][12] == & arr[1][2][1][ 1] ); + BOOST_TEST( &arr.reversed()[1][2][3][4] == &arr[4][3][2][1] ); - BOOST_REQUIRE( & flatted_last(arr)[1][2][12] == & arr[1][2][1][1] ); -} + BOOST_TEST( get<0>( arr.reversed().transposed().flatted().reversed().sizes() ) == 13 ); + BOOST_TEST( get<1>( arr.reversed().transposed().flatted().reversed().sizes() ) == 5 ); + BOOST_TEST( get<2>( arr.reversed().transposed().flatted().reversed().sizes() ) == 77 ); + + BOOST_TEST(( sizes(arr.reversed().transposed().flatted().reversed()) == decltype(sizes(arr.reversed().transposed().flatted().reversed())){13, 5, 77} )); + BOOST_TEST( &arr.reversed().transposed().flatted().reversed()[1][2][ 5] == & arr[1][2][0][ 5] ); + BOOST_TEST( &arr.reversed().transposed().flatted().reversed()[1][2][10] == & arr[1][2][0][10] ); + BOOST_TEST( &arr.reversed().transposed().flatted().reversed()[1][2][11] == & arr[1][2][1][ 0] ); + BOOST_TEST( &arr.reversed().transposed().flatted().reversed()[1][2][12] == & arr[1][2][1][ 1] ); + + BOOST_TEST( & flatted_last(arr)[1][2][12] == & arr[1][2][1][1] ); + } + + // BOOST_AUTO_TEST_CASE(multi_reversed_4d_partition_last) + { + multi::array arr({11, 5, 7, 12}); -BOOST_AUTO_TEST_CASE(multi_reversed_4d_partition_last) { - multi::array arr({11, 5, 7, 12}); + BOOST_TEST( arr.reversed().size() == 12 ); - BOOST_REQUIRE( arr.reversed().size() == 12 ); + BOOST_TEST( & arr.reversed()[1][2][3][4] == &arr[4][3][2][1] ); - BOOST_REQUIRE( & arr.reversed()[1][2][3][4] == &arr[4][3][2][1] ); + BOOST_TEST( & arr.reversed().partitioned(3).transposed().reversed()[1][2][3][0][1] == & arr[1][2][3][1] ); + BOOST_TEST( & arr.reversed().partitioned(3).transposed().reversed()[1][2][3][1][0] == & arr[1][2][3][4] ); + BOOST_TEST( & arr.reversed().partitioned(3).transposed().reversed()[1][2][3][1][1] == & arr[1][2][3][5] ); - BOOST_REQUIRE( & arr.reversed().partitioned(3).transposed().reversed()[1][2][3][0][1] == & arr[1][2][3][1] ); - BOOST_REQUIRE( & arr.reversed().partitioned(3).transposed().reversed()[1][2][3][1][0] == & arr[1][2][3][4] ); - BOOST_REQUIRE( & arr.reversed().partitioned(3).transposed().reversed()[1][2][3][1][1] == & arr[1][2][3][5] ); + BOOST_TEST( & partitioned_last(arr, 3)[1][2][3][1][1] == & arr[1][2][3][5] ); + } - BOOST_REQUIRE( & partitioned_last(arr, 3)[1][2][3][1][1] == & arr[1][2][3][5] ); + return boost::report_errors(); } diff --git a/test/rotated.cpp b/test/rotated.cpp index e6a57d539..89ad7abe3 100644 --- a/test/rotated.cpp +++ b/test/rotated.cpp @@ -1,141 +1,364 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4-*- -// Copyright 2021-2023 Alfredo A. Correa +// Copyright 2021-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi rotate" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array, layout_t, subarray, sizes -#include +#include -#include // for std::iota +#include // for array +#include // for iota + +#if defined(__cplusplus) && (__cplusplus >= 202002L) && __has_include() +#if !defined(__clang_major__) || (__clang_major__ != 16) +#include // IWYU pragma: keep +#endif +#endif + +#include // for get // NOLINT(misc-include-cleaner) +#include // for is_assignable_v namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_rotate_3d) { - multi::array arr({3, 4, 5}); +#if defined(__cplusplus) && (__cplusplus >= 202002L) +#if !defined(__clang_major__) || (__clang_major__ != 16) +#if defined(__cpp_lib_ranges_repeat) && (__cpp_lib_ranges_repeat >= 202207L) - BOOST_REQUIRE( std::get<0>(sizes(arr)) == 3 ); - BOOST_REQUIRE( std::get<1>(sizes(arr)) == 4 ); - BOOST_REQUIRE( std::get<2>(sizes(arr)) == 5 ); +template +auto meshgrid(X1D const& x, Y1D const& y) { + return std::pair{x.broadcasted().rotated(), y.broadcasted()}; +} - auto&& RA = rotated(arr); - BOOST_REQUIRE(( sizes(RA) == decltype(sizes(RA)){4, 5, 3} )); - BOOST_REQUIRE( &arr[0][1][2] == &RA[1][2][0] ); +template +auto meshgrid_copy(X1D const& x, Y1D const& y) { + auto ret = std::pair{ + multi::array({x.size(), y.size()}), + multi::array(std::ranges::views::repeat(y, x.size())) + }; - auto&& UA = unrotated(arr); - BOOST_REQUIRE(( sizes(UA) == decltype(sizes(UA)){5, 3, 4} )); - BOOST_REQUIRE( &arr[0][1][2] == &UA[2][0][1] ); + std::fill(ret.first.rotated().begin(), ret.first.rotated().end(), x); + // std::ranges::fill(ret.first.rotated(), x); - auto&& RRA = rotated(RA); - BOOST_REQUIRE(( sizes(RRA) == decltype(sizes(RRA)){5, 3, 4} )); - BOOST_REQUIRE( &arr[0][1][2] == &RRA[2][0][1] ); + return ret; } +#endif +#endif +#endif -BOOST_AUTO_TEST_CASE(multi_rotate_4d) { - multi::array original({14, 14, 7, 4}); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(constexpr_carray_rotated_end) + { + constexpr auto test = [] { + std::array + buffer = { + {0, 1, 2, 3, 4, 5, 6, 7, 8} + }; // , 10, 11}; - auto&& unrotd = original.unrotated(); - BOOST_REQUIRE(( sizes(unrotd) == decltype(sizes(unrotd)){4, 14, 14, 7} )); - BOOST_REQUIRE( &original[0][1][2][3] == &unrotd[3][0][1][2] ); + multi::array_ref arr({3, 3}, buffer.data()); // // TODO(correaa) think how to handle references to arrays (UB) - auto&& unrotd2 = original.unrotated().unrotated(); - BOOST_REQUIRE(( sizes(unrotd2) == decltype(sizes(unrotd2)){7, 4, 14, 14} )); - BOOST_REQUIRE( &original[0][1][2][3] == &unrotd2[2][3][0][1] ); -} + auto const& arrrot1 = arr.rotated()[1]; + return (arrrot1.end() != arrrot1.begin()); + }(); + BOOST_TEST(test); + } -BOOST_AUTO_TEST_CASE(multi_rotate_4d_op) { - multi::array original({14, 14, 7, 4}); + // BOOST_AUTO_TEST_CASE(constexpr_carray_rotated_end_interval) + { + constexpr auto test = [] { + std::array + buffer = { + {0, 1, 2, 3, 4, 5, 6, 7, 8} + }; // , 10, 11}; - auto&& unrotd = (original.unrotated() ); - BOOST_REQUIRE(( sizes(unrotd) == decltype(sizes(unrotd)){4, 14, 14, 7} )); - BOOST_REQUIRE( &original[0][1][2][3] == &unrotd[3][0][1][2] ); + multi::array_ref arr({3, 3}, buffer.data()); // // TODO(correaa) think how to handle references to arrays (UB) - auto&& unrotd2 = (original.unrotated().unrotated() ); - BOOST_REQUIRE(( sizes(unrotd2) == decltype(sizes(unrotd2)){7, 4, 14, 14} )); - BOOST_REQUIRE( &original[0][1][2][3] == &unrotd2[2][3][0][1] ); -} + return arr.rotated()[1]({3, 3}).size() == 0; + }(); + BOOST_TEST(test); + } -BOOST_AUTO_TEST_CASE(multi_rotate_part1) { - std::array, 4> stdarr = {{ - {{ 0., 1., 2., 3., 4.}}, - {{ 5., 6., 7., 8., 9.}}, - {{10., 11., 12., 13., 14.}}, - {{15., 16., 17., 18., 19.}} - }}; - std::array, 4> stdarr2 = {}; - - multi::array_ref arr (&stdarr [0][0], {4, 5}); // NOLINT(readability-container-data-pointer) test access - multi::array_ref arr2(&stdarr2[0][0], {4, 5}); // NOLINT(readability-container-data-pointer) test access - - rotated(arr2) = rotated(arr); - BOOST_REQUIRE( arr2[1][1] == 6 ); - BOOST_REQUIRE( arr2[2][1] == 11 ); - BOOST_REQUIRE( arr2[1][2] == 7 ); - BOOST_REQUIRE( (arr2.rotated() ) == (arr.rotated() ) ); - BOOST_REQUIRE( (arr2.rotated() )[2][1] == 7 ); -} + // BOOST_AUTO_TEST_CASE(constexpr_carray_diagonal_end_2D) + { + constexpr auto test = [] { + std::array + buffer = {}; -BOOST_AUTO_TEST_CASE(multi_rotate) { -{ - multi::array arr = { - {00.0, 01.0}, - {10.0, 11.0} - }; - BOOST_REQUIRE( arr[1][0] == 10 ); - BOOST_REQUIRE( (arr.rotated())[0][1] == 10 ); - BOOST_REQUIRE( & arr[1][0] == &(arr.rotated() )[0][1] ); + multi::array_ref arr({3, 4}, buffer.data()); // // TODO(correaa) think how to handle references to arrays (UB) - BOOST_REQUIRE( arr.transposed()[0][1] == 10 ); - BOOST_REQUIRE( transposed(arr)[0][1] == 10 ); - BOOST_REQUIRE( (~arr)[0][1] == 10 ); - BOOST_REQUIRE( &arr[1][0] == &arr.transposed()[0][1] ); + return arr.diagonal().end() != arr.diagonal().begin(); + }(); + BOOST_TEST(test); + } - (arr.rotated())[0][1] = 100; - BOOST_REQUIRE( arr[1][0] == 100 ); -} -{ - multi::array arr({11, 13, 17}); - BOOST_REQUIRE( & arr[3][5][7] == & arr.transposed()[5][3][7] ); - BOOST_REQUIRE( & arr[3][5][7] == & transposed(arr) [5][3][7] ); - BOOST_REQUIRE( & arr[3][5][7] == & (~arr) [5][3][7] ); - BOOST_REQUIRE( & arr[3][5][7] == & arr[3].transposed()[7][5] ); - BOOST_REQUIRE( & arr[3][5][7] == & (~arr[3]) [7][5] ); - - BOOST_REQUIRE( & arr[3][5] == & (~arr)[5][3] ); - - BOOST_REQUIRE( & ~~arr == & arr ); - BOOST_REQUIRE( & (arr.rotated().rotated().rotated() ) == & arr ); - BOOST_REQUIRE( & arr() == & (arr.rotated().rotated().rotated() ) ); - BOOST_REQUIRE( & (arr.rotated() ) != & arr ); - BOOST_REQUIRE( & (arr.unrotated().rotated()) == & arr ); - - std::iota(arr.data_elements(), arr.data_elements() + arr.num_elements(), 0.1); - BOOST_REQUIRE( ~~arr == arr ); - BOOST_REQUIRE( arr.unrotated().rotated() == arr ); -} -{ - multi::array const arr = { - {00, 01}, - {10, 11} - }; - BOOST_REQUIRE( arr.rotated() [0][1] == 10 ); - BOOST_REQUIRE( &(arr.rotated())[1][0] == &arr[0][1] ); - BOOST_REQUIRE( &(~arr)[1][0] == &arr[0][1] ); -} -} + // BOOST_AUTO_TEST_CASE(constexpr_carray_rotated_end_3D) + { + constexpr auto test = [] { + std::array + buffer = {}; -BOOST_AUTO_TEST_CASE(multi_transposed) { - multi::array const arr0 = { - { 9., 24., 30., 9.}, - { 4., 10., 12., 7.}, - {14., 16., 36., 1.} - }; - multi::array const arr1 = arr0.transposed(); - multi::array const arr2 = ~arr0; - BOOST_REQUIRE( arr1 == arr2 ); -} + multi::array_ref arr({3, 4, 5}, buffer.data()); // // TODO(correaa) think how to handle references to arrays (UB) + + return arr.diagonal().diagonal().end() != arr.diagonal().diagonal().begin(); + }(); + BOOST_TEST(test); + } + +#if __cplusplus >= 202002L +#if defined(__GNUC__) && !defined(__clang__) && !defined(__NVCOMPILER) && !defined(__NVCC__) + // BOOST_AUTO_TEST_CASE(constexpr_dynamic_array_rotated_end) + { + constexpr auto test = [] { + multi::array arr({3, 3}, 99); + return arr.rotated()[1].end() != arr.rotated()[1].begin(); + }(); + BOOST_TEST(test); + } +#endif +#endif + + // BOOST_AUTO_TEST_CASE(multi_2d_const) + { + multi::array const arr = { + {10, 20}, + {30, 40}, + }; + + BOOST_TEST( arr.rotated()[1][1] == 40 ); + static_assert(!std::is_assignable_v); + } + + // BOOST_AUTO_TEST_CASE(multi_2d) + { + multi::array arr = { + {10, 20}, + {30, 40}, + }; + + BOOST_TEST( arr.rotated()[1][1] == 40 ); + + // what(arr.rotated()[0][0]); + + static_assert(std::is_assignable_v); + + // arr.rotated()[1][1] = 50; + } + + // BOOST_AUTO_TEST_CASE(multi_rotate_3d) + { + multi::array arr({3, 4, 5}); + + using std::get; + + BOOST_TEST( get<0>(arr.sizes()) == 3 ); + BOOST_TEST( get<1>(arr.sizes()) == 4 ); + BOOST_TEST( get<2>(arr.sizes()) == 5 ); + + auto&& RA = arr.rotated(); + BOOST_TEST(( sizes(RA) == decltype(RA.sizes()){4, 5, 3} )); + BOOST_TEST( &arr[0][1][2] == &RA[1][2][0] ); + + auto&& UA = arr.unrotated(); + BOOST_TEST(( sizes(UA) == decltype(sizes(UA)){5, 3, 4} )); + BOOST_TEST( &arr[0][1][2] == &UA[2][0][1] ); + + auto&& RRA = RA.rotated(); + BOOST_TEST(( sizes(RRA) == decltype(sizes(RRA)){5, 3, 4} )); + BOOST_TEST( &arr[0][1][2] == &RRA[2][0][1] ); + } + + // BOOST_AUTO_TEST_CASE(multi_rotate_4d) + { + using std::get; // workaround no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions] + + multi::array original({14, 14, 7, 4}); + + auto&& unrotd = original.unrotated(); + BOOST_TEST( get<0>(unrotd.sizes()) == 4 ); + BOOST_TEST( get<1>(unrotd.sizes()) == 14 ); + BOOST_TEST( get<2>(unrotd.sizes()) == 14 ); + BOOST_TEST( get<3>(unrotd.sizes()) == 7 ); + + BOOST_TEST(( unrotd.sizes() == decltype(unrotd.sizes()){4, 14, 14, 7} )); + BOOST_TEST( &original[0][1][2][3] == &unrotd[3][0][1][2] ); + + auto&& unrotd2 = original.unrotated().unrotated(); + BOOST_TEST(( sizes(unrotd2) == decltype(sizes(unrotd2)){7, 4, 14, 14} )); + BOOST_TEST( &original[0][1][2][3] == &unrotd2[2][3][0][1] ); + } + + // BOOST_AUTO_TEST_CASE(multi_rotate_4d_op) + { + multi::array original({14, 14, 7, 4}); + + auto&& unrotd = (original.unrotated()); + BOOST_TEST(( sizes(unrotd) == decltype(sizes(unrotd)){4, 14, 14, 7} )); + BOOST_TEST( &original[0][1][2][3] == &unrotd[3][0][1][2] ); + + auto&& unrotd2 = (original.unrotated().unrotated()); + BOOST_TEST(( sizes(unrotd2) == decltype(sizes(unrotd2)){7, 4, 14, 14} )); + BOOST_TEST( &original[0][1][2][3] == &unrotd2[2][3][0][1] ); + } + + // BOOST_AUTO_TEST_CASE(multi_rotate_part1) + { + // clang-format off + std::array, 4> stdarr = {{ + {{ 0, 1, 2, 3, 4}}, + {{ 5, 6, 7, 8, 9}}, + {{10, 11, 12, 13, 14}}, + {{15, 16, 17, 18, 19}}, + }}; + // clang-format on + + std::array, 4> stdarr2 = {}; + + multi::array_ref arr(&stdarr[0][0], {4, 5}); // NOLINT(readability-container-data-pointer) test access + multi::array_ref arr2(&stdarr2[0][0], {4, 5}); // NOLINT(readability-container-data-pointer) test access + + arr2.rotated() = arr.rotated(); + + BOOST_TEST( arr2[1][1] == 6 ); + BOOST_TEST( arr2[2][1] == 11 ); + BOOST_TEST( arr2[1][2] == 7 ); + + BOOST_TEST( arr2.rotated() == arr.rotated() ); + BOOST_TEST( arr2.rotated()[2][1] == 7 ); + } + + // BOOST_AUTO_TEST_CASE(multi_rotate) + { + multi::array arr = { + {00, 01}, + {10, 11}, + }; + BOOST_TEST( arr[1][0] == 10 ); + BOOST_TEST( (arr.rotated())[0][1] == 10 ); + BOOST_TEST( & arr[1][0] == &(arr.rotated() )[0][1] ); + + BOOST_TEST( arr.transposed()[0][1] == 10 ); + BOOST_TEST( arr.transposed()[0][1] == 10 ); + BOOST_TEST( (~arr)[0][1] == 10 ); + BOOST_TEST( &arr[1][0] == &arr.transposed()[0][1] ); + + (arr.rotated())[0][1] = 100; + BOOST_TEST( arr[1][0] == 100 ); + } + { + multi::array arr({11, 13, 17}); + BOOST_TEST( & arr[3][5][7] == & arr .transposed()[5][3][7] ); + BOOST_TEST( & arr[3][5][7] == & arr .transposed()[5][3][7] ); + BOOST_TEST( & arr[3][5][7] == & (~arr) [5][3][7] ); + BOOST_TEST( & arr[3][5][7] == & arr[3].transposed()[7][5] ); + BOOST_TEST( & arr[3][5][7] == & (~arr[3]) [7][5] ); + + // BOOST_TEST( & ~~arr == & arr ); // TODO(correaa) make it work + // BOOST_TEST( & (arr.rotated().rotated().rotated() ) == & arr ); // TODO(correaa) make it work + BOOST_TEST( & arr() == & (arr.rotated().rotated().rotated() ) ); + // BOOST_TEST( & (arr.rotated() ) != & arr ); // TODO(correaa) make it work + BOOST_TEST( & (arr.unrotated().rotated()) == & arr() ); + // BOOST_TEST( & (arr.unrotated().rotated()) == & arr ); // TODO(correaa) make it work + + BOOST_TEST( & arr[3][5] == & (~arr)[5][3] ); + + std::iota(arr.elements().begin(), arr.elements().end(), 11); + BOOST_TEST( ~~arr == arr ); + BOOST_TEST( arr.unrotated().rotated() == arr ); + } + { + multi::array const arr = { + {00, 01}, + {10, 11}, + }; + BOOST_TEST( arr.rotated() [0][1] == 10 ); + BOOST_TEST( &(arr.rotated())[1][0] == &arr[0][1] ); + BOOST_TEST( &(~arr)[1][0] == &arr[0][1] ); + } + + // BOOST_AUTO_TEST_CASE(multi_transposed) + { + multi::array const arr0 = { + { 9, 24, 30, 9}, + { 4, 10, 12, 7}, + {14, 16, 36, 1}, + }; + multi::array const arr1 = arr0.transposed(); + multi::array const arr2 = ~arr0; + BOOST_TEST( arr1 == arr2 ); + } + + // BOOST_AUTO_TEST_CASE(miguel) + { + multi::array G2D({41, 35}); + auto const& G3D = G2D.rotated().partitioned(7).sliced(0, 3).unrotated(); + + BOOST_TEST( &G3D[0][0][0] == &G2D[0][0] ); + } + { + multi::array const arr = { + {00, 01}, + {10, 11}, + }; + BOOST_TEST( arr.rotated() [0][1] == 10 ); + BOOST_TEST( &(arr.rotated())[1][0] == &arr[0][1] ); + BOOST_TEST( &(~arr)[1][0] == &arr[0][1] ); + } + + // BOOST_AUTO_TEST_CASE(multi_transposed) + { + multi::array const arr0 = { + { 9, 24, 30, 9}, + { 4, 10, 12, 7}, + {14, 16, 36, 1}, + }; + multi::array const arr1{arr0.transposed()}; + multi::array const arr2{~arr0}; + BOOST_TEST( arr1 == arr2 ); + } + + // BOOST_AUTO_TEST_CASE(miguel) + { + multi::array G2D({41, 35}); + auto const& G3D = G2D.rotated().partitioned(7).sliced(0, 3).unrotated(); + + BOOST_TEST( &G3D[0][0][0] == &G2D[0][0] ); + } + +#if __cplusplus >= 202002L +#if defined(__cpp_lib_ranges_repeat) && (__cpp_lib_ranges_repeat >= 202207L) +#if !defined(__clang_major__) || (__clang_major__ != 16) +#if !defined(__GNUC__) || (__GNUC__ < 14) + + // BOOST_AUTO_TEST_CASE(matlab_meshgrid) + { + auto const x = multi::array{1, 2, 3}; + auto const y = multi::array{1, 2, 3, 4, 5}; + + auto const& [X, Y] = meshgrid(x, y); + + auto const [X_copy, Y_copy] = meshgrid_copy(x, y); + + for(auto i : x.extension()) { + for(auto j : y.extension()) { + BOOST_TEST( X[i][j] == X_copy[i][j] ); + BOOST_TEST( Y[i][j] == Y_copy[i][j] ); + } + } + } +#endif +#endif +#endif +#endif -BOOST_AUTO_TEST_CASE(miguel) { - multi::array G2D({41, 35}); - auto const& G3D = G2D.rotated().partitioned(7).sliced(0, 3).unrotated(); - BOOST_REQUIRE( &G3D[0][0][0] == &G2D[0][0] ); + return boost::report_errors(); } diff --git a/test/scoped_allocator.cpp b/test/scoped_allocator.cpp index e448686cd..2fc1c7328 100644 --- a/test/scoped_allocator.cpp +++ b/test/scoped_allocator.cpp @@ -1,46 +1,65 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi scoped_allocators" // NOLINT(cppcoreguidelines-macro-usage) title -#include +#include // for static_array, array -#include +#include -#include -#include +// IWYU pragma: no_include // for max // bug in iwyu 14.0.6? with GNU stdlib +#include // for assert +#include // for size_t +#include // for int64_t, int32_t +// IWYU pragma: no_include // for allocator_traits<>::value_type +#include // for bad_alloc +#include // for scoped_allocator_adaptor +#include // for is_same_v +#include // for vector namespace multi = boost::multi; -template +template class allocator1 { int* heap_ = nullptr; template friend class allocator1; public: - using value_type = T; - - allocator1() noexcept = delete; - // NOLINTNEXTLINE(runtime/explicit) - allocator1(int* heap) : heap_{heap} { assert(heap_); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - template allocator1(allocator1 const& other) noexcept : heap_{other.heap_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - auto allocate(std::size_t n) { - if(n == 0) {return static_cast(nullptr);} - if(heap_ == nullptr) {throw std::bad_alloc{};} // this cuts branches with UB (null deref) for the sanitizer - ++*heap_; return static_cast(::operator new (n*sizeof(value_type))); + using value_type = T; + + allocator1() noexcept = delete; + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + allocator1(int* heap) : heap_{heap} { assert(heap_); } // NOLINT(runtime/explicit) // NOSONAR(cpp:S1709) mimic memory resource syntax (pass pointer) + + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + template allocator1(allocator1 const& other) noexcept : heap_{other.heap_} {} // NOSONAR(cpp:S1709) allocator conversions are not explicit + + auto allocate(std::size_t n) { + if(n == 0) { + return static_cast(nullptr); + } + if(heap_ == nullptr) { + throw std::bad_alloc{}; + } // this cuts branches with UB (null deref) for the sanitizer + ++*heap_; + static_assert(!std::is_same_v); + // cppcheck-suppress sizeofVoid ; + return static_cast(::operator new(n * sizeof(value_type))); // NOLINT(misc-include-cleaner) bug in clang-tidy 20 } - void deallocate(value_type* ptr, std::size_t n) noexcept { - if(n == 0) {return;} - --*heap_; ::operator delete(ptr); + void deallocate(value_type* ptr, std::size_t n) noexcept { + if(n == 0) { + return; + } + --*heap_; + ::operator delete(ptr); } - template - friend auto operator==(allocator1 const& self, allocator1 const& other) noexcept { return self.heap_ == other.heap_; } + template + friend auto operator==(allocator1 const& self, allocator1 const& other) noexcept -> bool { return self.heap_ == other.heap_; } + template + friend auto operator!=(allocator1 const& self, allocator1 const& other) noexcept -> bool { return self.heap_ != other.heap_; } }; -template -auto operator!=(allocator1 const& self, allocator1 const& other) noexcept { return not(self == other); } - template class allocator2 { std::int64_t* heap_ = nullptr; @@ -48,88 +67,151 @@ class allocator2 { template friend class allocator2; public: - using value_type = T; - - allocator2() noexcept = delete; - // NOLINTNEXTLINE(runtime/explicit) - allocator2(std::int64_t* heap) : heap_{heap} { assert(heap_); } // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - template allocator2(allocator2 const& other) noexcept : heap_{other.heap_} {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - auto allocate(std::size_t n) { - if(n == 0) {return static_cast(nullptr);} - if(heap_ == nullptr) {throw std::bad_alloc{};} // this cuts branches with UB (null deref) for the sanitizer - ++*heap_; return static_cast(::operator new(n * sizeof(value_type))); + using value_type = T; + + allocator2() noexcept = default; + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + allocator2(std::int64_t* heap) : heap_{heap} { assert(heap_); } // NOLINT(runtime/explicit) // NOSONAR(cpp:S1709) mimic memory resource syntax (pass pointer) + + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + template allocator2(allocator2 const& other) noexcept : heap_{other.heap_} {} // NOSONAR(cpp:S1709) allocator conversions are not explicit + + auto allocate(std::size_t n) { + if(n == 0) { + return static_cast(nullptr); + } + if(heap_ == nullptr) { // this cuts branches with UB (null deref) for the sanitizer + throw std::bad_alloc{}; + } + ++*heap_; + static_assert(!std::is_same_v); + // cppcheck-suppress sizeofVoid ; + return static_cast(::operator new(n * sizeof(value_type))); } - void deallocate(value_type* ptr, std::size_t n) noexcept { - if(n == 0) {return;} - --*heap_; ::operator delete(ptr); + void deallocate(value_type* ptr, std::size_t n) noexcept { + if(n == 0) { + return; + } + --*heap_; + ::operator delete(ptr); } + + template + friend auto operator==(allocator2 const& self, allocator2 const& other) noexcept -> bool { return self.heap_ == other.heap_; } template - friend auto operator==(allocator2 const& self, allocator2 const& other) noexcept { return self.heap_ == other.heap_; } + friend auto operator!=(allocator2 const& self, allocator2 const& other) noexcept -> bool { return self.heap_ != other.heap_; } }; -template -auto operator!=(allocator2 const& self, allocator2 const& other) noexcept { return not(self == other); } - -BOOST_AUTO_TEST_CASE(scoped_allocator_vector) { - std::int32_t heap1 = 0; - std::int64_t heap2 = 0; - +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(scoped_allocator_vector) { - using InnerCont = std::vector>; - using OuterCont = std::vector, allocator2 >>; - - OuterCont cont({&heap1, &heap2}); - cont.resize(2); - - cont.resize(10); + std::int32_t heap1 = 0; + std::int64_t heap2 = 0; + + { + using InnerCont = std::vector>; + using OuterCont = + std::vector< + InnerCont, + std::scoped_allocator_adaptor< + allocator1, + allocator2>>; + + // OuterCont cont({&heap1, &heap2}); // gives ambiguous construction in libc++ + OuterCont cont({&heap1, allocator2{&heap2}}); + + cont.resize(2); + + cont.resize(10); + + cont.back().resize(10); + cont.back().resize(100); + cont.back().resize(300); + +// these values are depdenent on the implementation of std::vector +#ifndef _MSC_VER + BOOST_TEST( heap1 == 1 ); + BOOST_TEST( heap2 == 1L ); +#endif + } + + BOOST_TEST( heap1 == 0 ); + BOOST_TEST( heap2 == 0 ); + } - cont.back().resize(10); - cont.back().resize(100); - cont.back().resize(300); + // BOOST_AUTO_TEST_CASE(scoped_allocator_array_vector) + { + std::int32_t heap1 = 0; + std::int64_t heap2 = 0; - BOOST_TEST( heap1 == 1 ); - BOOST_TEST( heap2 == 1L ); + using InnerCont = std::vector>; + using OuterCont = multi::array, allocator2>>; + + { + OuterCont cont( +#ifdef _MSC_VER // problem with MSVC 14.3 c++17 + multi::extensions_t<2> +#endif + {3, 4}, + {&heap1, allocator2{&heap2}} // without allocator2<>{...} gives ambiguous construction in libc++ + ); + + cont[1][2].resize(10); + cont[1][2].resize(100); + cont[1][2].resize(200); + +// these values are depdenent on the implementation of std::vector +#ifndef _MSC_VER + BOOST_TEST( heap1 == 1 ); + BOOST_TEST( heap2 == 1L ); +#endif + } } - BOOST_TEST( heap1 == 0 ); - BOOST_TEST( heap2 == 0 ); -} -BOOST_AUTO_TEST_CASE(scoped_allocator_array_vector) { - std::int32_t heap1 = 0; - std::int64_t heap2 = 0; + // vvv this cases confuse gcc (and MSVC?) + // BOOST_AUTO_TEST_CASE(scoped_allocator_array_vector_auto) + { + std::int32_t heap1 = 0; + std::int64_t heap2 = 0; - using InnerCont = std::vector>; - using OuterCont = multi::array, allocator2 >>; + using InnerCont = std::vector>; + using OuterCont = multi::array, allocator2<>>>; - { - OuterCont cont({3, 4}, {&heap1, &heap2}); + { + OuterCont cont({3, 4}, {&heap1, allocator2<>{&heap2}}); // without allocator2<>{...} gives ambiguous construction in libc++ - cont[1][2].resize(10); - cont[1][2].resize(100); - cont[1][2].resize(200); + cont[1][2].resize(10); + cont[1][2].resize(100); + cont[1][2].resize(200); - BOOST_TEST( heap1 == 1 ); - BOOST_TEST( heap2 == 1L ); + BOOST_TEST( heap1 == 1 ); +// these values are depdenent on the implementation of std::vector +#ifndef _MSC_VER + BOOST_TEST( heap2 == 1L ); +#endif + } } -} -BOOST_AUTO_TEST_CASE(scoped_allocator_array_vector_auto) { - std::int32_t heap1 = 0; - std::int64_t heap2 = 0; + // BOOST_AUTO_TEST_CASE(scoped_allocator_array_array_auto) + { + std::int32_t heap1 = 0; + std::int64_t heap2 = 0; - using InnerCont = std::vector>; - using OuterCont = multi::array, allocator2<> >>; + using InnerCont = multi::array>; + using OuterCont = multi::array, allocator2<>>>; - { - OuterCont cont({3, 4}, {&heap1, &heap2}); + { + OuterCont cont({3, 4}, {&heap1, allocator2<>{&heap2}}); // without allocator2<>{...} gives ambiguous construction in libc++ - cont[1][2].resize(10); - cont[1][2].resize(100); - cont[1][2].resize(200); + cont[1][2].reextent({10, 10}); + cont[1][2].reextent({100, 100}); + cont[1][2].reextent({200, 200}); - BOOST_TEST( heap1 == 1 ); - BOOST_TEST( heap2 == 1L ); + BOOST_TEST( heap1 == 1 ); + BOOST_TEST( heap2 == 1L ); + } } + + return boost::report_errors(); } diff --git a/test/sean.cpp b/test/sean.cpp new file mode 100644 index 000000000..7c593b0e6 --- /dev/null +++ b/test/sean.cpp @@ -0,0 +1,82 @@ +// Copyright 2021-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#include // IWYU pragma: keep + +#include // IWYU pragma: keep // for std::equal +#include // IWYU pragma: keep +#include // IWYU pragma: keep // for std::tuple // NOLINT(misc-include-cleaner) + +#if defined(__cplusplus) && (__cplusplus >= 202002L) && __has_include() +#if !defined(__clang_major__) || (__clang_major__ != 16) +#include // for constructible_from, defau... +#include // IWYU pragma: keep +#include // for is_constructible_v +#endif +#endif + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(bugprone-exception-escape,readability-function-cognitive-complexity) + { +#ifdef __NVCC__ + auto fun = [](auto ii, auto jj) noexcept { return static_cast((10 * ii) + jj); }; + auto rst = fun ^ multi::extensions_t(5, 5); +#else + auto rst = [](auto ii, auto jj) noexcept { return static_cast((10 * ii) + jj); } ^ multi::extensions_t(5, 5); +#endif + multi::array const AA = rst; + + BOOST_TEST( AA.size() == rst.size() ); + BOOST_TEST( AA.extensions() == rst.extensions() ); + +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) && !defined(_MSC_VER) +#if !defined(__clang_major__) || (__clang_major__ != 16) + multi::array const BB = rst | std::ranges::views::reverse; + + BOOST_TEST( AA[0] == BB[4] ); // as A[0][0] == B[4][0] && A[0][1] == B[4][1] ... + BOOST_TEST( AA[1] == BB[3] ); // as A[1][0] == B[3][0] && A[1][1] == B[3][1] ... + BOOST_TEST( AA[2] == BB[2] ); // ... + BOOST_TEST( AA[3] == BB[1] ); + BOOST_TEST( AA[4] == BB[0] ); +#endif +#endif + + auto rstT = rst.transposed(); + + using std::get; + BOOST_TEST( get<0>(rstT.extensions()) == get<1>(rst.extensions()) ); + BOOST_TEST( get<1>(rstT.extensions()) == get<0>(rst.extensions()) ); + + BOOST_TEST( rstT[1][2] == rst[2][1] ); + +#if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) && !defined(_MSC_VER) +#if !defined(__clang_major__) || (__clang_major__ != 16) + static_assert(std::weakly_incrementable); + static_assert(std::input_or_output_iterator); + BOOST_TEST( rstT.begin() == std::ranges::begin(rstT) ); + + static_assert(std::constructible_from); + static_assert(std::default_initializable); + static_assert(std::is_constructible_v); + static_assert(std::semiregular); + BOOST_TEST( rstT.end() == std::ranges::end(rstT) ); + + static_assert(std::ranges::viewable_range); + auto rstTR = rstT | std::ranges::views::reverse; + + BOOST_TEST( rstTR.back()[0] == rstT.front()[0] ); + BOOST_TEST( rstTR.front()[0] == rstT.back()[0] ); + + auto rstTR2 = rstT.reversed(); + + BOOST_TEST( rstTR2[3][4] == rstTR[3][4] ); +#endif +#endif + } + + return boost::report_errors(); +} diff --git a/test/select_column.cpp b/test/select_column.cpp index 6e0594867..fcfca047d 100644 --- a/test/select_column.cpp +++ b/test/select_column.cpp @@ -1,126 +1,139 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2021 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi select range" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for intersecting_range, array, size -#include "multi/array.hpp" +#include -namespace multi = boost::multi; - -BOOST_AUTO_TEST_CASE(multi_array_range_section_1D) { - multi::array arr = {00.0, 01.0, 02.0}; (void)arr; - BOOST_REQUIRE( arr == arr(multi::ALL) ); - BOOST_REQUIRE( size(arr( 1 <= multi::ALL )) == 2 ); - BOOST_REQUIRE( arr( 1 <= multi::ALL )[0] == 1.0 ); - BOOST_REQUIRE( size(arr( multi::ALL < 2 )) == 2 ); - BOOST_REQUIRE( arr( multi::ALL < 2 )[1] == 1.0 ); -} - -BOOST_AUTO_TEST_CASE(multi_array_range_section_part1) { - multi::array arr = { - {00.0, 01.0, 02.0}, - {10.0, 11.0, 12.0}, - {20.0, 21.0, 22.0}, - {30.0, 31.0, 32.0}, - }; - - using multi::_; - using multi::U; - - BOOST_REQUIRE( size( arr( multi::ALL , 2) ) == size(arr) ); - BOOST_REQUIRE( size( arr( multi::_ , 2) ) == size(arr) ); - BOOST_REQUIRE( size( arr( *multi::_ , 2) ) == size(arr) ); - BOOST_REQUIRE( size( arr( multi::U , 2) ) == size(arr) ); - - BOOST_REQUIRE( size( arr( multi::ALL , 2) ) == 4 ); - BOOST_REQUIRE( size( arr( multi::ALL < 2 , 2) ) == 2 ); - BOOST_REQUIRE( size( arr( 1 <= multi::ALL , 2) ) == 3 ); - BOOST_REQUIRE( size( arr( 1 <= multi::ALL < 3 , 2) ) == 2 ); - - BOOST_REQUIRE( size( arr( multi::_ , 2) ) == 4 ); - BOOST_REQUIRE( size( arr( multi::_ < 2 , 2) ) == 2 ); - BOOST_REQUIRE( size( arr( 1 <= multi::_ , 2) ) == 3 ); - BOOST_REQUIRE( size( arr( 1 <= multi::_ < 3 , 2) ) == 2 ); - - BOOST_REQUIRE( size( arr( _ , 2) ) == 4 ); - BOOST_REQUIRE( size( arr( _ < 2 , 2) ) == 2 ); - BOOST_REQUIRE( size( arr( 1 <= _ , 2) ) == 3 ); - BOOST_REQUIRE( size( arr( 1 <= _ < 3 , 2) ) == 2 ); -} - -BOOST_AUTO_TEST_CASE(multi_array_range_section_part2) { - multi::array arr = { - {00.0, 01.0, 02.0}, - {10.0, 11.0, 12.0}, - {20.0, 21.0, 22.0}, - {30.0, 31.0, 32.0}, - }; - - BOOST_REQUIRE( size( arr(arr.extension(), 2) ) == size(arr) ); - - auto&& col2( arr(arr.extension(), 2) ); // select column #2 - // same as arr(extesion(arr), 2) - // same as arr(arr.extension(0), 2); - // same as rotated(arr)[2]; -// BOOST_REQUIRE( col2.size(0) == size(arr) ); - - BOOST_REQUIRE( dimensionality(col2) == 1 ); - BOOST_REQUIRE( size(col2) == size(arr) ); - BOOST_REQUIRE( col2.size() == size(arr) ); - BOOST_REQUIRE( col2.stride() == 3 ); - BOOST_REQUIRE( col2[0] == 02. ); - BOOST_REQUIRE( col2[1] == 12. ); - BOOST_REQUIRE(( col2 == multi::array{02.0, 12.0, 22.0, 32.0} )); - BOOST_REQUIRE(( col2 == multi::array(rotated(arr)[2]) )); - BOOST_REQUIRE(( col2 == rotated(arr)[2] )); - BOOST_REQUIRE(( col2 == arr(arr.extension(), 2) )); -} - -BOOST_AUTO_TEST_CASE(multi_array_range_section_syntax) { - multi::array arr = { - {00.0, 01.0, 02.0}, - {10.0, 11.0, 12.0}, - {20.0, 21.0, 22.0}, - {30.0, 31.0, 32.0}, - }; - - using multi::_; - BOOST_REQUIRE( size( arr( _ , 2) ) == size(arr) ); - BOOST_REQUIRE( size( arr( *_ , 2) ) == size(arr) ); - - BOOST_REQUIRE( size( arr( (_) , 2) ) == size(arr) ); +#include // for size - using multi::U; - BOOST_REQUIRE( size( arr( U , 2) ) == size(arr) ); - BOOST_REQUIRE( size( arr( U , 2) ) == size(arr) ); - - BOOST_REQUIRE( size( arr( U , 2) ) == size(arr) ); - - using multi::V; - BOOST_REQUIRE( size( arr( V , 2) ) == size(arr) ); - BOOST_REQUIRE( size( arr( V , 2) ) == size(arr) ); - - BOOST_REQUIRE( size( arr( V , 2) ) == size(arr) ); - -// using multi::A; -// BOOST_REQUIRE( size( arr( arr , 2) ) == size(arr) ); -// BOOST_REQUIRE( size( arr( arr , 2) ) == size(arr) ); - -// BOOST_REQUIRE( size( arr( arr , 2) ) == size(arr) ); - - BOOST_REQUIRE( size( arr( _ < 2 , 2) ) == 2 ); - BOOST_REQUIRE( size( arr( *_ < 2 , 2) ) == 2 ); - BOOST_REQUIRE( size( arr( U < 2 , 2) ) == 2 ); - - BOOST_REQUIRE( size( arr( 1 <= _ , 2) ) == 3 ); - BOOST_REQUIRE( size( arr( 1 <= *_ , 2) ) == 3 ); - BOOST_REQUIRE( size( arr( 1 <= U , 2) ) == 3 ); - - BOOST_REQUIRE( size( arr( 1 <= _ < 3 , 2) ) == 2 ); - BOOST_REQUIRE( size( arr( 1 <= *_ < 3 , 2) ) == 2 ); - BOOST_REQUIRE( size( arr( 1 <= U < 3 , 2) ) == 2 ); +namespace multi = boost::multi; - BOOST_REQUIRE( size( arr( *_ < 2 , 2) ) == 2 ); - BOOST_REQUIRE( size( arr( U < 2 , 2) ) == 2 ); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_array_range_section_1D) + { + multi::array arr = {0, 10, 20}; + (void)arr; + BOOST_TEST( arr == arr(multi::ALL) ); + BOOST_TEST( size(arr( 1 <= multi::ALL )) == 2 ); + BOOST_TEST( arr( 1 <= multi::ALL )[0] == 10 ); + BOOST_TEST( size(arr( multi::ALL < 2 )) == 2 ); + BOOST_TEST( arr( multi::ALL < 2 )[1] == 10 ); + } + + // BOOST_AUTO_TEST_CASE(multi_array_range_section_part1) + { + multi::array arr = { + {00.0, 01.0, 02.0}, + {10.0, 11.0, 12.0}, + {20.0, 21.0, 22.0}, + {30.0, 31.0, 32.0}, + }; + + using multi::U; + + BOOST_TEST( size( arr( multi::ALL , 2) ) == size(arr) ); + BOOST_TEST( size( arr( multi::_ , 2) ) == size(arr) ); + BOOST_TEST( size( arr( *multi::_ , 2) ) == size(arr) ); + BOOST_TEST( size( arr( multi::U , 2) ) == size(arr) ); + + BOOST_TEST( size( arr( multi::ALL , 2) ) == 4 ); + BOOST_TEST( size( arr( multi::ALL < 2 , 2) ) == 2 ); + BOOST_TEST( size( arr( 1 <= multi::ALL , 2) ) == 3 ); + // cppcheck-suppress [compareBoolExpressionWithInt, knownConditionTrueFalse] ; + BOOST_TEST( size( arr( 1 <= multi::ALL < 3 , 2) ) == 2 ); // NOLINT(bugprone-chained-comparison) + + BOOST_TEST( size( arr( multi::_ , 2) ) == 4 ); + BOOST_TEST( size( arr( multi::_ < 2 , 2) ) == 2 ); + BOOST_TEST( size( arr( 1 <= multi::_ , 2) ) == 3 ); // cppcheck-suppress compareBoolExpressionWithInt ; + + // cppcheck-suppress [compareBoolExpressionWithInt, knownConditionTrueFalse] ; library syntax + BOOST_TEST( size( arr( 1 <= multi::_ < 3 , 2) ) == 2 ); // NOLINT(bugprone-chained-comparison) + + using multi::_; + BOOST_TEST( size( arr( _ , 2) ) == 4 ); + BOOST_TEST( size( arr( _ < 2 , 2) ) == 2 ); + BOOST_TEST( size( arr( 1 <= _ , 2) ) == 3 ); + + // cppcheck-suppress [compareBoolExpressionWithInt, knownConditionTrueFalse] ; library syntax + BOOST_TEST( size( arr( 1 <= _ < 3 , 2) ) == 2 ); // NOLINT(bugprone-chained-comparison) + } + + // BOOST_AUTO_TEST_CASE(multi_array_range_section_part2) + { + multi::array arr = { + { 0, 10, 20}, + {100, 110, 120}, + {200, 210, 220}, + {300, 310, 320}, + }; + + BOOST_TEST( size( arr(arr.extension(), 2) ) == size(arr) ); + + auto&& col2(arr(arr.extension(), 2)); // select column #2 + // same as arr(extesion(arr), 2) + // same as arr(arr.extension(0), 2); + // same as rotated(arr)[2]; + // BOOST_TEST( col2.size(0) == size(arr) ); + + BOOST_TEST( dimensionality(col2) == 1 ); + BOOST_TEST( size(col2) == size(arr) ); + BOOST_TEST( col2.size() == size(arr) ); + BOOST_TEST( col2.stride() == 3 ); + BOOST_TEST( col2[0] == 20 ); + BOOST_TEST( col2[1] == 120 ); + BOOST_TEST(( col2 == multi::array{20, 120, 220, 320} )); + BOOST_TEST(( col2 == multi::array(arr.rotated()[2]) )); + BOOST_TEST(( col2 == arr.rotated()[2] )); + BOOST_TEST(( col2 == arr(arr.extension(), 2) )); + } + + // BOOST_AUTO_TEST_CASE(multi_array_range_section_syntax) + { + multi::array arr = { + {00.0, 01.0, 02.0}, + {10.0, 11.0, 12.0}, + {20.0, 21.0, 22.0}, + {30.0, 31.0, 32.0}, + }; + + using multi::_; + BOOST_TEST( size( arr( _ , 2) ) == size(arr) ); + BOOST_TEST( size( arr( *_ , 2) ) == size(arr) ); + + BOOST_TEST( size( arr( (_) , 2) ) == size(arr) ); + + using multi::U; + BOOST_TEST( size( arr( U , 2) ) == size(arr) ); + BOOST_TEST( size( arr( U , 2) ) == size(arr) ); + + BOOST_TEST( size( arr( U , 2) ) == size(arr) ); + + using multi::V; + BOOST_TEST( size( arr( V , 2) ) == size(arr) ); + BOOST_TEST( size( arr( V , 2) ) == size(arr) ); + + BOOST_TEST( size( arr( V , 2) ) == size(arr) ); + + BOOST_TEST( size( arr( _ < 2 , 2) ) == 2 ); + BOOST_TEST( size( arr( *_ < 2 , 2) ) == 2 ); + BOOST_TEST( size( arr( U < 2 , 2) ) == 2 ); + + BOOST_TEST( size( arr( 1 <= _ , 2) ) == 3 ); + BOOST_TEST( size( arr( 1 <= *_ , 2) ) == 3 ); + BOOST_TEST( size( arr( 1 <= U , 2) ) == 3 ); + + // cppcheck-suppress-begin [compareBoolExpressionWithInt, knownConditionTrueFalse] ; library syntax + BOOST_TEST( size( arr( 1 <= _ < 3 , 2) ) == 2 ); // NOLINT(bugprone-chained-comparison) + BOOST_TEST( size( arr( 1 <= *_ < 3 , 2) ) == 2 ); // NOLINT(bugprone-chained-comparison) + BOOST_TEST( size( arr( 1 <= U < 3 , 2) ) == 2 ); // NOLINT(bugprone-chained-comparison) + // cppcheck-suppress-end [compareBoolExpressionWithInt, knownConditionTrueFalse] ; library syntax + + BOOST_TEST( size( arr( *_ < 2 , 2) ) == 2 ); + BOOST_TEST( size( arr( U < 2 , 2) ) == 2 ); + } + + return boost::report_errors(); } diff --git a/test/sliced.cpp b/test/sliced.cpp index bd347f7d8..1d99b536d 100644 --- a/test/sliced.cpp +++ b/test/sliced.cpp @@ -1,94 +1,120 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2021-2023 Alfredo A. Correa +// Copyright 2021-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi slice" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include +#include -#include +#include -#include // std::iota +#include // for std::iota +#include // for std::vector +// IWYU pragma: no_include // for copy namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_array_sliced_empty) { - multi::array arr({0, 0}, 99.0); - BOOST_REQUIRE( arr.sliced(0, 0).is_empty() ); - BOOST_REQUIRE( arr.sliced(1, 1).is_empty() ); -} - -BOOST_AUTO_TEST_CASE(multi_array_sliced) { - multi::array arr({10, 20, 30, 40}, 99.0); - std::iota(arr.elements().begin(), arr.elements().end(), 0.); - - static_assert( decltype( arr.sliced(0, 5) )::rank::value == decltype(arr)::rank::value); // NOLINT(misc-redundant-expression) - static_assert( decltype( arr.sliced(0, 5) )::rank{} == decltype(arr)::rank{}); // NOLINT(misc-redundant-expression) - static_assert( decltype( arr.sliced(0, 5) )::rank_v == decltype(arr)::rank_v); // NOLINT(misc-redundant-expression) - - BOOST_REQUIRE( arr.sliced( 0, 5)[1][2][3][4] == arr[1][2][3][4] ); - BOOST_REQUIRE( &arr.sliced( 0, 5)[1][2][3][4] == &arr[1][2][3][4] ); - - BOOST_REQUIRE( arr.sliced( 0, 5)[1] == arr[1] ); - BOOST_REQUIRE( &arr.sliced( 0, 5)[1] == &arr[1] ); - - BOOST_REQUIRE( arr.sliced( 0, 0).empty() ); - BOOST_REQUIRE( arr.sliced( 1, 1).empty() ); - BOOST_REQUIRE( arr.sliced( 0, 10).size() == 10 ); - - BOOST_REQUIRE( arr[1].sliced(0, 5)[2][3][4] == arr[1][2][3][4] ); - BOOST_REQUIRE( &arr[1].sliced(0, 5)[2][3][4] == &arr[1][2][3][4] ); - - BOOST_REQUIRE( arr[1].sliced(0, 5)[2] == arr[1][2] ); - BOOST_REQUIRE( &arr[1].sliced(0, 5)[2] == &arr[1][2] ); - - BOOST_REQUIRE( arr[1].sliced(0, 0).is_empty() ); - BOOST_REQUIRE( arr[1].sliced(1, 1).is_empty() ); - BOOST_REQUIRE( arr[1].sliced(0, 20).size() == 20 ); - - BOOST_REQUIRE( (arr.rotated()).sliced(0, 5)[1][2][3][4] == (arr.rotated())[1][2][3][4] ); - BOOST_REQUIRE( &(arr.rotated()).sliced(0, 5)[1][2][3][4] == &(arr.rotated())[1][2][3][4] ); -} - -BOOST_AUTO_TEST_CASE(multi_array_stride) { - multi::array arr = { - { 1.0, 2.0, 3.0, 4.0}, - { 5.0, 6.0, 7.0, 8.0}, - { 9.0, 10.0, 11.0, 12.0}, - {13.0, 14.0, 15.0, 16.0}, - }; - BOOST_REQUIRE(( - arr.strided(2) == multi::array{ - { 1.0, 2.0, 3.0, 4.0}, - { 9.0, 10.0, 11.0, 12.0}, +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_array_sliced_empty) + { + multi::array const arr({0, 0}, 99.0); + BOOST_TEST( arr.sliced(0, 0).is_empty() ); + // BOOST_TEST( arr.sliced(1, 1).is_empty() ); // this results in offsetting nullptr + } + + // BOOST_AUTO_TEST_CASE(multi_array_sliced) + { + multi::array arr({10, 20, 30, 40}, 99); + std::iota(arr.elements().begin(), arr.elements().end(), 0); + + static_assert(decltype(arr.sliced(0, 5))::rank::value == 4); + static_assert(decltype(arr.sliced(0, 5))::rank{} == 4); + static_assert(decltype(arr.sliced(0, 5))::rank_v == 4); + + BOOST_TEST( arr.sliced( 0, 5)[1][2][3][4] == arr[1][2][3][4] ); + BOOST_TEST( &arr.sliced( 0, 5)[1][2][3][4] == &arr[1][2][3][4] ); + + BOOST_TEST( arr.sliced( 0, 5)[1] == arr[1] ); + BOOST_TEST( &arr.sliced( 0, 5)[1] == &arr[1] ); + + BOOST_TEST( arr.sliced( 0, 0).empty() ); + BOOST_TEST( arr.sliced( 1, 1).empty() ); + BOOST_TEST( arr.sliced( 0, 10).size() == 10 ); + + BOOST_TEST( arr[1].sliced(0, 5)[2][3][4] == arr[1][2][3][4] ); + BOOST_TEST( &arr[1].sliced(0, 5)[2][3][4] == &arr[1][2][3][4] ); + + BOOST_TEST( arr[1].sliced(0, 5)[2] == arr[1][2] ); + BOOST_TEST( &arr[1].sliced(0, 5)[2] == &arr[1][2] ); + + BOOST_TEST( arr[1].sliced(0, 0).is_empty() ); + BOOST_TEST( arr[1].sliced(1, 1).is_empty() ); + BOOST_TEST( arr[1].sliced(0, 20).size() == 20 ); + + BOOST_TEST( (arr.rotated()).sliced(0, 5)[1][2][3][4] == (arr.rotated())[1][2][3][4] ); + BOOST_TEST( &(arr.rotated()).sliced(0, 5)[1][2][3][4] == &(arr.rotated())[1][2][3][4] ); + } + + // BOOST_AUTO_TEST_CASE(multi_array_stride) + { + multi::array arr = { + { 10, 20, 30, 40}, + { 50, 60, 70, 80}, + { 90, 100, 110, 120}, + {130, 140, 150, 160}, + }; + BOOST_TEST(( + arr.strided(2) == multi::array{ + { 10, 20, 30, 40}, + { 90, 100, 110, 120}, } )); -} - -BOOST_AUTO_TEST_CASE(multi_array_take) { - multi::array arr = { - { 1.0, 2.0, 3.0, 4.0}, - { 5.0, 6.0, 7.0, 8.0}, - { 9.0, 10.0, 11.0, 12.0}, - {13.0, 14.0, 15.0, 16.0}, - }; - BOOST_REQUIRE(( - arr.take(2) == multi::array{ - { 1.0, 2.0, 3.0, 4.0}, - { 5.0, 6.0, 7.0, 8.0}, + } + + // BOOST_AUTO_TEST_CASE(multi_array_take) + { + multi::array arr = { + { 10, 20, 30, 40}, + { 50, 60, 70, 80}, + { 90, 100, 110, 120}, + {130, 140, 150, 160}, + }; + BOOST_TEST(( + arr.taked(2) == multi::array{ + { 10, 20, 30, 40}, + { 50, 60, 70, 80}, } )); -} - -BOOST_AUTO_TEST_CASE(drop) { - multi::array arr = { - { 1., 2., 3., 4.}, - { 5., 6., 7., 8.}, - { 9., 10., 11., 12.}, - {13., 14., 15., 16.}, - }; - BOOST_REQUIRE(( - arr.drop(2) == multi::array{ - { 9., 10., 11., 12.}, - {13., 14., 15., 16.}, + } + + // BOOST_AUTO_TEST_CASE(drop) + { + multi::array arr = { + { 10, 20, 30, 40}, + { 50, 60, 70, 80}, + { 90, 100, 110, 120}, + {130, 140, 150, 160}, + }; + BOOST_TEST(( + arr.dropped(2) == multi::array{ + { 90, 100, 110, 120}, + {130, 140, 150, 160}, } )); + } + + // slicing 1D array + { + std::vector VV = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + multi::array_ref> const AA({static_cast(VV.size())}, VV.data()); + + BOOST_TEST( AA.nelems() == static_cast(VV.size()) ); + BOOST_TEST( !AA.is_empty() ); + + BOOST_TEST( AA.sliced(2, 9).size() == 7 ); + BOOST_TEST( AA.sliced(2, 9)[0] == 2 ); + BOOST_TEST( AA.sliced(2, 9)[6] == 8 ); + } + + return boost::report_errors(); } diff --git a/test/sort.cpp b/test/sort.cpp index 782d61d0e..3c7041325 100644 --- a/test/sort.cpp +++ b/test/sort.cpp @@ -1,73 +1,314 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;-*- -// Copyright 2019-2022 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "Unit Tests for Multi sort" // test title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for apply, operator!=, operator== -#include +#include -#include // for std::stable_sort -#include +#include // for is_sorted, stable_sort +#include // for array +#include // for abs // IWYU pragma: keep +// IWYU pragma: no_include // for abs +// #include // for __cpp_lib_ranges // IWYU pragma: keep +#include // for begin, end +#include // for accumulate +#include // for vector +// IWYU pragma: no_include // for __cpp_lib_ranges +#ifdef __cpp_lib_ranges +#include // IWYU pragma: keep +#endif namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_array_stable_sort) { - std::vector vec = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( std::is_sorted(begin(vec), end(vec)) ); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(array_1D_partial_order_syntax) + { + multi::array const tt = {1, 1, 1}; + multi::array const uu = {2, 2, 2}; - multi::array d2D = { - {150.0, 16.0, 17.0, 18.0, 19.0}, - { 30.0, 1.0, 2.0, 3.0, 4.0}, - {100.0, 11.0, 12.0, 13.0, 14.0}, - { 50.0, 6.0, 7.0, 8.0, 9.0} - }; - BOOST_REQUIRE( not std::is_sorted(begin(d2D), end(d2D) ) ); + BOOST_TEST( tt < uu ); + BOOST_TEST( !(tt > uu) ); + BOOST_TEST( tt <= uu ); + BOOST_TEST( !(tt >= uu) ); + BOOST_TEST( !(tt == uu) ); + BOOST_TEST( (tt != uu) ); + BOOST_TEST( !(uu < tt) ); + BOOST_TEST( (uu > tt) ); + BOOST_TEST( !(uu <= tt) ); + BOOST_TEST( (uu >= tt) ); + } - std::stable_sort( begin(d2D), end(d2D) ); - BOOST_REQUIRE( std::is_sorted( begin(d2D), end(d2D) ) ); +#ifdef __cpp_lib_ranges + // BOOST_AUTO_TEST_CASE(sort_2D) + { + multi::array A2D = { + {3, 3, 3}, + {2, 2, 2}, + {1, 1, 1}, + }; + BOOST_TEST( !std::ranges::is_sorted(A2D) ); // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE(( - d2D == decltype(d2D){ + using it = boost::multi::array_iterator; + + static_assert(std::forward_iterator); + + using In = it; + using Out = it; + + static_assert(std::indirectly_readable); + + static_assert(std::indirectly_writable>); + static_assert(std::indirectly_writable>); + static_assert(std::indirectly_movable); + static_assert(std::indirectly_writable>); + static_assert(std::movable>); + static_assert(std::constructible_from, std::iter_rvalue_reference_t>); + static_assert(std::assignable_from&, std::iter_rvalue_reference_t>); + + static_assert(std::indirectly_movable_storable); + static_assert(std::indirectly_swappable); + static_assert(std::permutable); + + // std::sort(A.begin(), A.end()); + std::ranges::sort(A2D); // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( std::ranges::is_sorted(A2D) ); // NOLINT(fuchsia-default-arguments-calls) + } + + // BOOST_AUTO_TEST_CASE(sort_strings) + { + auto A2D = multi::array{ + {'S', 'e', 'a', 'n', ' ', ' '}, + {'A', 'l', 'e', 'x', ' ', ' '}, + {'B', 'j', 'a', 'r', 'n', 'e'}, + }; + BOOST_TEST( !std::ranges::is_sorted(A2D) ); // NOLINT(fuchsia-default-arguments-calls) + + std::ranges::sort(A2D); // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( std::ranges::is_sorted(A2D)); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST(( + A2D == multi::array{ + {'A', 'l', 'e', 'x', ' ', ' '}, + {'B', 'j', 'a', 'r', 'n', 'e' }, + {'S', 'e', 'a', 'n', ' ', ' '}, + } + )); + + std::ranges::sort(~A2D); // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST(std::ranges::is_sorted(~A2D)); // NOLINT(fuchsia-default-arguments-calls) + + static_assert(std::permutable>); + } +#endif + + // BOOST_AUTO_TEST_CASE(multi_array_stable_sort) + { + std::vector vec = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( std::is_sorted(begin(vec), end(vec)) ); // NOLINT(fuchsia-default-arguments-calls,modernize-use-ranges) for C++20 + + multi::array d2D = { + {150.0, 16.0, 17.0, 18.0, 19.0}, { 30.0, 1.0, 2.0, 3.0, 4.0}, - { 50.0, 6.0, 7.0, 8.0, 9.0}, {100.0, 11.0, 12.0, 13.0, 14.0}, - {150.0, 16.0, 17.0, 18.0, 19.0} + { 50.0, 6.0, 7.0, 8.0, 9.0}, + }; + + { + bool check = true; + auto first = d2D.begin(); + auto const last = d2D.end(); + auto next = d2D.begin(); + ++next; + while(next != last) { // NOLINT(altera-unroll-loops,altera-id-dependent-backward-branch) + if(*next < *first) { + check = false; + break; + } + ++first; + ++next; + } + BOOST_TEST( !check ); } - )); - BOOST_REQUIRE( not std::is_sorted( begin(d2D.rotated()), end(d2D.rotated()) ) ); + BOOST_TEST(!( + (*(d2D.begin() ) <= *(d2D.begin() + 1)) + && (*(d2D.begin() + 1) <= *(d2D.begin() + 2)) + && (*(d2D.begin() + 2) <= *(d2D.begin() + 3)) + )); + +#if !defined(__clang_major__) || (__clang_major__ != 7) // bug in is_sorted in clang 7 + BOOST_TEST( !std::is_sorted(d2D.begin(), d2D.end() ) ); // NOLINT(fuchsia-default-arguments-calls,modernize-use-ranges) for C++20 +#endif - std::stable_sort( begin(d2D.rotated()), end(d2D.rotated()) ); - BOOST_REQUIRE( std::is_sorted( begin(d2D.rotated()), end(d2D.rotated()) ) ); - BOOST_REQUIRE( std::is_sorted( begin(d2D ), end(d2D ) ) ); + std::stable_sort(d2D.begin(), d2D.end()); // NOLINT(modernize-use-ranges) for C++20 + BOOST_TEST( std::is_sorted( begin(d2D), end(d2D) ) ); // NOLINT(fuchsia-default-arguments-calls,modernize-use-ranges) - BOOST_REQUIRE(( + // clang-format off + BOOST_TEST( + d2D == decltype(d2D)({ + { 30.0, 1.0, 2.0, 3.0, 4.0}, + { 50.0, 6.0, 7.0, 8.0, 9.0}, + {100.0, 11.0, 12.0, 13.0, 14.0}, + {150.0, 16.0, 17.0, 18.0, 19.0}, + }) + ); + // clang-format on + +#if !defined(__clang_major__) || (__clang_major__ != 7) // bug in is_sorted in clang 7 + BOOST_TEST( !std::is_sorted( begin(d2D.rotated()), end(d2D.rotated()) ) ); // NOLINT(modernize-use-ranges) for C++20 +#endif + + std::stable_sort(begin(d2D.rotated()), end(d2D.rotated())); // NOLINT(modernize-use-ranges) for C++20 + BOOST_TEST( std::is_sorted( begin(d2D.rotated()), end(d2D.rotated()) ) ); // NOLINT(modernize-use-ranges) for C++20 + BOOST_TEST( std::is_sorted( begin(d2D ), end(d2D ) ) ); // NOLINT(modernize-use-ranges) for C++20 + + BOOST_TEST(( d2D == decltype(d2D){ { 1.0, 2.0, 3.0, 4.0, 30.0}, { 6.0, 7.0, 8.0, 9.0, 50.0}, {11.0, 12.0, 13.0, 14.0, 100.0}, - {16.0, 17.0, 18.0, 19.0, 150.0} + {16.0, 17.0, 18.0, 19.0, 150.0}, } )); -} + } -BOOST_AUTO_TEST_CASE(multi_array_ref_stable_sort) { - std::vector vec = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) - BOOST_REQUIRE( std::is_sorted(begin(vec), end(vec)) ); + // BOOST_AUTO_TEST_CASE(multi_array_ref_stable_sort) + { + std::vector vec = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) + BOOST_TEST( std::is_sorted(begin(vec), end(vec)) ); // NOLINT(modernize-use-ranges) for C++20 + // clang-format off std::array, 4> d2D {{ {{150.0, 16.0, 17.0, 18.0, 19.0}}, {{ 30.0, 1.0, 2.0, 3.0, 4.0}}, {{100.0, 11.0, 12.0, 13.0, 14.0}}, {{ 50.0, 6.0, 7.0, 8.0, 9.0}} }}; - auto&& d2D_ref = *multi::array_ptr(&d2D[0][0], {4, 5}); // NOLINT(readability-container-data-pointer) test access + // clang-format on + + auto&& d2D_ref = *multi::array_ptr(&d2D[0][0], {4, 5}); // NOLINT(readability-container-data-pointer) test access + +#if !defined(__clang_major__) || (__clang_major__ != 7) // bug in is_sorted in clang 7 + BOOST_TEST( !std::is_sorted(begin(d2D_ref), end(d2D_ref) ) ); +#endif - BOOST_REQUIRE( not std::is_sorted(begin(d2D_ref), end(d2D_ref) ) ); - std::stable_sort( begin(d2D_ref), end(d2D_ref) ); - BOOST_REQUIRE( std::is_sorted( begin(d2D_ref), end(d2D_ref) ) ); + std::stable_sort(begin(d2D_ref), end(d2D_ref)); + BOOST_TEST( std::is_sorted( begin(d2D_ref), end(d2D_ref) ) ); + +#if !defined(__clang_major__) || (__clang_major__ != 7) // bug in is_sorted in clang 7 + BOOST_TEST( !std::is_sorted( begin(d2D_ref.rotated()), end(d2D_ref.rotated()) ) ); +#endif + + std::stable_sort(begin(d2D_ref.rotated()), end(d2D_ref.rotated())); + BOOST_TEST( std::is_sorted( begin(d2D_ref.rotated()), end(d2D_ref.rotated()) ) ); + } + + // BOOST_AUTO_TEST_CASE(lexicographical_compare) + { + multi::array const name1 = {'a', 'b', 'c'}; + multi::array const name2 = {'a', 'c', 'c'}; + + BOOST_TEST( name1 != name2 ); + BOOST_TEST( name1 < name2); + BOOST_TEST( name1 <= name2); + BOOST_TEST(!(name1 > name2)); + BOOST_TEST(!(name1 > name2)); + } + + // BOOST_AUTO_TEST_CASE(lexicographical_compare_offset) + { + multi::array const name1 = {'a', 'b', 'c'}; + // clang-format off + multi::array name2({{ 1, 4 }}, '\0'); + // clang-format on + + BOOST_TEST( name2.size() == 3 ); + BOOST_TEST(( name2.extension() == multi::extension_t{1, 4} )); + BOOST_TEST(( name2.extension() == multi::extension_t{multi::index{1}, multi::index{4}} )); + + // BOOST_TEST(( name2.extension() == multi::extension_t{1L, 4L} )); + + BOOST_TEST(( name2.extension() == multi::extension_t<>{1, 4} )); + // BOOST_TEST(( name2.extension() == multi::extension_t{1 , 4 } )); TODO(correaa) solve ambiguity + + name2[1] = 'a'; + name2[2] = 'b'; + name2[3] = 'c'; // cppcheck-suppress knownConditionTrueFalse ; + + BOOST_TEST( name2 != name1 ); + BOOST_TEST(!(name2 == name1)); + + BOOST_TEST( name2 < name1 ); + BOOST_TEST( name2 <= name1 ); + + BOOST_TEST(!(name2 > name1)); + BOOST_TEST(!(name2 >= name1)); + + BOOST_TEST( name2[1] == 'a' ); + BOOST_TEST( name2[2] == 'b' ); + BOOST_TEST( name2[3] == 'c' ); // cppcheck-suppress knownConditionTrueFalse ; + } + + // BOOST_AUTO_TEST_CASE(lexicographical_compare_offset_2d) + { + multi::array const name1 = { + {'a', 'b'}, + {'b', 'c'}, + {'c', 'd'}, + }; + + // clang-format off + multi::array name2({{1, 4}, {0, 2}}, '\0'); + // clang-format on + + BOOST_TEST( name2.size() == 3 ); + BOOST_TEST(( name2.extension() == multi::extension_t{1, 4} )); + BOOST_TEST(( name2.extension() == multi::extension_t<>{1, 4} )); + // BOOST_TEST(( name2.extension() == multi::extension_t{1 , 4 } )); TODO(correaa) solve ambiguity + + name2[1][0] = 'a'; + name2[1][1] = 'a'; + name2[2][0] = 'b'; + name2[2][1] = 'a'; + name2[3][0] = 'c'; + name2[3][1] = 'a'; + + BOOST_TEST( name2 != name1 ); + BOOST_TEST(!(name2 == name1)); + + BOOST_TEST( name2 < name1 ); + BOOST_TEST( name2 <= name1 ); + + BOOST_TEST( name1 > name2 ); + BOOST_TEST(!(name1 < name2)); + + BOOST_TEST( name2[1][0] == 'a' ); + BOOST_TEST( name2[1][1] == 'a' ); + BOOST_TEST( name2[2][0] == 'b' ); + BOOST_TEST( name2[2][1] == 'a' ); + BOOST_TEST( name2[3][0] == 'c' ); + BOOST_TEST( name2[3][1] == 'a' ); // cppcheck-suppress knownConditionTrueFalse ; + } + + // BOOST_AUTO_TEST_CASE(accumulate_1d) + { + { + std::vector vec = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) + + auto const sum = std::accumulate(vec.begin(), vec.end(), double{}); + BOOST_TEST(std::abs(sum - 6.0) < 1e-10); + } + { + multi::array arr = {1.0, 2.0, 3.0}; + + auto const sum = std::accumulate(arr.begin(), arr.end(), double{}); + BOOST_TEST(std::abs(sum - 6.0) < 1e-10); + } + } - BOOST_REQUIRE( not std::is_sorted( begin(d2D_ref.rotated()), end(d2D_ref.rotated()) ) ); - std::stable_sort( begin(d2D_ref.rotated()), end(d2D_ref.rotated()) ); - BOOST_REQUIRE( std::is_sorted( begin(d2D_ref.rotated()), end(d2D_ref.rotated()) ) ); + return boost::report_errors(); } diff --git a/test/static_array_cast.cpp b/test/static_array_cast.cpp index c320aa649..7eae2326e 100644 --- a/test/static_array_cast.cpp +++ b/test/static_array_cast.cpp @@ -1,40 +1,69 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi static array cast" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include +#include +#include // TODO(correaa) remove in c++20 -#include -#include // TODO(remove in c++20) +#include -#include +#include // for equal +#include // for assert +#include // for negate // IWYU pragma: keep +#include // for begin, end +#include // for pointer_t... +#include // for iota +#include // for decay_t +#include // for move, dec... namespace multi = boost::multi; -template class involuter; - template class involuted { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4820) // '3' bytes padding added after data member 'involuted>::f_' +#endif + BOOST_MULTI_NO_UNIQUE_ADDRESS Involution f_; // TODO(correaa) put nounique members first? +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif Ref r_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - MULTI_NO_UNIQUE_ADDRESS Involution f_; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif public: using decay_type = std::decay_t()(std::declval()))>; - constexpr involuted(Ref ref, Involution fun) : r_{std::forward(ref)}, f_{fun} {} - constexpr explicit involuted(Ref ref) : r_{std::forward(ref)}, f_{} {} - involuted(involuted const&) = default; + constexpr involuted(Ref ref, Involution fun) + // : r_{ref}, f_{fun} {} + : f_{fun}, r_{ref} {} + + constexpr explicit involuted(Ref ref) + // : r_{ref}, f_{} {} + : f_{}, r_{ref} {} + + involuted(involuted const&) = default; involuted(involuted&&) noexcept = default; - constexpr auto operator=(involuted const& other) = delete; + + constexpr auto operator=(involuted const&) = delete; + constexpr auto operator=(involuted&&) = delete; + ~involuted() = default; - // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions): simulates a reference - constexpr operator decay_type() const& {return f_(r_);} - // NOLINTNEXTLINE(google-runtime-operator,fuchsia-overloaded-operator): simulates reference - constexpr auto operator&() && -> decltype(auto) {return involuter()), Involution>{&r_, f_};} // NOLINT(runtime/operator) + + // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + constexpr operator decay_type() const& noexcept { return f_(r_); } // NOSONAR(cpp:S1709) simulates a reference + // NOLINTNEXTLINE(fuchsia-trailing-return,-warnings-as-errors): trailing return helps reading - template constexpr auto operator=(DecayType&& other) & -> involuted& {r_ = f_(std::forward(other)); return *this;} - // NOLINTNEXTLINE(fuchsia-trailing-return): trailing return helps reading - constexpr auto operator=(involuted&& other)& noexcept -> involuted& = default; + template constexpr auto operator=(DecayType&& other) & -> involuted& { + r_ = f_(std::forward(other)); + return *this; + } friend auto operator==(involuted const& self, involuted const& other) -> bool { assert(self.f_ == other.f_); @@ -46,16 +75,21 @@ class involuted { } }; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif template class involuter { - It it_; - MULTI_NO_UNIQUE_ADDRESS F f_; + It it_; + BOOST_MULTI_NO_UNIQUE_ADDRESS F f_; + template friend class involuter; public: - using pointer = involuter::pointer, F>; - using element_type = typename std::pointer_traits::element_type; - using difference_type = typename std::pointer_traits::difference_type; + using pointer = involuter::pointer, F>; + using element_type = typename std::pointer_traits::element_type; + using difference_type = typename std::pointer_traits::difference_type; template using rebind = involuter::template rebind, F>; using reference = involuted::reference, F>; @@ -64,98 +98,196 @@ class involuter { explicit constexpr involuter(It it) : it_{std::move(it)}, f_{} {} // NOLINT(readability-identifier-length) clang-tidy 14 bug constexpr involuter(It it, F fun) : it_{std::move(it)}, f_{std::move(fun)} {} - // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions): this is needed to make involuter implicitly convertible to involuter - template constexpr involuter(involuter const& other) : it_{multi::implicit_cast(other.it_)}, f_{other.f_} {} + // vvv this is needed to make involuter implicitly convertible to involuter + // cppcheck-suppress noExplicitConstructor ; // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + template constexpr involuter(involuter const& other) // NOSONAR(cpp:S1709) + : it_{multi::detail::implicit_cast(other.it_)}, f_{other.f_} {} + + constexpr auto operator*() const { return reference{*it_, f_}; } + constexpr auto operator->() const { return pointer{&*it_, f_}; } // cppcheck-suppress redundantPointerOp ; lib idiom + + constexpr auto operator==(involuter const& other) const { return it_ == other.it_; } + constexpr auto operator!=(involuter const& other) const { return it_ != other.it_; } + constexpr auto operator<(involuter const& other) const { return it_ < other.it_; } + +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + constexpr auto operator+=(typename involuter::difference_type n) -> decltype(auto) { + it_ += n; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return *this; + } + + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + constexpr auto operator+(typename involuter::difference_type n) const { return involuter{it_ + n, f_}; } + constexpr auto operator-(typename involuter::difference_type n) const { return involuter{it_ - n, f_}; } - constexpr auto operator* () const {return reference{ *it_, f_};} - constexpr auto operator->() const {return pointer {&*it_, f_};} + constexpr auto operator[](typename involuter::difference_type n) const { return reference{*(it_ + n), f_}; } + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) - constexpr auto operator==(involuter const& other) const {return it_ == other.it_;} - constexpr auto operator!=(involuter const& other) const {return it_ != other.it_;} - constexpr auto operator< (involuter const& other) const {return it_ < other.it_;} + friend constexpr auto operator+(typename involuter::difference_type n, involuter const& self) { return self + n; } - constexpr auto operator+=(typename involuter::difference_type n) -> decltype(auto) {it_+=n; return *this;} - constexpr auto operator+ (typename involuter::difference_type n) const {return involuter{it_+n, f_};} - constexpr auto operator- (typename involuter::difference_type n) const {return involuter{it_-n, f_};} - constexpr auto operator-(involuter const& other) const {return it_ - other.it_;} +#if defined(__clang__) && (__clang_major__ >= 16) && !defined(__INTEL_LLVM_COMPILER) +#pragma clang diagnostic pop +#endif - constexpr auto operator[](typename involuter::difference_type n) const {return reference{*(it_ + n), f_};} + constexpr auto operator-(involuter const& other) const { return it_ - other.it_; } }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif -#if defined(__cpp_deduction_guides) -template involuted(T&&, F)->involuted; +#ifdef __cpp_deduction_guides +template involuted(T&&, F) -> involuted; // NOLINT(misc-use-internal-linkage) bug in clang-tidy 19 #endif template using negated = involuted>; template using negater = involuter>; -BOOST_AUTO_TEST_CASE(multi_array_involution) { - double doub = 5; +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // BOOST_AUTO_TEST_CASE(multi_array_involution) + { + int doub = 50; - auto&& cee = involuted>{doub}; - BOOST_REQUIRE( cee == -5. ); + auto&& cee = involuted>{doub}; + BOOST_TEST( cee == -50 ); - cee = 10.; - BOOST_REQUIRE( doub = -10. ); + cee = 100; + BOOST_TEST( doub == -100 ); - auto m5 = involuted>(5.); - BOOST_REQUIRE( m5 == -5. ); -} + auto m5 = involuted>(50); + BOOST_TEST( m5 == -50 ); + } -BOOST_AUTO_TEST_CASE(static_array_cast) { - multi::static_array arr = { 0., 1., 2., 3., 4.}; - auto&& ref = arr.static_array_cast(); - BOOST_REQUIRE( &ref[2] == &arr [2] ); - BOOST_REQUIRE( &arr [2] == &ref[2] ); + // BOOST_AUTO_TEST_CASE(static_array_cast) + { + multi::static_array arr = {0.0, 1.0, 2.0, 3.0, 4.0}; - BOOST_REQUIRE( std::equal(begin(ref), end(ref), begin(arr), end(arr)) ); - BOOST_REQUIRE( ref == arr ); - BOOST_REQUIRE( arr == ref ); -} + auto&& ref = arr.static_array_cast(); -BOOST_AUTO_TEST_CASE(static_array_cast_2) { - multi::array arr({2, 5}); - std::iota(arr.elements().begin(), arr.elements().end(), 0.); - - auto&& ref = arr.static_array_cast(); - BOOST_REQUIRE( ref[1][1] == arr[1][1] ); - BOOST_REQUIRE( std::equal(begin(ref[1]), end(ref[1]), begin(arr[1]), end(arr[1])) ); - BOOST_REQUIRE( ref[1] == arr[1] ); - BOOST_REQUIRE( std::equal(begin(ref), end(ref), begin(arr), end(arr)) ); - BOOST_REQUIRE( ref == arr ); - BOOST_REQUIRE( arr == ref ); -} + BOOST_TEST( &ref[2] == &arr[2] ); + BOOST_TEST( &arr[2] == &ref[2] ); -BOOST_AUTO_TEST_CASE(static_array_cast_3) { -{ - multi::static_array const arr { { 0.0, 1.0, 2.0, 3.0, 4.0} }; - multi::static_array arr2 = { -0.0, -1.0, -2.0, -3.0, -4.0}; - auto&& neg_arr = multi::static_array_cast>>(arr); - BOOST_REQUIRE( neg_arr[2] == arr2[2] ); - BOOST_REQUIRE( arr2[2] == neg_arr[2] ); - BOOST_REQUIRE( std::equal(begin(neg_arr), end(neg_arr), begin(arr2), end(arr2)) ); - BOOST_REQUIRE( neg_arr == arr2 ); - BOOST_REQUIRE( arr2 == neg_arr ); -} -{ - multi::static_array arr({4, 5}, 0.); - std::iota(elements(arr).begin(), elements(arr).end(), 0.); + BOOST_TEST( std::equal(begin(ref), end(ref), begin(arr), end(arr)) ); - multi::array arr2({4, 5}); - std::transform(begin(elements(arr)), end(elements(arr)), begin(elements(arr2)), std::negate<>{}); + BOOST_TEST( ref == arr() ); + BOOST_TEST( arr() == ref ); - auto&& neg_arr = arr.static_array_cast>(); + BOOST_TEST( ref == arr ); + BOOST_TEST( arr == ref ); + } - BOOST_REQUIRE( neg_arr[1][1] == arr2[1][1] ); - BOOST_REQUIRE( arr2[1][1] == neg_arr[1][1] ); + // BOOST_AUTO_TEST_CASE(static_array_cast_2) + { + multi::array arr({2, 5}); + std::iota(arr.elements().begin(), arr.elements().end(), 0); - BOOST_REQUIRE( std::equal(begin(arr2[1]), end(arr2[1]), begin(neg_arr[1]), end(neg_arr[1])) ); + auto&& ref = arr.static_array_cast(); - BOOST_REQUIRE( arr2[1] == neg_arr[1] ); - BOOST_REQUIRE( neg_arr[1] == arr2[1] ); + BOOST_TEST( ref[1][1] == arr[1][1] ); + BOOST_TEST( std::equal(ref[1].begin(), ref[1].end(), arr[1].begin(), arr[1].end()) ); + BOOST_TEST( ref[1] == arr[1] ); - BOOST_REQUIRE( std::equal(begin(arr2), end(arr2), begin(neg_arr), end(neg_arr)) ); - BOOST_REQUIRE( neg_arr == arr2 ); - BOOST_REQUIRE( arr2 == neg_arr ); -} +#if !defined(_MSC_VER) && !defined(__NVCC__) + BOOST_TEST( std::equal(ref.begin(), ref.end(), arr.begin(), arr.end()) ); // NOLINT(modernize-use-ranges) +#endif + // ^^^ this doesn't work on MSVC+NVCC in C++20 because it tries to generate this type: + // using coty = std::common_reference< + // boost::multi::subarray> &&, + // boost::multi::array> & + // >::type; + // instantiation of type "std::_Cond_res> &&, boost::multi::array> &>" at line 1405 + // instantiation of class "std::_Common_reference2C<_Ty1, _Ty2> [with _Ty1=boost::multi::subarray + // > &&, _Ty2= + // boost::multi::array> &]" at line 1414 + // instantiation of class "std::_Common_reference2B<_Ty1, _Ty2> [with _Ty1=boost::multi::subarray + // > &&, _Ty2=boost::multi::array> &]" at line 1426 + // instantiation of class "std::_Common_reference2A<_Ty1, _Ty2> [with _Ty1=boost::multi::subarray + // > &&, _Ty2=boost::multi::array> &]" at line 1474 + // instantiation of class "std::common_reference<_Ty1, _Ty2> [with + // _Ty1=boost::multi::subarray> &&, + // _Ty2=boost::multi::array> &]" at line 1313 of + // C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\xutility + // instantiation of "const __nv_bool std::_Is_ranges_random_iter_v [with _Iter= + // boost::multi::array_iterator]" at line 5563 of + // C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\xutility + // instantiation of "__nv_bool std::equal(_InIt1, _InIt1, _InIt2, _InIt2, _Pr) [with + // _InIt1=boost::multi::array_iterator, + // _InIt2=boost::multi::array_iterator, _Pr=std::equal_to]" + // at line 5599 of C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\xutility + // instantiation of "__nv_bool std::equal(_InIt1, _InIt1, _InIt2, _InIt2) [with + // _InIt1=boost::multi::array_iterator, + // _InIt2=boost::multi::array_iterator]" + // at line 193 of C:\Gitlab-Runner\builds\t3_1sV2uA\0\correaa\boost-multi\test\static_array_cast.cpp + + BOOST_TEST( ref == arr ); + BOOST_TEST( arr == ref ); + } + + // // BOOST_AUTO_TEST_CASE(static_array_cast_3) + { + multi::static_array const arr = {+00, +10, +20, +30, +40}; + multi::static_array arr2 = {-00, -10, -20, -30, -40}; + + auto&& neg_arr = multi::static_array_cast>>(arr); + + BOOST_TEST( neg_arr[2] == arr2[2] ); + BOOST_TEST( arr2[2] == neg_arr[2] ); + BOOST_TEST( std::equal(begin(neg_arr), end(neg_arr), begin(arr2), end(arr2)) ); // NOLINT(modernize-use-ranges) + BOOST_TEST( neg_arr == arr2 ); + BOOST_TEST( arr2 == neg_arr ); + } + { + multi::static_array arr({4, 5}, 0); + std::iota(elements(arr).begin(), elements(arr).end(), 0); + + multi::array arr2({4, 5}); + std::transform(begin(elements(arr)), end(elements(arr)), begin(elements(arr2)), std::negate<>{}); + + auto&& neg_arr = arr.static_array_cast>(); + + BOOST_TEST( neg_arr[1][1] == arr2[1][1] ); + BOOST_TEST( arr2[1][1] == neg_arr[1][1] ); + + BOOST_TEST( std::equal(begin(arr2[1]), end(arr2[1]), begin(neg_arr[1]), end(neg_arr[1])) ); + + BOOST_TEST( arr2[1] == neg_arr[1] ); + BOOST_TEST( neg_arr[1] == arr2[1] ); + + BOOST_TEST( std::equal(begin(arr2), end(arr2), begin(neg_arr), end(neg_arr)) ); // NOLINT(modernize-use-ranges) + BOOST_TEST( neg_arr == arr2 ); + BOOST_TEST( arr2 == neg_arr ); + } + { + multi::array const arr({3, 4}, multi::uninitialized_elements_t{}); + BOOST_TEST( arr.size() == 3 ); + } + { + multi::array const arr({3, 4}); + // std::cout << arr[0][0] << std::endl; // ok, gives an error in Valgrind "Uninitialized Memory Read" + // valgrind output: + // Memory checking results: + // Uninitialized Memory Conditional - 6 + // Uninitialized Memory Read - 2 + BOOST_TEST( arr.size() == 3 ); + } + + { + multi::array const arr({3, 4}, multi::uninitialized_elements); + // std::cout << arr[0][0] << std::endl; // ok, gives an error in Valgrind "Uninitialized Memory Read" + // valgrind output: + // Memory checking results: + // Uninitialized Memory Conditional - 6 + // Uninitialized Memory Read - 2 + BOOST_TEST( arr.size() == 3 ); + } + { + // multi::array arr( {3, 4}, multi::uninitialized ); // ok, fails compilation because std::string cannot be uninitialized + // BOOST_TEST( arr.size() == 3 ); + } + + return boost::report_errors(); } diff --git a/test/std_vector_substitutability.cpp b/test/std_vector_substitutability.cpp new file mode 100644 index 000000000..d16b614b1 --- /dev/null +++ b/test/std_vector_substitutability.cpp @@ -0,0 +1,296 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array, layout_t, operator==, imp... + +#include + +#include // for equal +// IWYU pragma: no_include // for __alloc_traits<>::value_type +#include // for decay_t +#include // for vector + +namespace multi = boost::multi; + +namespace { +template // e.g. std::vector or multi::array +void resize_copy_1(std::vector const& source, DynamicArray& darr) { + darr = DynamicArray(source); +} + +template // e.g. std::vector or multi::array +void resize_copy_2(std::vector const& source, DynamicArray& darr) { + darr = DynamicArray(source.begin(), source.end()); // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) +} + +template // e.g. std::vector or multi::array +void resize_copy_3(std::vector const& source, DynamicArray& darr) { + // NOLINTNEXTLINE(fuchsia-default-arguments-calls,-warnings-as-errors) + darr = std::decay_t(source.begin(), source.end()); // testing std::vector vs multi:array +} + +template // e.g. std::vector or multi::array +void resize_copy_4(It first, It last, DynamicArray& darr) { + // NOLINTNEXTLINE(fuchsia-default-arguments-calls,-warnings-as-errors) testing std::vector vs multi:array + darr = DynamicArray(first, last); // or std::decay_t(source.begin(), source.end()) +} + +template // e.g. std::vector or multi::array +void resize_copy_5(It first, It last, DynamicArray& darr) { + darr.assign(first, last); // or std::decay_t(source.begin(), source.end()) +} + +// void resize_copy_6 ----> see below test_resize_copy_6 +} // end unnamed namespace + +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_AUTO_TEST_CASE(test_resize_copy_1) { + std::vector const source = {0, 1, 2, 3}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + + std::vector dest_v = {99, 99}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + multi::array dest_a = {88, 88}; + + BOOST_TEST( dest_v.size() == 2 ); + BOOST_TEST( dest_a.size() == 2 ); + + resize_copy_1(source, dest_v); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 3 ); + + resize_copy_1(source, dest_a); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 3 ); + } + + BOOST_AUTO_TEST_CASE(test_resize_copy_2) { + std::vector const source = {0, 1, 2, 3}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + + std::vector dest_v = {99, 99}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + multi::array dest_a = {88, 88}; + + BOOST_TEST( dest_v.size() == 2 ); + BOOST_TEST( dest_a.size() == 2 ); + + resize_copy_2(source, dest_v); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 3 ); + + resize_copy_2(source, dest_a); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 3 ); + } + + BOOST_AUTO_TEST_CASE(test_resize_copy_3) { + std::vector const source = {0, 10, 20, 30}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + + std::vector dest_v = {990, 990}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + multi::array dest_a = {880, 880}; + + BOOST_TEST( dest_v.size() == 2 ); + BOOST_TEST( dest_a.size() == 2 ); + + resize_copy_3(source, dest_v); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 30 ); + + resize_copy_3(source, dest_a); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 30 ); + } + + BOOST_AUTO_TEST_CASE(test_resize_copy_4) { + std::vector const source = {0, 10, 20, 30}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + + std::vector dest_v = {990, 990}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + multi::array dest_a = {880, 880}; + + BOOST_TEST( dest_v.size() == 2 ); + BOOST_TEST( dest_a.size() == 2 ); + + resize_copy_4(source.begin(), source.end(), dest_v); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 30 ); + + resize_copy_4(source.begin(), source.end(), dest_a); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 30 ); + } + + BOOST_AUTO_TEST_CASE(test_resize_copy_5) { + std::vector const source = {0, 10, 20, 30}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + + std::vector dest_v = {990, 990}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + multi::array dest_a = {880, 880}; + + BOOST_TEST( dest_v.size() == 2 ); + BOOST_TEST( dest_a.size() == 2 ); + + resize_copy_5(source.begin(), source.end(), dest_v); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 30 ); + + resize_copy_5(source.begin(), source.end(), dest_a); + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 30 ); + } + + BOOST_AUTO_TEST_CASE(test_resize_copy_6) { + std::vector const source = {0, 10, 20, 30}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + + std::vector dest_v = {990, 990}; // testing std::vector vs multi:array NOLINT(fuchsia-default-arguments-calls,-warnings-as-errors) + multi::array dest_a = {880, 880}; + + BOOST_TEST( dest_v.size() == 2 ); + BOOST_TEST( dest_a.size() == 2 ); + + { // look same code as below + dest_v = decltype(dest_v)(source); + } + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 30 ); + + { // look same code as above + dest_a = decltype(dest_a)(source); + } + + BOOST_TEST( dest_v.size() == 4 ); + BOOST_TEST( dest_v[3] == 30 ); + } + + BOOST_AUTO_TEST_CASE(assign_equality) { + { + multi::array const AA = {10, 20, 30}; + std::vector const aa = {10, 20, 30}; // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( std::equal(AA.begin(), AA.end(), aa.begin() ) ); + } + { + multi::array const AA = {10, 20, 30}; + std::vector const aa(AA.begin(), AA.end()); // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST( std::equal(AA.begin(), AA.end(), aa.begin() ) ); + } + { + multi::array const AA = {10, 20, 30}; + + auto const aa(AA().operator std::vector()); + + BOOST_TEST( std::equal(AA.begin(), AA.end(), aa.begin() ) ); + } + // { + // multi::array const AA = {1.0, 2.0, 3.0}; + // std::vector const aa(AA); + + // BOOST_TEST( std::equal(AA.begin(), AA.end(), aa.begin() ) ); + // } + { + std::vector const aa = {10, 20, 30}; // NOLINT(fuchsia-default-arguments-calls) + multi::array const AA(aa.begin(), aa.end()); + + BOOST_TEST( std::equal(AA.begin(), AA.end(), aa.begin() ) ); + } + { + std::vector const aa = {10, 20, 30}; // NOLINT(fuchsia-default-arguments-calls) + multi::array const AA(aa); + + BOOST_TEST( std::equal(AA.begin(), AA.end(), aa.begin() ) ); + } + } + + BOOST_AUTO_TEST_CASE(construct_from_vector_2D) { + { + multi::array const AA = { + {10, 20}, + {30, 40}, + }; + BOOST_TEST( AA.num_elements() == 4 ); + + std::vector> const aa(AA.begin(), AA.end()); // NOLINT(fuchsia-default-arguments-calls) + + BOOST_TEST(( aa[0] == multi::array{10, 20} )); + } + { + multi::array const AA = { + {10, 20}, + {30, 40}, + }; + BOOST_TEST( AA.num_elements() == 4 ); + + auto const aa(AA().operator std::vector>()); + BOOST_TEST( aa[1][1] == AA[1][1] ); + } + { + multi::array const AA = { + {10, 20}, + {30, 40}, + }; + BOOST_TEST( AA.num_elements() == 4 ); + + auto const aa = AA.operator std::vector>(); + BOOST_TEST( aa[1][1] == AA[1][1] ); + } + { + multi::array const AA = { + {10, 20}, + {30, 40}, + }; + BOOST_TEST( AA.num_elements() == 4 ); + + auto const aa = static_cast>>(AA); + BOOST_TEST( aa[1][1] == AA[1][1] ); + } + { + multi::array const AA = { + {1.0, 2.0}, + {3.0, 4.0}, + }; + + BOOST_TEST( AA.num_elements() == 4 ); + + std::vector> const aa(AA); + // std::vector> const aaa = AA; // doesn't compile, needs implicit conversion + + BOOST_TEST( aa.size() == 2 ); + + // multi::what(multi::extensions<2>(AA)); + // multi::what(multi::extensions(aa)); + // multi::array const BB({static_cast(aa.size()), static_cast(aa[0].size())}); + // std::copy(aa.begin(), aa.end(), BB.begin()); + // BB = aa; + // BOOST_TEST( BB.num_elements() == 4 ); + } + { + multi::array const AA = { + {1.0, 2.0}, + {3.0, 4.0}, + }; + BOOST_TEST( AA.num_elements() == 4 ); + } + { + multi::array const AA = { + {1.0, 2.0}, + {3.0, 4.0}, + }; + BOOST_TEST( AA.num_elements() == 4 ); + + std::vector> const aa(AA); + BOOST_TEST( aa.size() == 2 ); + } + } + return boost::report_errors(); +} diff --git a/test/subarray.cpp b/test/subarray.cpp new file mode 100644 index 000000000..3038f4e26 --- /dev/null +++ b/test/subarray.cpp @@ -0,0 +1,267 @@ +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for implicit_cast, explicit_cast + +#include + +#include // for std::is_swappable_v +#include // for as_const + +namespace multi = boost::multi; + +namespace { +auto f_arr(multi::array arr) { + return arr[2]; +} + +auto f_sub(multi::const_subarray const& arr) { + return arr[2]; +} +} // namespace + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + { + auto f_arr_ret = f_arr({1, 2, 3}); + BOOST_TEST(f_arr_ret == 3 ); + + auto f_sub_ret = f_sub({1, 2, 3}); + BOOST_TEST(f_sub_ret == 3 ); + } + + /* subarray_assignment */ + { + multi::array A1({3, 4, 5}, 99); + A1[2][1][1] = 88; + + auto constA2 = std::as_const(A1)[2]; + BOOST_TEST( constA2[1][1] == 88 ); + + auto A2 = A1[2]; + BOOST_TEST( A2[1][1] == 88 ); + + A2[1][1] = 77; + BOOST_TEST( A2[1][1] == 77 ); + } + + /* subarray_assignment */ + { + multi::array A1 = { + {{1, 2}, + {3, 4}}, + {{5, 6}, + {7, 8}}, + }; + + auto const& R0 = std::as_const(A1)[0]; + auto&& R1 = A1[1]; + + R1 = R0; + + BOOST_TEST( A1[0] == A1[1] ); + } + + /* subarray_assignment */ + { + multi::array A1 = { + {{1, 2}, + {3, 4}}, + {{5, 6}, + {7, 8}}, + }; + + auto const& R0 = A1[0]; + auto&& R1 = A1[1]; + + R1 = R0; + + BOOST_TEST( A1[0] == A1[1] ); + } + + /* subarray_base */ + { + multi::array A1({3, 4, 5}, 99); + + auto&& Asub = A1(); + *Asub.base() = 88; + + BOOST_TEST( A1[0][0][0] == 88 ); + + *A1().base() = 77; + + BOOST_TEST( A1[0][0][0] == 77 ); + + // *std::as_const(Asub).base() = 66; // should not compile, read-only + } + + /* test ref(begin, end)*/ + { + multi::array A2D = { + {1, 2}, + {3, 4}, + }; + BOOST_TEST( A2D[0][0] == 1 ); + + multi::const_subarray R2D(A2D.begin(), A2D.end()); + BOOST_TEST( R2D.addressof()== A2D.addressof() ); + } + + // equality 1D + { + multi::array const AA = {1, 2, 3}; + multi::array const BB = {2, 3, 4}; + + BOOST_TEST( AA != BB ); + BOOST_TEST( !(AA == BB ) ); + + BOOST_TEST( AA() != BB() ); + BOOST_TEST( !(AA() == BB()) ); + +#if defined(__cpp_multidimensional_subscript) && (__cpp_multidimensional_subscript >= 202110L) + BOOST_TEST( AA[] != BB[] ); +#endif + } + + // equality 2D + { + multi::array const AA = { + {1, 2}, + {3, 4}, + }; + multi::array const BB = { + {2, 3}, + {4, 5}, + }; + + BOOST_TEST( AA != BB ); + BOOST_TEST( !(AA == BB ) ); + + BOOST_TEST( AA() != BB() ); + BOOST_TEST( !(AA() == BB()) ); + } + + // equality 1D + { + multi::array const AA = {1, 2, 3}; + multi::array const BB = {2, 3, 4}; + + BOOST_TEST( AA[0] != static_cast(BB[0]) ); + + auto const to_int = [](auto elem) noexcept { + return static_cast(elem); + }; + + BOOST_TEST( AA != BB.element_transformed(to_int) ); + +#if !defined(_MSC_VER) // MSVC would warn deeply in the standard library here, TODO(correaa) make warning evident at top level lib + BOOST_TEST( AA != BB ); + BOOST_TEST( !(AA == BB ) ); + + BOOST_TEST( AA() != BB() ); + BOOST_TEST( !(AA() == BB()) ); +#endif + } + + // equality 2D + { + multi::array const AA = { + {1, 2}, + {3, 4}, + }; + multi::array const BB = { + {2, 3}, + {4, 5}, + }; + + auto const to_int = [](auto const& elem) noexcept { + return static_cast(elem); + }; + + BOOST_TEST( AA != BB.element_transformed(to_int) ); + BOOST_TEST( !(AA == BB.element_transformed(to_int)) ); + +#if !defined(_MSC_VER) // MSVC would warn deeply in the standard library here, TODO(correaa) make warning evident at top level lib + BOOST_TEST( AA != BB ); + BOOST_TEST( !(AA == BB ) ); + + BOOST_TEST( AA() != BB() ); + BOOST_TEST( !(AA() == BB()) ); +#endif + } + + /* test ref(begin, end)*/ + { + multi::array A2D = { + {1, 2}, + {3, 4}, + }; + BOOST_TEST( A2D[0][0] == 1 ); + + multi::subarray R2D(A2D.begin(), A2D.end()); + BOOST_TEST( R2D.addressof()== A2D.addressof() ); + + R2D[0][0] = 77; + BOOST_TEST( R2D[0][0] == 77 ); + } + + { // https://godbolt.org/z/a5dr7YvMz + namespace multi = boost::multi; + + // template + // struct std::is_trivially_relocatable<::boost::multi::array> : std::true_type{}; + + // template + // struct std::is_trivially_relocatable<::boost::multi::array_ref> : std::true_type{}; + + // template + // struct std::is_trivially_relocatable<::boost::multi::subarray> : std::true_type{}; + + static_assert(std::is_nothrow_move_constructible_v>); + static_assert(!std::is_trivially_copyable_v>); + static_assert(std::is_swappable_v>); + static_assert(std::is_nothrow_swappable_v>); + // static_assert( std::is_trivially_relocatable_v>); // <========== + + static_assert(!std::is_move_constructible_v>); + static_assert(!std::is_nothrow_move_constructible_v>); + +#if !defined(__NVCOMPILER) || (__NVCOMPILER_MAJOR__ >= 24) && !defined(__NVCC__) + static_assert(!std::is_copy_constructible_v>); +#endif + + static_assert(!std::is_trivially_copyable_v>); + static_assert(std::is_copy_assignable_v>); + static_assert(!std::is_trivially_copy_assignable_v>); + static_assert(!std::is_swappable_v>); // TODO(correaa) fix? swap can be called on it, and it is O(N) + // static_assert( std::is_nothrow_swappable_v >); + // static_assert( std::is_trivially_relocatable_v >); // <========== + + static_assert(std::is_move_constructible_v>); // mmm, something strange here + static_assert(std::is_nothrow_move_constructible_v>); // mmm, something strange here + +#ifndef __NVCC__ + static_assert(!std::is_copy_constructible_v>); +#endif + +#ifndef __circle_build__ + static_assert(!std::is_trivially_copyable_v>); +#endif + + static_assert(std::is_copy_assignable_v>); + static_assert(!std::is_trivially_copy_assignable_v>); + static_assert(std::is_swappable_v>); // TODO(correaa) fix? + + // static_assert( std::is_nothrow_swappable_v >); + // static_assert( std::is_trivially_relocatable_v >); // <========== + + static_assert(std::is_move_constructible_v::iterator>); + static_assert(std::is_nothrow_move_constructible_v::iterator>); + static_assert(std::is_trivially_copyable_v::iterator>); + static_assert(std::is_swappable_v::iterator>); + // static_assert( std::is_trivially_relocatable_v::iterator>); // <========== + } + + return boost::report_errors(); +} diff --git a/test/subrange.cpp b/test/subrange.cpp index c38f113f6..7038dc8ab 100644 --- a/test/subrange.cpp +++ b/test/subrange.cpp @@ -1,200 +1,303 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa +// Copyright 2018-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi subrange selection" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include +#include -#include "multi/array.hpp" +#include -#include // for std::iota +#include // for std::iota namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(multi_array_range_section) { -{ - multi::array arr({10, 20, 30, 40}, 99.0); - std::iota(arr.elements().begin(), arr.elements().end(), 0.0); - +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // multi_array_range_section) { - static_assert( decltype( arr({0, 10}, {0, 20}, {0, 30}, {0, 40}) )::rank::value == 4); - static_assert( decltype( arr( 5, {0, 20}, {0, 30}, {0, 40}) )::rank::value == 3); - static_assert( decltype( arr({0, 10}, 10, {0, 30}, {0, 40}) )::rank::value == 3); - static_assert( decltype( arr({0, 10}, {0, 20}, 15, {0, 40}) )::rank::value == 3); - static_assert( decltype( arr({0, 10}, {0, 20}, {0, 30}, 20) )::rank::value == 3); - - static_assert( decltype( arr( 5, 6, {0, 30}, {0, 40}) )::rank::value == 2); - static_assert( decltype( arr({0, 10}, 6, 15, {0, 40}) )::rank::value == 2); - static_assert( decltype( arr({0, 10}, {0, 20}, 15, 20) )::rank::value == 2); - - static_assert( decltype( arr({0, 10}, {0, 20}, {0, 30}, {0, 40}) )::rank_v == 4); - static_assert( decltype( arr( 5, {0, 20}, {0, 30}, {0, 40}) )::rank_v == 3); - static_assert( decltype( arr({0, 10}, 10, {0, 30}, {0, 40}) )::rank_v == 3); - static_assert( decltype( arr({0, 10}, {0, 20}, 15, {0, 40}) )::rank_v == 3); - static_assert( decltype( arr({0, 10}, {0, 20}, {0, 30}, 20) )::rank_v == 3); - - static_assert( decltype( arr( 5, 6, {0, 30}, {0, 40}) )::rank_v == 2); - static_assert( decltype( arr({0, 10}, 6, 15, {0, 40}) )::rank_v == 2); - static_assert( decltype( arr({0, 10}, {0, 20}, 15, 20) )::rank_v == 2); +#ifndef _MSC_VER + multi::array arr({10, 20, 30, 40}, 99.0); +#else + multi::array arr(multi::extensions_t<4>{10, 20, 30, 40}, 99.0); +#endif + std::iota(arr.elements().begin(), arr.elements().end(), 0.0); + + { + static_assert(decltype(arr({0, 10}, {0, 20}, {0, 30}, {0, 40}))::rank::value == 4); + static_assert(decltype(arr(5, {0, 20}, {0, 30}, {0, 40}))::rank::value == 3); + static_assert(decltype(arr({0, 10}, 10, {0, 30}, {0, 40}))::rank::value == 3); + static_assert(decltype(arr({0, 10}, {0, 20}, 15, {0, 40}))::rank::value == 3); + static_assert(decltype(arr({0, 10}, {0, 20}, {0, 30}, 20))::rank::value == 3); + + static_assert(decltype(arr(5, 6, {0, 30}, {0, 40}))::rank::value == 2); + static_assert(decltype(arr({0, 10}, 6, 15, {0, 40}))::rank::value == 2); + static_assert(decltype(arr({0, 10}, {0, 20}, 15, 20))::rank::value == 2); + + static_assert(decltype(arr({0, 10}, {0, 20}, {0, 30}, {0, 40}))::rank_v == 4); + static_assert(decltype(arr(5, {0, 20}, {0, 30}, {0, 40}))::rank_v == 3); + static_assert(decltype(arr({0, 10}, 10, {0, 30}, {0, 40}))::rank_v == 3); + static_assert(decltype(arr({0, 10}, {0, 20}, 15, {0, 40}))::rank_v == 3); + static_assert(decltype(arr({0, 10}, {0, 20}, {0, 30}, 20))::rank_v == 3); + + static_assert(decltype(arr(5, 6, {0, 30}, {0, 40}))::rank_v == 2); + static_assert(decltype(arr({0, 10}, 6, 15, {0, 40}))::rank_v == 2); + static_assert(decltype(arr({0, 10}, {0, 20}, 15, 20))::rank_v == 2); + } + { + auto&& all = arr({0, 10}, {0, 20}, {0, 30}, {0, 40}); + BOOST_TEST( &arr[1][2][3][4] == &all[1][2][3][4] ); + BOOST_TEST( &arr[1][2][3][4] == &arr({0, 10}, {0, 20}, {0, 30}, {0, 40})[1][2][3][4] ); + } + { + using multi::_; + auto&& all = arr({0, 10}, {0, 20}); + BOOST_TEST( &arr[1][2][3][4] == &all[1][2][3][4] ); + } + { + BOOST_TEST( &arr(0, 0, 0, 0) == &arr[0][0][0][0] ); + } + { + auto&& sub = arr({0, 5}, {0, 10}, {0, 15}, {0, 20}); + BOOST_TEST( &sub[1][2][3][4] == &arr[1][2][3][4] ); + } } { - auto&& all = arr({0, 10}, {0, 20}, {0, 30}, {0, 40}); - BOOST_REQUIRE( &arr[1][2][3][4] == &all[1][2][3][4] ); - BOOST_REQUIRE( &arr[1][2][3][4] == &arr({0, 10}, {0, 20}, {0, 30}, {0, 40})[1][2][3][4] ); + multi::array arr = { + {10, 20, 30, 40}, + {50, 60, 70, 80}, + {90, 00, 10, 20}, + {30, 40, 50, 60}, + }; + multi::array arr2 = { + {910, 920, 930, 940}, + {950, 960, 970, 980}, + {990, 900, 910, 920}, + {930, 940, 950, 960}, + }; + + arr({0, 2}, {0, 2}) = arr2({0, 2}, {0, 2}); + BOOST_TEST( arr != arr2 ); + BOOST_TEST( arr({0, 2}, {0, 2}) == arr2({0, 2}, {0, 2}) ); + BOOST_TEST( arr[1][1] == 960 ); } + + // subrange_assignment { - using multi::_; - auto&& all = arr( {0, 10} , {0, 20} ); - BOOST_REQUIRE( &arr[1][2][3][4] == &all[1][2][3][4] ); + multi::array const arr = { + {10, 20, 30, 40}, + {50, 60, 70, 80}, + {90, 00, 10, 20}, + {30, 40, 50, 60}, + }; + { + multi::array arr2 = { + {90, 90, 90}, + {90, 90, 90}, + {90, 90, 90}, + }; + arr2({0, 3}, {0, 3}) = arr({0, 3}, {0, 3}); + BOOST_TEST( arr2[1][2] == arr[1][2] ); + } + { + multi::array arr2 = { + {90, 90, 90}, + {90, 90, 90}, + {90, 90, 90}, + }; + arr2() = arr({0, 3}, {0, 3}); + BOOST_TEST( arr2[1][2] == arr[1][2] ); + BOOST_TEST( arr2() == arr({0, 3}, {0, 3}) ); + } + { + multi::array arr2 = { + {90, 90, 90}, + {90, 90, 90}, + {90, 90, 90}, + }; + arr2 = arr({0, 3}, {0, 3}); + BOOST_TEST( arr2[1][2] == arr[1][2] ); + BOOST_TEST( arr2 == arr({0, 3}, {0, 3}) ); + } } + + // subrange_ranges_sliced_1D { - BOOST_REQUIRE( &arr(0, 0, 0, 0) == &arr[0][0][0][0] ); - } - { - auto&& sub = arr({0, 5}, {0, 10}, {0, 15}, {0, 20}); - BOOST_REQUIRE( &sub[1][2][3][4] == &arr[1][2][3][4] ); - } -} -{ - multi::array arr = { - { 1.0, 2.0, 3.0, 4.0}, - { 5.0, 6.0, 7.0, 8.0}, - { 9.0, 0.0, 1.0, 2.0}, - { 3.0, 4.0, 5.0, 6.0} - }; - multi::array arr2 = { - {91.0, 92.0, 93.0, 94.0}, - {95.0, 96.0, 97.0, 98.0}, - {99.0, 90.0, 91.0, 92.0}, - {93.0, 94.0, 95.0, 96.0} - }; + multi::array arr = {1.0, 2.0, 3.0, 4.0}; - arr({0, 2}, {0, 2}) = arr2({0, 2}, {0, 2}); - BOOST_REQUIRE( arr != arr2 ); - BOOST_REQUIRE( arr({0, 2}, {0, 2}) == arr2({0, 2}, {0, 2}) ); - BOOST_REQUIRE( arr[1][1] == 96. ); -} -} + auto&& Ab = arr.sliced(1, 3); + BOOST_TEST( &Ab[0] == &arr[1] ); -BOOST_AUTO_TEST_CASE(subrange_assignment) { - multi::array const arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 0.0, 1.0, 2.0}, - {3.0, 4.0, 5.0, 6.0} - }; + auto&& Ab2 = Ab; + BOOST_TEST( &Ab2[0] == &arr[1] ); + + // auto Abb = Ab; // not allowed! + // auto Abb = std::move(Ab); (void)Abb; + + auto const& Abc = arr.sliced(1, 3); + BOOST_TEST( &Abc[0] == &arr[1] ); + + auto Aba = arr.sliced(1, 3); + BOOST_TEST( &Aba[0] == &arr[1] ); + } + + // subrange_ranges_strided_1D { - multi::array arr2 = { - {9.0, 9.0, 9.0}, - {9.0, 9.0, 9.0}, - {9.0, 9.0, 9.0} - }; - arr2({0, 3}, {0, 3}) = arr({0, 3}, {0, 3}); - BOOST_REQUIRE( arr2[1][2] == arr[1][2] ); + multi::array const arr = {1, 2, 3, 4}; + auto const& As = arr.strided(2); + BOOST_TEST( As.begin() < As.end() ); + + auto const& Arev = arr.sliced(3, 0, -1); + + BOOST_TEST(Arev.size() == 3 ); + + BOOST_TEST(Arev[0] == 4 ); + BOOST_TEST(Arev[1] == 3 ); + BOOST_TEST(Arev[2] == 2 ); + + BOOST_TEST( Arev.begin() < Arev.end() ); + BOOST_TEST( Arev.begin() <= Arev.end() ); + BOOST_TEST( Arev.begin() != Arev.end() ); + BOOST_TEST( !(Arev.begin() == Arev.end()) ); + BOOST_TEST( Arev.begin() == Arev.begin() ); + BOOST_TEST( !(Arev.begin() != Arev.begin()) ); + + BOOST_TEST( Arev.end() > Arev.begin() ); + BOOST_TEST( Arev.end() >= Arev.begin() ); + BOOST_TEST( Arev.end() == Arev.end() ); + BOOST_TEST( !(Arev.end() != Arev.end()) ); } + + // subrange_ranges_sliced { - multi::array arr2 = { - {9.0, 9.0, 9.0}, - {9.0, 9.0, 9.0}, - {9.0, 9.0, 9.0} + multi::array arr = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 0.0, 1.0, 2.0}, + {3.0, 4.0, 5.0, 6.0}, }; - arr2() = arr({0, 3}, {0, 3}); - BOOST_REQUIRE( arr2[1][2] == arr[1][2] ); - BOOST_REQUIRE( arr2() == arr({0, 3}, {0, 3}) ); + auto&& Ab = arr.sliced(0, 3); + BOOST_TEST( &Ab[2][2] == &arr[2][2] ); + + auto const& Abc = arr.sliced(0, 3); + BOOST_TEST( &Abc[2][2] == &arr[2][2] ); + + auto AB = arr.sliced(0, 3); + BOOST_TEST( &AB[2][2] == &arr[2][2] ); } - { - multi::array arr2 = { - {9.0, 9.0, 9.0}, - {9.0, 9.0, 9.0}, - {9.0, 9.0, 9.0} + + // subrange_ranges + { + multi::array arr = { + {1.0, 2.0, 3.0, 4.0}, + {5.0, 6.0, 7.0, 8.0}, + {9.0, 0.0, 1.0, 2.0}, + {3.0, 4.0, 5.0, 6.0}, }; - arr2 = arr({0, 3}, {0, 3}); - BOOST_REQUIRE( arr2[1][2] == arr[1][2] ); - BOOST_REQUIRE( arr2 == arr({0, 3}, {0, 3}) ); + auto&& Ab = arr({0, 3}, {0, 3}); + BOOST_TEST( &Ab[2][2] == &arr[2][2] ); + + auto const& Abc = arr({0, 3}, {0, 3}); + BOOST_TEST( &Abc[2][2] == &arr[2][2] ); + + auto AB = arr({0, 3}, {0, 3}); + BOOST_TEST( &AB[2][2] == &arr[2][2] ); } -} -BOOST_AUTO_TEST_CASE(subrange_ranges_sliced_1D) { - multi::array arr = {1.0, 2.0, 3.0, 4.0}; - auto&& Ab = arr.sliced(1, 3); - BOOST_REQUIRE( &Ab[0] == &arr[1] ); + // subrange_1D_issue129) + { + multi::array arr({1024}, int{}); + std::iota(arr.elements().begin(), arr.elements().end(), 0); - auto&& Ab2 = Ab; - BOOST_REQUIRE( &Ab2[0] == &arr[1] ); + BOOST_TEST( arr.sliced(0, 512, 2)[ 1] == 2 ); + BOOST_TEST( arr.sliced(0, 512, 2)[255] == 510 ); -// auto Abb = Ab; // not allowed! -// auto Abb = std::move(Ab); (void)Abb; + BOOST_TEST( arr.sliced(0, 512)[ 1] == 1 ); + BOOST_TEST( arr.sliced(0, 512)[511] == 511 ); - auto const& Abc = arr.sliced(1, 3); - BOOST_REQUIRE( &Abc[0] == &arr[1] ); + BOOST_TEST( arr({0, 512})[ 1] == 1 ); + BOOST_TEST( arr({0, 512})[511] == 511 ); - auto Aba = arr.sliced(1, 3); - BOOST_REQUIRE( &Aba[0] == &arr[1] ); -} + // BOOST_TEST( arr({0, 512, 2})[ 1] == 2 ); // TODO(correaa) coompilation error + // BOOST_TEST( arr({0, 512, 2})[255] == 510 ); // TODO(correaa) coompilation error + } -BOOST_AUTO_TEST_CASE(subrange_ranges_sliced) { - multi::array arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 0.0, 1.0, 2.0}, - {3.0, 4.0, 5.0, 6.0} - }; - auto&& Ab = arr.sliced(0, 3); - BOOST_REQUIRE( &Ab[2][2] == &arr[2][2] ); + // subrange_2D_issue129 + { + multi::array arr({66, 1024}, int{}); + std::iota(arr.elements().begin(), arr.elements().end(), 0); - auto const& Abc = arr.sliced(0, 3); - BOOST_REQUIRE( &Abc[2][2] == &arr[2][2] ); + BOOST_TEST( arr[0].sliced(0, 512, 2)[ 1] == 2 ); + BOOST_TEST( arr[0].sliced(0, 512, 2)[255] == 510 ); - auto AB = arr.sliced(0, 3); - BOOST_REQUIRE( &AB[2][2] == &arr[2][2] ); -} + BOOST_TEST( arr[0].sliced(0, 512)[ 1] == 1 ); + BOOST_TEST( arr[0].sliced(0, 512)[511] == 511 ); + + BOOST_TEST( arr(0, {0, 512})[ 1] == 1 ); + BOOST_TEST( arr(0, {0, 512})[511] == 511 ); -BOOST_AUTO_TEST_CASE(subrange_ranges) { - multi::array arr = { - {1.0, 2.0, 3.0, 4.0}, - {5.0, 6.0, 7.0, 8.0}, - {9.0, 0.0, 1.0, 2.0}, - {3.0, 4.0, 5.0, 6.0} + // BOOST_TEST( arr(0, {0, 512, 2})[ 1] == 2 ); // TODO(correaa) coompilation error + // BOOST_TEST( arr(0, {0, 512, 2})[255] == 510 ); // TODO(correaa) coompilation error + } + + class rng3_t { + int start_; + int finish_; + + public: // NOLINT(whitespace/indent) bug in cpplint + rng3_t(int start, int finish) : start_{start}, finish_{finish} {} // NOLINT(bugprone-easily-swappable-parameters) + auto first() const { return start_; } + auto last() const { return finish_; } }; - auto&& Ab = arr({0, 3}, {0, 3}); - BOOST_REQUIRE( &Ab[2][2] == &arr[2][2] ); - auto const& Abc = arr({0, 3}, {0, 3}); - BOOST_REQUIRE( &Abc[2][2] == &arr[2][2] ); + // subrange_start_finish + { + multi::array arr = { + { 1.0, 2.0}, + { 3.0, 4.0}, + { 5.0, 6.0}, + { 7.0, 8.0}, + { 9.0, 10.0}, + {11.0, 12.0}, + {13.0, 14.0}, + }; + BOOST_TEST( &arr({2, 5}, 1)[0] == &arr[2][1] ); - auto AB = arr({0, 3}, {0, 3}); - BOOST_REQUIRE( &AB[2][2] == &arr[2][2] ); -} + multi::irange const rng(2, 5); + BOOST_TEST( &arr(rng, 1)[0] == &arr[2][1] ); -BOOST_AUTO_TEST_CASE(subrange_1D_issue129) { - multi::array arr({1024}, double{}); - std::iota(arr.elements().begin(), arr.elements().end(), 0.0); + struct : multi::irange { + using multi::irange::irange; + } const rng2(2, 5); - BOOST_REQUIRE( arr.sliced(0, 512, 2)[ 1] == 2.0 ); - BOOST_REQUIRE( arr.sliced(0, 512, 2)[255] == 510.0 ); + BOOST_TEST( &arr(rng2, 1)[0] == &arr[2][1] ); - BOOST_REQUIRE( arr.sliced(0, 512)[ 1] == 1.0 ); - BOOST_REQUIRE( arr.sliced(0, 512)[511] == 511.0 ); + rng3_t const rng3{2, 5}; - BOOST_REQUIRE( arr({0, 512})[ 1] == 1.0 ); - BOOST_REQUIRE( arr({0, 512})[511] == 511.0 ); + multi::irange const rng4(rng3); -// BOOST_REQUIRE( arr({0, 512, 2})[ 1] == 2. ); // TODO(correaa) coompilation error -// BOOST_REQUIRE( arr({0, 512, 2})[255] == 510. ); // TODO(correaa) coompilation error -} + BOOST_TEST( &arr(rng4, 1)[0] == &arr[2][1] ); + BOOST_TEST( &arr(rng3, 1)[0] == &arr[2][1] ); + } -BOOST_AUTO_TEST_CASE(subrange_2D_issue129) { - multi::array arr({66, 1024}, double{}); - std::iota(arr.elements().begin(), arr.elements().end(), 0.0); + // elements random access + { + multi::array arr = { + {0, 1, 2}, + {3, 4, 5}, + {6, 7, 8}, + }; - BOOST_REQUIRE( arr[0].sliced(0, 512, 2)[ 1] == 2.0 ); - BOOST_REQUIRE( arr[0].sliced(0, 512, 2)[255] == 510.0 ); + auto const it = arr.elements().begin() + 5; + BOOST_TEST( *it == 5 ); + BOOST_TEST( *(it + 2) == 7 ); + } - BOOST_REQUIRE( arr[0].sliced(0, 512)[ 1] == 1.0 ); - BOOST_REQUIRE( arr[0].sliced(0, 512)[511] == 511.0 ); + { + multi::array arr = {0, 1, 2, 3, 4, 5}; - BOOST_REQUIRE( arr(0, {0, 512})[ 1] == 1.0 ); - BOOST_REQUIRE( arr(0, {0, 512})[511] == 511.0 ); + auto const it = arr.elements().begin() + 3; + BOOST_TEST( *it == 3 ); + BOOST_TEST( *(it + 2) == 5 ); + } -// BOOST_REQUIRE( arr(0, {0, 512, 2})[ 1] == 2. ); // TODO(correaa) coompilation error -// BOOST_REQUIRE( arr(0, {0, 512, 2})[255] == 510. ); // TODO(correaa) coompilation error + return boost::report_errors(); } diff --git a/test/swap.cpp b/test/swap.cpp new file mode 100644 index 000000000..b52bbe19b --- /dev/null +++ b/test/swap.cpp @@ -0,0 +1,104 @@ +// Copyright 2020-2024 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 10. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array, apply, array_types<>::ele... + +#include + +#include // for swap // IWYU pragma: keep // for std::swap +// IWYU pragma: no_include // for swap +// IWYU pragma: no_include // for swap + +namespace multi = boost::multi; + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + // swap_array_1D) + { + multi::array arr1 = {0, 1, 2, 3}; + multi::array arr2 = {100, 101, 102}; + + using std::swap; + swap(arr1, arr2); + + BOOST_TEST( arr1[1] == 101 ); + BOOST_TEST( arr2[1] == 1 ); + } + + // BOOST_AUTO_TEST_CASE(swap_array_2D) + { + multi::array arr1 = { + {00, 01, 02, 03}, + {10, 11, 12, 13}, + {20, 21, 22, 23}, + }; + + multi::array arr2 = { + {100, 101, 102, 103}, + {110, 111, 112, 113}, + }; + + using std::swap; + swap(arr1, arr2); + + BOOST_TEST( arr1[1][1] == 111 ); + BOOST_TEST( arr2[1][1] == 11 ); + } + + // BOOST_AUTO_TEST_CASE(swap_subarray_1D) + { + multi::array arr1 = {0, 1, 2, 3}; + multi::array arr2 = {100, 101, 102, 103}; + + using std::swap; + swap(arr1(), arr2()); + + BOOST_TEST( arr1[1] == 101 ); + BOOST_TEST( arr2[1] == 1 ); + } + + // BOOST_AUTO_TEST_CASE(swap_subarray_2D) + { + multi::array arr1 = { + {00, 01, 02, 03}, + {10, 11, 12, 13}, + {20, 21, 22, 23}, + }; + + multi::array arr2 = { + {100, 101, 102, 103}, + {110, 111, 112, 113}, + {120, 121, 122, 123}, + }; + + using std::swap; + swap(arr1(), arr2()); + + BOOST_TEST( arr1[1][1] == 111 ); + BOOST_TEST( arr2[1][1] == 11 ); + } + + // BOOST_AUTO_TEST_CASE(swap_const_subarray_2D) + { + multi::array const arr1 = { + {00, 01, 02, 03}, + {10, 11, 12, 13}, + {20, 21, 22, 23}, + }; + + multi::array arr2 = { + {100, 101, 102, 103}, + {110, 111, 112, 113}, + {120, 121, 122, 123}, + }; + + // using std::swap; + // swap(arr1(), arr2()); + + BOOST_TEST( arr1[1][1] == 11 ); + BOOST_TEST( arr2[1][1] == 111 ); + } + + return boost::report_errors(); +} diff --git a/test/thrust_array.cu b/test/thrust_array.cu index a79441aac..6b5b1ccb2 100644 --- a/test/thrust_array.cu +++ b/test/thrust_array.cu @@ -1,62 +1,66 @@ #ifdef COMPILATION_INSTRUCTIONS -clang++ --cuda-gpu-arch=sm_52`#nvcc --expt-relaxed-constexpr` -std=c++14 $0 -o $0x -lcudart&& $0x && rm $0x; exit +clang++ --cuda - gpu - arch = sm_52`#nvcc-- expt - relaxed - constexpr` - std = c++ 14 $0 - o $0x - lcudart && $0x && rm $0x; +exit #endif -#include "../adaptors/thrust/allocator_traits.hpp" #include "../adaptors/thrust/algorithms.hpp" +#include "../adaptors/thrust/allocator_traits.hpp" #include "../array.hpp" -#include #include +#include #include -#include // iota -#include +#include +#include // iota namespace multi = boost::multi; template using thrust_array = multi::array>; - - -int main(){ -// using Alloc = thrust::device_allocator; +int main() { + // using Alloc = thrust::device_allocator; using Alloc = std::allocator; { Alloc all; - auto p = all.allocate(10); + auto p = all.allocate(10); all.deallocate(p, 10); auto&& v = p[2]; - v = 45.; - assert( v == 45. ); - assert( p[2] == 45. ); + v = 45.; + assert(v == 45.); + assert(p[2] == 45.); } - multi::array A(100, 11.); - assert(A[20]==11.); - A[20] = 44.; + multi::array A(100, 11.); + assert(A[20] == 11.); + A[20] = 44.0; multi::array> BB(10, 99.); - multi::array, 1, thrust::device_allocator >> BBB(10, 99.); - multi::array, 1, thrust::device_allocator >> BBB_cpy = BBB; + multi::array, 1, thrust::device_allocator>> BBB(10, 99.); + multi::array, 1, thrust::device_allocator>> BBB_cpy = BBB; - assert( static_cast>(BBB[0]) == std::complex(99.) ); - -// assert( B[2] == 99. ); - thrust_array B(100, 11.); - B[20] = 11.; + assert(static_cast>(BBB[0]) == std::complex(99.)); + + // assert( B[2] == 99. ); + thrust_array B(100, 11.); + B[20] = 11.0; std::cout << B[20] << std::endl; - assert( B[20] == 11. ); + assert(B[20] == 11.); thrust_array C(100); thrust::copy(begin(B), end(B), begin(C)); - assert(C[20]==11.); + assert(C[20] == 11.); - multi::array> A2({10,10}); - multi::array> B2({10,10}); + multi::array> A2({10, 10}); + multi::array> B2({10, 10}); A2[5][0] = 50.; thrust::copy(begin(rotated(A2)[0]), end(rotated(A2)[0]), begin(rotated(B2)[0])); - assert(B2[5][0] == 50. ); + assert(B2[5][0] == 50.0); + + static_assert( std::is_same_v >, multi::thrust::device_array> ); + + multi::thrust::device_array darr = [](multi::index i, multi::index j) __device__ { return i + j; } ^ multi::extensions_t<2>(10, 10); +dssas #if 0 multi::array A_host({100}, 99.); @@ -94,15 +98,15 @@ int main(){ std::cout << A_host[20] << std::endl; assert(A_host[20] == 44. ); - // H has storage for 4 integers - thrust::host_vector H(4); + // H has storage for 4 integers + thrust::host_vector H(4); + + // initialize individual elements + H[0] = 14; + H[1] = 20; + H[2] = 38; + H[3] = 46; - // initialize individual elements - H[0] = 14; - H[1] = 20; - H[2] = 38; - H[3] = 46; - multi::array> H2(4, 99); assert(size(H2) == 4); assert( H2[2] == 99 ); copy( begin(H), end(H), begin(H2) ); @@ -134,5 +138,3 @@ int main(){ // H and D are automatically deleted when the function returns #endif } - - diff --git a/test/transform.cpp b/test/transform.cpp index e82e49e77..49e61f74f 100644 --- a/test/transform.cpp +++ b/test/transform.cpp @@ -1,50 +1,89 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa - -#include - -#include +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array, subarray, static_array + +#include + +#include // for array // IWYU pragma: keep +#include // for complex, operator*, operator+ +#include // for ptrdiff_t +#include // for abs +#include // for negate // IWYU pragma: keep +#include // for iterator_traits +#include // for pointer_traits +#include // for to_string // IWYU pragma: keep // NOLINT(misc-include-cleaner) +#include // for decay_t, conditional_t, true_type +#include // for move, declval + +#ifdef _MSC_VER +#pragma warning(disable : 4626) // assignment operator was implicitly defined as deleted +#endif -#include +#define BOOST_MULTI_DECLRETURN(ExpR) \ + ->decltype(ExpR) { return ExpR; } // NOLINT(cppcoreguidelines-macro-usage) saves a lot of typing namespace test { -constexpr struct neg_t { + +struct neg_t { template constexpr auto operator()(T const& value) const -> decltype(-value) { return -value; } -} neg; +}; +constexpr inline neg_t neg; + } // end namespace test namespace test { -template class involuter; - template class involuted { Ref r_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) friend auto underlying(involuted& self) -> decltype(auto) { return self.r_; } - friend auto underlying(involuted&& self) -> decltype(auto) { return self.r_; } + friend auto underlying(involuted&& self) -> decltype(auto) { return std::move(self).r_; } friend auto underlying(involuted const& self) -> decltype(auto) { return self.r_; } public: using decay_type = std::decay_t()(std::declval()))>; - constexpr involuted(Involution /*stateless*/, Ref ref) : r_{std::forward(ref)} {} + constexpr involuted(Involution /*stateless*/, Ref ref) : r_{ref} {} + involuted(involuted const&) = default; + involuted(involuted&&) noexcept = default; + + auto operator=(involuted&&) -> involuted& = delete; + auto operator=(involuted const&) -> involuted& = default; auto operator=(decay_type const& other) -> involuted& { // NOLINT(fuchsia-trailing-return) simulate reference r_ = Involution{}(other); return *this; } constexpr explicit operator decay_type() const { return Involution{}(r_); } // NOLINTNEXTLINE(google-runtime-operator): simulated reference - constexpr auto operator&() && { return involuter())>{Involution{}, &r_}; } // NOLINT(runtime/operator) + // constexpr auto operator&() && { return involuter())>{Involution{}, &r_}; } // NOLINT(runtime/operator) // NOLINTNEXTLINE(google-runtime-operator): simulated reference - constexpr auto operator&() & { return involuter())>{Involution{}, &r_}; } // NOLINT(runtime/operator) + // constexpr auto operator&() & { return involuter())>{Involution{}, &r_}; } // NOLINT(runtime/operator) // NOLINTNEXTLINE(google-runtime-operator): simulated reference - constexpr auto operator&() const& { return involuter())>{Involution{}, &r_}; } // NOLINT(runtime/operator) + // constexpr auto operator&() const& { // NOLINT(runtime/operator) + // return involuter())>{Involution{}, &r_}; + // } auto operator==(involuted const& other) const { return r_ == other.r_; } auto operator!=(involuted const& other) const { return r_ == other.r_; } +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif auto operator==(decay_type const& other) const { return Involution{}(r_) == other; } auto operator!=(decay_type const& other) const { return Involution{}(r_) != other; } +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + ~involuted() = default; }; template @@ -68,7 +107,8 @@ class involuter { explicit involuter(involuter const& other) : it_{other.it_} {} constexpr auto operator*() const { return reference{Involution{}, *it_}; } - constexpr auto operator->() const { return pointer{&*it_}; } + // cppcheck-suppress redundantPointerOp ; lib idiom + constexpr auto operator->() const { return &*it_; } // pointer{&*it_}; } constexpr auto operator==(involuter const& other) const { return it_ == other.it_; } constexpr auto operator!=(involuter const& other) const { return it_ != other.it_; } @@ -82,40 +122,61 @@ class involuter { return *this; } - constexpr auto operator+(difference_type n) const { return involuter{it_ + n}; } - constexpr auto operator-(difference_type n) const { return involuter{it_ - n}; } +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + + constexpr auto operator+(difference_type n) const { return involuter{it_ + n}; } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + constexpr auto operator-(difference_type n) const { return involuter{it_ - n}; } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + friend constexpr auto operator+(difference_type n, involuter const& self) { return self + n; } // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) +#ifdef __clang__ +#pragma clang diagnostic pop +#endif }; template using negated = involuted, Ref>; template using negater = involuter, It>; class basic_conjugate_t { + // clang-format off template struct prio : std::conditional_t, std::true_type> {}; - template static auto _(prio<0> /**/, T const& value) DECLRETURN(std::conj(value)) template static auto _(prio<1> /**/, T const& value) DECLRETURN(conj(value)) template static auto _(prio<2> /**/, T const& value) DECLRETURN(T::conj(value)) template static auto _(prio<3> /**/, T const& value) DECLRETURN(value.conj()) - public : template - static auto _(T const& value) DECLRETURN(_(prio<3>{}, value)) + template static auto _(prio<0> /**/, T const& value) BOOST_MULTI_DECLRETURN( std::conj(value)) + template static auto _(prio<1> /**/, T const& value) BOOST_MULTI_DECLRETURN( conj(value)) + template static auto _(prio<2> /**/, T const& value) BOOST_MULTI_DECLRETURN( T::conj(value)) + template static auto _(prio<3> /**/, T const& value) BOOST_MULTI_DECLRETURN(value.conj() ) + + public: + template + static auto _(T const& value) BOOST_MULTI_DECLRETURN(_(prio<3>{}, value)) + // clang-format on }; template struct conjugate : private basic_conjugate_t { - constexpr auto operator()(T const& arg) const DECLRETURN(_(arg)) + constexpr auto operator()(T const& arg) const BOOST_MULTI_DECLRETURN(_(arg)) }; template<> struct conjugate<> : private basic_conjugate_t { template - constexpr auto operator()(T const& arg) const DECLRETURN(_(arg)) + constexpr auto operator()(T const& arg) const BOOST_MULTI_DECLRETURN(_(arg)) }; -#if defined(__NVCC__) +#if defined(__NVCC__) && !defined(_MSC_VER) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsubobject-linkage" #endif + template struct conjd : test::involuted, ComplexRef> { explicit conjd(ComplexRef ref) : test::involuted, ComplexRef>(conjugate<>{}, ref) {} - auto real() const { return underlying(*this).real(); } - auto imag() const { return negated, ComplexRef> const&>()).imag())>{std::negate<>{}, underlying(*this).imag()}; } + auto real() const { return underlying(*this).real(); } + auto imag() const { + return negated, ComplexRef> const&>()).imag())>{std::negate<>{}, underlying(*this).imag()}; + } friend auto real(conjd const& self) -> decltype(auto) { using std::real; return real(static_cast(self)); @@ -125,12 +186,13 @@ template struct conjd : test::involuted, ComplexRe return imag(static_cast(self)); } }; -#if defined(__NVCC__) + +#if defined(__NVCC__) && !defined(_MSC_VER) #pragma GCC diagnostic pop #endif -#if defined(__cpp_deduction_guides) -template conjd(T&&) -> conjd; +#ifdef __cpp_deduction_guides +template conjd(T&&) -> conjd; // NOLINT(misc-use-internal-linkage) bug in clang-tidy 19 #endif template using conjr = test::involuter, Complex>; @@ -154,118 +216,195 @@ class indirect_real { } // namespace test -BOOST_AUTO_TEST_CASE(transformed_array) { - namespace multi = boost::multi; - { - using complex = std::complex; - complex cee{1.0, 2.0}; +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ - auto&& zee = test::conjd{cee}; - BOOST_REQUIRE(( zee == complex{1.0, -2.0} )); +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + static_assert(std::is_same_v, int>); + static_assert(std::is_same_v, int const&>); - BOOST_REQUIRE( real(zee) == 1.0 ); - BOOST_REQUIRE( imag(zee) == -2.0 ); - BOOST_REQUIRE( zee.real() == 1.0 ); - BOOST_REQUIRE( zee.imag() == -2.0 ); - } - { - double doub = 5.0; + BOOST_AUTO_TEST_CASE(transformed_array) { + namespace multi = boost::multi; + { + using complex = std::complex; + complex cee{1.0, 2.0}; - auto&& negd_a = test::involuted(test::neg, doub); - BOOST_REQUIRE( negd_a == -5.0 ); + auto&& zee = test::conjd{cee}; + BOOST_TEST(( zee == complex{1.0, -2.0} )); - negd_a = 10.0; - BOOST_REQUIRE( negd_a == 10.0 ); - BOOST_REQUIRE( doub = -10.0 ); - } - { - multi::array arr = {0.0, 1.0, 2.0, 3.0, 4.0}; - auto&& ref = arr.static_array_cast(); - BOOST_REQUIRE( ref[2] == arr[2] ); - } + BOOST_TEST( std::abs( real(zee) - +1.0 ) < 1E-6); + BOOST_TEST( std::abs( imag(zee) - -2.0 ) < 1E-6); + } + { + int val = 50; - { - multi::array const arr = {+0.0, +1.0, +2.0, +3.0, +4.0}; - multi::array neg = {-0.0, -1.0, -2.0, -3.0, -4.0}; - auto&& negd_arr = arr.static_array_cast>(); - BOOST_REQUIRE( negd_arr[2] == neg[2] ); - } - { - multi::array const arr = { - { +0.0, +1.0, +2.0, +3.0, +4.0}, - { +5.0, +6.0, +7.0, +8.0, +9.0}, - {+10.0, +11.0, +12.0, +13.0, +14.0}, - {+15.0, +16.0, +17.0, +18.0, +19.0}, - }; - multi::array neg = { - { -0.0, -1.0, -2.0, -3.0, -4.0}, - { -5.0, -6.0, -7.0, -8.0, -9.0}, - {-10.0, -11.0, -12.0, -13.0, -14.0}, - {-15.0, -16.0, -17.0, -18.0, -19.0}, - }; - auto&& negd_arr = arr.static_array_cast>(); - BOOST_REQUIRE( negd_arr[1][1] == neg[1][1] ); - } - { -#if defined(__cpp_deduction_guides) - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : testing legacy types - double zee[4][5]{ - { 0.0, 1.0, 2.0, 3.0, 4.0}, - { 5.0, 6.0, 7.0, 8.0, 9.0}, - {10.0, 11.0, 12.0, 13.0, 14.0}, - {15.0, 16.0, 17.0, 18.0, 19.0}, - }; - auto&& d2DC = multi::make_array_ref(test::involuter{test::neg, &zee[0][0]}, {4, 5}); + auto&& negd_a = test::involuted(test::neg, val); + BOOST_TEST( negd_a == -50 ); - d2DC[1][1] = -66.0; - BOOST_REQUIRE( zee[1][1] == 66.0 ); -#endif + negd_a = 100; + BOOST_TEST( negd_a == 100 ); + BOOST_TEST( val == -100 ); + } { - using complex = std::complex; + multi::array arr = {0.0, 1.0, 2.0, 3.0, 4.0}; + auto&& ref = arr.static_array_cast(); + BOOST_TEST( std::abs(ref[2] - arr[2]) < 1E-6 ); + } + { + multi::array const arr = {+0.0, +1.0, +2.0, +3.0, +4.0}; + multi::array neg = {-0.0, -1.0, -2.0, -3.0, -4.0}; + auto&& negd_arr = arr.static_array_cast>(); + BOOST_TEST( negd_arr[2] == neg[2] ); + } + { + multi::array const arr = { + { +0.0, +1.0, +2.0, +3.0, +4.0}, + { +5.0, +6.0, +7.0, +8.0, +9.0}, + {+10.0, +11.0, +12.0, +13.0, +14.0}, + {+15.0, +16.0, +17.0, +18.0, +19.0}, + }; + multi::array neg = { + { -0.0, -1.0, -2.0, -3.0, -4.0}, + { -5.0, -6.0, -7.0, -8.0, -9.0}, + {-10.0, -11.0, -12.0, -13.0, -14.0}, + {-15.0, -16.0, -17.0, -18.0, -19.0}, + }; + // auto&& negd_arr = arr.static_array_cast>(); // not compile, ok, read only + auto&& negd_arr = arr.static_array_cast>(); + BOOST_TEST( negd_arr[1][1] == neg[1][1] ); + BOOST_TEST( negd_arr[1][1] == -6.0 ); + // negd_arr2[1][1] = 3.0; // can't compile, ok, read-only + } + { +#ifdef __cpp_deduction_guides + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif - multi::array d2D = { - { {0.0, 3.0}, {1.0, 9.0}, {2.0, 4.0}, {3.0, 0.0}, {4.0, 0.0}}, - { {5.0, 0.0}, {6.0, 3.0}, {7.0, 5.0}, {8.0, 0.0}, {9.0, 0.0}}, - { {1.0, 4.0}, {9.0, 1.0}, {12.0, 0.0}, {13.0, 0.0}, {14.0, 0.0}}, - {{15.0, 0.0}, {16.0, 0.0}, {17.0, 0.0}, {18.0, 0.0}, {19.0, 0.0}}, + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : testing legacy types + double zee[4][5]{ + { 0.0, 1.0, 2.0, 3.0, 4.0}, + { 5.0, 6.0, 7.0, 8.0, 9.0}, + {10.0, 11.0, 12.0, 13.0, 14.0}, + {15.0, 16.0, 17.0, 18.0, 19.0}, }; + auto&& d2DC = multi::make_array_ref(test::involuter{test::neg, &zee[0][0]}, {4, 5}); - auto&& d2Dreal = d2D.reinterpret_array_cast(); - BOOST_REQUIRE( d2Dreal[2][1] == 9.0 ); + d2DC[1][1] = -66.0; - d2Dreal[2][1] = 12.0; - BOOST_REQUIRE( d2D[2][1] == complex(12.0, 1.0) ); + BOOST_TEST( std::abs( zee[1][1] - 66.0) < 1E-6 ); - auto&& d2DrealT = rotated(d2D).reinterpret_array_cast(); - BOOST_REQUIRE( d2DrealT[2][1] == 7.0 ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif - multi::array const d2Dreal_copy = d2D.template reinterpret_array_cast(); - BOOST_REQUIRE( d2Dreal_copy == d2Dreal ); +#endif + { + using complex = std::complex; + + multi::array d2D = { + { {0.0, 3.0}, {1.0, 9.0}, {2.0, 4.0}, {3.0, 0.0}, {4.0, 0.0}}, + { {5.0, 0.0}, {6.0, 3.0}, {7.0, 5.0}, {8.0, 0.0}, {9.0, 0.0}}, + { {1.0, 4.0}, {9.0, 1.0}, {12.0, 0.0}, {13.0, 0.0}, {14.0, 0.0}}, + {{15.0, 0.0}, {16.0, 0.0}, {17.0, 0.0}, {18.0, 0.0}, {19.0, 0.0}}, + }; + + auto&& d2Dreal = d2D.reinterpret_array_cast(); + BOOST_TEST( std::abs( d2Dreal[2][1] - 9.0) < 1E-6 ); + + d2Dreal[2][1] = 12.0; + BOOST_TEST( d2D[2][1] == complex(12.0, 1.0) ); + + auto&& d2DrealT = d2D.rotated().reinterpret_array_cast(); + BOOST_TEST( std::abs( d2DrealT[2][1] - 7.0) < 1E-6); + + multi::array const d2Dreal_copy{d2D.template reinterpret_array_cast()}; + BOOST_TEST( d2Dreal_copy == d2Dreal ); + } + { + using complex = std::complex; + + auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit + + multi::array arr = { + {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, + {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, + }; + auto conjd_arr = arr.static_array_cast>(); + BOOST_TEST( conjd_arr[1][2] == conj(arr[1][2]) ); + } } - { - using complex = std::complex; + } - auto const I = complex{0.0, 1.0}; // NOLINT(readability-identifier-length) imaginary unit +#if !defined(__NVCC__) && defined(__GNU_MINOR__) && (__GNUC_MINOR__ > 7) + BOOST_AUTO_TEST_CASE(transformed_to_string) { + namespace multi = boost::multi; - multi::array arr = { - {1.0 + 3.0 * I, 3.0 - 2.0 * I, 4.0 + 1.0 * I}, - {9.0 + 1.0 * I, 7.0 - 8.0 * I, 1.0 - 3.0 * I}, - }; - auto conjd_arr = arr.static_array_cast>(); - BOOST_REQUIRE( conjd_arr[1][2] == conj(arr[1][2]) ); - } + multi::array const AA = { + {1, 2}, + {3, 4}, + }; + multi::array BB = AA.element_transformed([](int ee) noexcept { return std::to_string(ee); }); + + BOOST_TEST( BB[1][1] == "4" ); } -} +#endif + + /* accumulate rows */ + { + namespace multi = boost::multi; -#if not defined(__NVCC__) and (__GNUC_MINOR__ > 7) -BOOST_AUTO_TEST_CASE(transformed_to_string) { - namespace multi = boost::multi; - multi::array const AA = { - {1, 2}, - {3, 4}, - }; - multi::array BB = AA.element_transformed([](int ee) noexcept { return std::to_string(ee); }); + multi::array Arr({100, 200}, 1); + + multi::array vv({200}, 0); + for(auto i : Arr.extension()) { // NOLINT(altera-unroll-loops) + for(auto j : vv.extension()) { // NOLINT(altera-unroll-loops) + vv[j] += Arr[i][j]; + } + } - BOOST_REQUIRE( BB[1][1] == "4" ); + // auto const v = std::reduce( + // A.begin(), A.end(), + // multi::array({200}, 0), + // [](auto&& acc, auto&& row) { + // std::transform( + // acc.begin(), vv.end(), row.begin(), acc.begin(), + // [](auto const& e1, auto const& e2) { + // return e1 + e2; + // } + // ); + // return acc; + // } + // ); + + // auto const v = std::transform_reduce( + // A.begin(), A.end(), + // multi::array({200}, 0), + // [](auto&& vv, auto&& row) { + // std::transform( + // vv.begin(), vv.end(), row.begin(), vv.begin(), + // [](auto const& e1, auto const& e2) { + // return e1 + e2; + // } + // ); + // return vv; + // }, + // [](auto&& vv) {return vv;} + // ); + + // multi::array v({200}, 0); + + // std::transform( + // (~A).begin(), (~A).end(), v.begin(), + // [](auto const& col) { return std::reduce(col.begin(), col.end()); } + // ); + + BOOST_TEST(vv[0] == 100); + } + + return boost::report_errors(); } -#endif + +#undef BOOST_MULTI_DECLRETURN diff --git a/test/utility.cpp b/test/utility.cpp index 59bf51469..0966360a8 100644 --- a/test/utility.cpp +++ b/test/utility.cpp @@ -1,202 +1,235 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2018-2023 Alfredo A. Correa - -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi utility" // test tile NOLINT(cppcoreguidelines-macro-usage) -#include - -#include -#include - -#include -#include // for iota +// Copyright 2018-2024 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // for array_ref, data_elements, num_el... + +// IWYU pragma: no_include // for fill_n +#include // for array +#include // for begin, end, iterator_traits, rend +#include // for addressof // IWYU pragma: keep +#include // for iota +#include // for is_same +// IWYU pragma: no_include +#include // for vector, allocator namespace multi = boost::multi; // TODO(correaa) add test for reinterpret_pointer_cast -BOOST_AUTO_TEST_CASE(std_array_extensions_3d) { - std::array, 4>, 3> arr = {}; - - static_assert(std::is_same::element, double>{}, "!"); - - BOOST_REQUIRE( multi::dimensionality(arr) == 3 ); - - BOOST_REQUIRE( multi::extension(arr) == 3 ); - - BOOST_REQUIRE(( multi::extensions(arr) == decltype(multi::extensions(arr)){3, 4, 5} )); - - using multi::data_elements; - BOOST_REQUIRE( data_elements(arr) == &arr[0][0][0] ); // NOLINT(readability-container-data-pointer) - BOOST_REQUIRE( data_elements(arr) == arr[0][0].data() ); - - using multi::num_elements; - BOOST_REQUIRE( num_elements(arr) == 60 ); - - multi::array const marr({3, 4, 5}); - using multi::layout; - BOOST_REQUIRE( layout(arr) == layout(marr) ); +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ - BOOST_REQUIRE( multi::extensions(arr) == extensions(marr) ); -} +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_AUTO_TEST_CASE(std_array_extensions_3d) { + std::array, 4>, 3> arr = {}; -BOOST_AUTO_TEST_CASE(std_array_extensions_2d) { - std::array, 3> arr = {}; + static_assert(std::is_same::element, double>{}); - static_assert( std::is_same::element, double>{}); + BOOST_TEST( multi::dimensionality(arr) == 3 ); - using multi::dimensionality; - BOOST_REQUIRE( dimensionality(arr) == 2 ); + BOOST_TEST(( multi::extensions(arr) == (decltype(multi::extensions(arr))({3, 4, 5})) )); + BOOST_TEST(( multi::extensions(arr) == decltype(multi::extensions(arr))({3, 4, 5}) )); + BOOST_TEST(( multi::extensions(arr) == decltype(multi::extensions(arr)) {3, 4, 5} )); - using multi::extension; - BOOST_REQUIRE( extension(arr) == 3 ); + using multi::data_elements; + BOOST_TEST( data_elements(arr) == &arr[0][0][0] ); // NOLINT(readability-container-data-pointer) + BOOST_TEST( data_elements(arr) == arr[0][0].data() ); // cppcheck-suppress [mismatchingContainers, redundantContainerDataPointer] ; lib idiom - using multi::extensions; - BOOST_REQUIRE(( extensions(arr) == decltype(extensions(arr)){3, 4} )); + using multi::num_elements; + BOOST_TEST( num_elements(arr) == 60 ); - using multi::data_elements; - BOOST_REQUIRE( data_elements(arr) == &arr[0][0] ); // NOLINT(readability-container-data-pointer) test access - BOOST_REQUIRE( data_elements(arr) == arr[0].data() ); - BOOST_REQUIRE( data_elements(arr) == arr.front().data() ); + multi::array const marr({3, 4, 5}); + using multi::layout; + BOOST_TEST( layout(arr) == layout(marr) ); - using multi::num_elements; - BOOST_REQUIRE( num_elements(arr) == 12 ); + BOOST_TEST( multi::extensions(arr) == extensions(marr) ); + } - multi::array const marr({3, 4}); - using multi::layout; - BOOST_REQUIRE( layout(arr) == layout(marr) ); + BOOST_AUTO_TEST_CASE(std_array_extensions_2d) { + std::array, 3> arr = {}; - BOOST_REQUIRE( extensions(arr) == extensions(marr) ); -} + static_assert(std::is_same::element, double>{}); -BOOST_AUTO_TEST_CASE(std_array_extensions_1d) { - std::array arr = {}; + using multi::dimensionality; + BOOST_TEST( dimensionality(arr) == 2 ); - static_assert( std::is_same::element, double>{}); + using multi::extensions; + BOOST_TEST(( extensions(arr) == decltype(extensions(arr)){3, 4} )); - using multi::dimensionality; - BOOST_REQUIRE( dimensionality(arr) == 1 ); + using multi::data_elements; + BOOST_TEST( data_elements(arr) == &arr[0][0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( data_elements(arr) == arr[0].data() ); // cppcheck-suppress [mismatchingContainers, redundantContainerDataPointer] ; lib idiom + BOOST_TEST( data_elements(arr) == arr.front().data() ); // cppcheck-suppress [mismatchingContainers, redundantContainerDataPointer] ; lib idiom - using multi::extension; - BOOST_REQUIRE( extension(arr) == 4 ); + using multi::num_elements; + BOOST_TEST( num_elements(arr) == 12 ); - using multi::extensions; - BOOST_REQUIRE(( extensions(arr) == decltype(extensions(arr)){multi::iextension{4}} )); + multi::array const marr({3, 4}); + using multi::layout; + BOOST_TEST( layout(arr) == layout(marr) ); - using multi::data_elements; - BOOST_REQUIRE( data_elements(arr) == &arr[0] ); // NOLINT(readability-container-data-pointer) test access - BOOST_REQUIRE( data_elements(arr) == arr.data() ); + BOOST_TEST( extensions(arr) == extensions(marr) ); + } - using multi::num_elements; - BOOST_REQUIRE( num_elements(arr) == 4 ); -} + BOOST_AUTO_TEST_CASE(std_array_extensions_1d) { + std::array arr = {}; -BOOST_AUTO_TEST_CASE(test_utility_1d) { - std::array carr = {{0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}}; - multi::array_ref marr(carr.data(), {multi::iextension{10}}); + static_assert(std::is_same::element, double>{}); - std::vector varr(10) ; std::iota(begin(varr), end(varr), 0.0); // NOLINT(fuchsia-default-arguments-calls) - std::array aarr{}; std::iota(begin(aarr), end(aarr), 0.0); + using multi::dimensionality; + BOOST_TEST( dimensionality(arr) == 1 ); - BOOST_REQUIRE( size(marr) == 10 ); + // using multi::extension; + // BOOST_TEST( extension(arr) == 4 ); - BOOST_REQUIRE( static_cast(carr.size()) == size(marr) ); - BOOST_REQUIRE( static_cast(aarr.size()) == size(marr) ); + using multi::extensions; + BOOST_TEST(( extensions(arr) == decltype(extensions(arr)){multi::iextension{4}} )); - BOOST_REQUIRE( carr[7] == marr[7] ); - BOOST_REQUIRE( varr[7] == marr[7] ); - BOOST_REQUIRE( aarr[7] == marr[7] ); + using multi::data_elements; + BOOST_TEST( data_elements(arr) == &arr[0] ); // NOLINT(readability-container-data-pointer) test access + BOOST_TEST( data_elements(arr) == arr.data() ); - BOOST_REQUIRE( &carr[7] == &marr[7] ); + using multi::num_elements; + BOOST_TEST( num_elements(arr) == 4 ); + } - using multi::num_elements; - BOOST_REQUIRE( num_elements(carr) == num_elements(marr) ); - // BOOST_REQUIRE( num_elements(varr) == num_elements(marr) ); - BOOST_REQUIRE( num_elements(aarr) == num_elements(aarr) ); + BOOST_AUTO_TEST_CASE(test_utility_1d) { + std::array carr = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + }; - using multi::data_elements; - BOOST_REQUIRE( carr.data() == data_elements(marr) ); + multi::array_ref marr(carr.data(), {multi::iextension{10}}); - BOOST_REQUIRE( *begin(varr) == *begin(marr) ); + std::vector varr(10); // NOLINT(fuchsia-default-arguments-calls) + std::iota(varr.begin(), varr.end(), 0); + std::array aarr{}; + std::iota(aarr.begin(), aarr.end(), 0); - using std::begin; - BOOST_REQUIRE( *begin(carr) == *begin(marr) ); + BOOST_TEST( size(marr) == 10 ); - using std::rend; - BOOST_REQUIRE( *(end(varr)-1) == *(end(marr)-1) ); - - using std::end; - BOOST_REQUIRE( *(end(carr)-1) == *(end(marr)-1) ); -} + BOOST_TEST( static_cast(carr.size()) == size(marr) ); + BOOST_TEST( static_cast(aarr.size()) == size(marr) ); -BOOST_AUTO_TEST_CASE(test_utility_2d) { - std::array, 3> carr{{ - {{ 0., 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.}}, - }}; - multi::array_ref marr(&carr[0][0], {3, 10}); // NOLINT(readability-container-data-pointer) tests access + BOOST_TEST( carr[7] == marr[7] ); + BOOST_TEST( varr[7] == marr[7] ); + BOOST_TEST( aarr[7] == marr[7] ); - BOOST_REQUIRE( static_cast(carr.size()) == size(marr) ); + BOOST_TEST( &carr[7] == &marr[7] ); - BOOST_REQUIRE( carr[1][7] == marr[1][7] ); + using multi::num_elements; + BOOST_TEST( num_elements(carr) == num_elements(marr) ); + // BOOST_TEST( num_elements(varr) == num_elements(marr) ); + BOOST_TEST( num_elements(aarr) == num_elements(aarr) ); - BOOST_REQUIRE( &carr[1][7] == &marr[1][7] ); + using multi::data_elements; + BOOST_TEST( carr.data() == data_elements(marr) ); - using multi::num_elements; - BOOST_REQUIRE( num_elements(carr) == num_elements(marr) ); + BOOST_TEST( *begin(varr) == *begin(marr) ); - using multi::data_elements; - BOOST_REQUIRE( data_elements(carr) == data_elements(marr) ); -} + using std::begin; + BOOST_TEST( *begin(carr) == *begin(marr) ); -BOOST_AUTO_TEST_CASE(multi_utility_test) { - static_assert( std::is_same::value_type, double>{}, "!"); - - using multi::corigin; - using multi::dimensionality; - using multi::extension; - using multi::extensions; -// using multi::origin; - using multi::size; - using multi::sizes; - using multi::num_elements; -{ - double arr[4] = {1., 2., 3., 4.}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types - BOOST_REQUIRE( dimensionality(arr) == 1 ); - BOOST_REQUIRE( extension(arr).first() == 0 ); - BOOST_REQUIRE( extension(arr).last() == 4 ); - - BOOST_REQUIRE( size(arr) == 4 ); - - using boost::multi::detail::get; - BOOST_REQUIRE( get<0>(sizes(arr)) == size(arr) ); - using multi::get_allocator; - - static_assert(std::is_same >{}); - - using std::addressof; - - using multi::data_elements; - static_assert(std::is_same{}); -// BOOST_REQUIRE( data(A) == addressof(A[0]) ); - BOOST_REQUIRE(data_elements(arr) == addressof(arr[0])); -} -{ - double arr[2][3] = {{1., 2., 3.}, {4., 5., 6.}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : test legacy types - BOOST_REQUIRE( dimensionality(arr) == 2 ); - BOOST_REQUIRE( extension(arr).first() == 0 ); - BOOST_REQUIRE( extension(arr).last() == 2 ); + using std::rend; + BOOST_TEST( *(end(varr)-1) == *(end(marr)-1) ); - arr[0][0] = 99.; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif - BOOST_REQUIRE( arr[0][0] == 99. ); - BOOST_REQUIRE( corigin(arr) == &arr[0][0] ); - BOOST_REQUIRE( size(arr) == 2 ); + using std::end; + BOOST_TEST( *(end(carr)-1) == *(end(marr)-1) ); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - using multi::detail::get; - BOOST_REQUIRE( get<0>(sizes(arr)) == size(arr) ); - BOOST_REQUIRE( num_elements(arr) == 6 ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } - static_assert( num_elements(arr) == 6 , "!" ); -} + BOOST_AUTO_TEST_CASE(test_utility_2d) { + // clang-format off + std::array, 3> carr{{ + {{ 00, 10, 20, 30, 40, 50, 60, 70, 80, 90 }}, + {{ 100, 110, 120, 130, 140, 150, 160, 170, 180, 190 }}, + {{ 200, 210, 220, 230, 240, 250, 260, 270, 280, 290 }}, + }}; + // clang-format on + + multi::array_ref marr(&carr[0][0], {3, 10}); // NOLINT(readability-container-data-pointer) tests access + + BOOST_TEST( static_cast(carr.size()) == size(marr) ); + + BOOST_TEST( carr[1][7] == marr[1][7] ); + + BOOST_TEST( &carr[1][7] == &marr[1][7] ); + + using multi::num_elements; + BOOST_TEST( num_elements(carr) == num_elements(marr) ); + + using multi::data_elements; + BOOST_TEST( data_elements(carr) == data_elements(marr) ); + } + + BOOST_AUTO_TEST_CASE(multi_utility_test) { + static_assert(std::is_same_v::value_type, int>); + + using multi::corigin; + using multi::dimensionality; + using multi::extension; + using multi::extensions; + using multi::num_elements; + using multi::size; + using multi::sizes; + { + int arr[4] = {10, 20, 30, 40}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) test legacy types + BOOST_TEST( dimensionality(arr) == 1 ); + BOOST_TEST( extension(arr).first() == 0 ); + BOOST_TEST( extension(arr).last() == 4 ); + + BOOST_TEST( size(arr) == 4 ); + + using boost::multi::detail::get; + BOOST_TEST( get<0>(sizes(arr)) == size(arr) ); + using multi::get_allocator; + + static_assert(std::is_same_v>); + + using std::addressof; + + using multi::data_elements; + static_assert(std::is_same_v); + // BOOST_TEST( data(A) == addressof(A[0]) ); + BOOST_TEST(data_elements(arr) == addressof(arr[0])); + } + { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) : test legacy types + int arr[2][3] = { + {10, 20, 30}, + {40, 50, 60}, + }; + BOOST_TEST( dimensionality(arr) == 2 ); + BOOST_TEST( extension(arr).first() == 0 ); + BOOST_TEST( extension(arr).last() == 2 ); + + arr[0][0] = 990; + + BOOST_TEST( arr[0][0] == 990 ); // cppcheck-suppress knownConditionTrueFalse ; for test + BOOST_TEST( corigin(arr) == &arr[0][0] ); + BOOST_TEST( size(arr) == 2 ); + + using multi::detail::get; + BOOST_TEST( get<0>(sizes(arr)) == size(arr) ); + BOOST_TEST( num_elements(arr) == 6 ); + + static_assert(num_elements(arr) == 6); + } + { + BOOST_TEST(multi::detail::valid_mull(21)); + BOOST_TEST(multi::detail::valid_mull(22)); + } + } + + return boost::report_errors(); } diff --git a/test/zero_dimensionality.cpp b/test/zero_dimensionality.cpp index 550e91dd7..12cb2ac20 100644 --- a/test/zero_dimensionality.cpp +++ b/test/zero_dimensionality.cpp @@ -1,87 +1,103 @@ -// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*- -// Copyright 2019-2023 Alfredo A. Correa +// Copyright 2019-2025 Alfredo A. Correa +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt -// #define BOOST_TEST_MODULE "C++ Unit Tests for Multi zero dimensionality" // test title NOLINT(cppcoreguidelines-macro-usage) -#include +#include // for array_ref, static_array, array_ptr -#include - -#include +// IWYU pragma: no_include // for copy +#include // for complex +#include // for allocator // IWYU pragma: keep +// IWYU pragma: no_include // for remove_reference<>::type +#include // for move +#include // for vector, allocator namespace multi = boost::multi; -BOOST_AUTO_TEST_CASE(zero_dimensionality_part1) { - { - std::vector v1 = {1.0, 2.0, 3.0}; // NOLINT(fuchsia-default-arguments-calls) - - multi::array_ref m1(v1.data(), multi::extensions_t<1>{multi::iextension{3}}); - BOOST_REQUIRE( size(m1) == 3 ); - BOOST_REQUIRE( &m1[1] == &v1[1] ); - BOOST_REQUIRE( num_elements(m1) == 3 ); - - multi::array_ref m0(v1.data(), {}); -// BOOST_REQUIRE(( &m0 == multi::array_ptr(v1.data(), {}) )); - BOOST_REQUIRE( data_elements(m0) == v1.data() ); - BOOST_REQUIRE( num_elements(m0) == 1 ); - - m0 = 5.1; - BOOST_REQUIRE( v1[0] == 5.1 ); - - double const& doub = std::move(m0); - BOOST_REQUIRE( doub == 5.1 ); +#include +#define BOOST_AUTO_TEST_CASE(CasenamE) /**/ + +auto main() -> int { // NOLINT(readability-function-cognitive-complexity,bugprone-exception-escape) + BOOST_AUTO_TEST_CASE(zero_dimensionality_part1) { + { + std::vector v1 = {10, 20, 30}; // NOLINT(fuchsia-default-arguments-calls) + + multi::array_ref m1(v1.data(), multi::extensions_t<1>{multi::iextension{3}}); + BOOST_TEST( size(m1) == 3 ); + BOOST_TEST( &m1[1] == &v1[1] ); + BOOST_TEST( num_elements(m1) == 3 ); + + multi::array_ref const m0(v1.data(), {}); + // BOOST_TEST(( &m0 == multi::array_ptr(v1.data(), {}) )); + BOOST_TEST( m0.data_elements() == v1.data() ); + BOOST_TEST( m0.num_elements() == 1 ); + + // m0 = 51; + // BOOST_TEST( v1[0] == 51 ); + + // int const& doub = std::move(m0); + // BOOST_TEST( doub == 51 ); + } + { + // vvv--- TODO(correaa) this might trigger a compiler crash with g++ 7.5 because of operator&() && overloads + multi::static_array a0 = multi::static_array{45.0}; + BOOST_TEST( num_elements(a0) == 1 ); + BOOST_TEST( a0 == 45.0 ); + + a0 = multi::static_array{60.0}; + BOOST_TEST( a0 == 60.0 ); + } + { + std::allocator const alloc; + multi::static_array a0(45.0, alloc); + BOOST_TEST( num_elements(a0) == 1 ); + BOOST_TEST( a0 == 45.0 ); + + a0 = multi::static_array{60.0}; + BOOST_TEST( a0 == 60.0 ); + } } - { - multi::static_array a0 = multi::static_array{45.0}; // TODO(correaa) this might trigger a compiler crash with g++ 7.5 because of operator&() && overloads - BOOST_REQUIRE( num_elements(a0) == 1 ); - BOOST_REQUIRE( a0 == 45.0 ); - a0 = multi::static_array{60.0}; - BOOST_REQUIRE( a0 == 60.0 ); - } - { - std::allocator const alloc; - multi::static_array a0(45.0, alloc); - BOOST_REQUIRE( num_elements(a0) == 1 ); - BOOST_REQUIRE( a0 == 45.0 ); - - a0 = multi::static_array{60.0}; - BOOST_REQUIRE( a0 == 60.0 ); + BOOST_AUTO_TEST_CASE(zero_dimensionality_part2) { + { + multi::array, 2> const arr({1, 2}, std::allocator>{}); + BOOST_TEST( arr.size() == 1 ); + } + { + int doub = 20; + multi::array_ref arr(doub); + int const& the_doub = static_cast(arr); + BOOST_TEST( the_doub == doub ); + BOOST_TEST( &the_doub == &doub ); + } + { + int doub = 20; + auto dd = static_cast(multi::array_ref(&doub, {})); + + BOOST_TEST( dd == doub ); + + multi::array_ptr const ap1(&doub, multi::extensions_t<1>({0, 1})); + BOOST_TEST( (*ap1).base() == &doub ); + BOOST_TEST( ap1->base() == &doub ); + } + { + multi::array const arr({}, 5); + BOOST_TEST( arr.num_elements() == 1 ); + auto arr_val = int{arr}; + BOOST_TEST( arr_val == 5 ); + } + { + multi::array arr1(5); + BOOST_TEST( arr1.num_elements() == 1 ); + + auto const* arr1_base = arr1.base(); + + auto const arr2 = std::move(arr1); + BOOST_TEST( arr2.num_elements() == 1 ); + BOOST_TEST( int{arr2} == 5 ); + BOOST_TEST( arr2.base() == arr1_base ); + } } -} -BOOST_AUTO_TEST_CASE(zero_dimensionality_part2) { - { - multi::array, 2> const arr({1, 2}, std::allocator>{}); - BOOST_REQUIRE( size(arr) == 1 ); - } - { - double doub = 2.0; - multi::array_ref arr(doub); - double const& the_doub = static_cast(arr); - BOOST_REQUIRE( the_doub == doub ); - BOOST_REQUIRE( &the_doub == &doub ); - } - { - double doub = 2.0; - double dd{multi::array_ref(&doub, {})}; - - BOOST_REQUIRE( dd == doub ); - - multi::array_ptr const ap1(&doub, multi::extensions_t<1>{{0, 1}}); - BOOST_REQUIRE( ap1->base() == &doub ); - BOOST_REQUIRE( (*ap1).base() == &doub ); - - multi::array_ptr const ap0(&doub, {}); - - BOOST_REQUIRE(( ap0 == multi::array_ptr(&doub, {}) )); - BOOST_REQUIRE(( ap0 != multi::array_ptr(&dd, {}) )); - BOOST_REQUIRE( ap0->base() == &doub ); - BOOST_REQUIRE( (*ap0).base() == &doub ); - - multi::array_ptr const ap0dd{&dd}; - BOOST_REQUIRE( ap0dd != ap0 ); - BOOST_REQUIRE( *ap0 == *ap0dd ); - double d3 = M_PI; - BOOST_REQUIRE(( *multi::array_ptr(&d3, {}) == M_PI )); - } + return boost::report_errors(); } diff --git a/test_adaptors/CMakeLists.txt b/test_adaptors/CMakeLists.txt new file mode 100644 index 000000000..5fee541a0 --- /dev/null +++ b/test_adaptors/CMakeLists.txt @@ -0,0 +1,207 @@ +# Copyright 2018-2024 Alfredo A. Correa +# Copyright 2024 Matt Borland +# Distributed under the Boost Software License, Version 1.0. +# https://www.boost.org/LICENSE_1_0.txt + +cmake_minimum_required(VERSION 3.16) + +# Library doesn't require installation, to still install this project: +# ~~~ +# $ cmake .. --install-prefix=$HOME && cmake --build . --config Release --target test --target install -- -j $(nproc) +# ~~~ +# to use this library in another CMake project +# ~~~ +# project("Your project") +# find_package(boost-multi CONFIG REQUIRED) +# add_executable(${PROJECT_NAME} src/your_main.cpp) +# target_link_libraries(${PROJECT_NAME} boost-multi::boost-multi) +# ~~~ + +if (DEFINED BOOST_SUPERPROJECT_VERSION AND NOT DEFINED BOOST_MULTI_STANDALONE) + + project(boost_multi VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX) + + add_library(boost_multi INTERFACE) + + add_library(Boost::multi ALIAS boost_multi) + + target_include_directories(boost_multi INTERFACE include) + + target_compile_features(boost_multi INTERFACE cxx_std_14) + + message(STATUS "Boost.Multi: standalone mode OFF") + + if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt") + + add_subdirectory(test) + + endif() + +else() + + project( + multi + HOMEPAGE_URL "https://gitlab.com/correaa/boost-multi" + DESCRIPTION "A header-only modern C++ library that provides access and manipulation of data in multidimensional arrays." + VERSION 0.80.1 + LANGUAGES CXX) + + message(STATUS "Boost.Multi: standalone mode ON") + + message("current binary directory: ${CMAKE_CURRENT_BINARY_DIR}") + + include_directories(${PROJECT_SOURCE_DIR}/include) # workaround for vscode to detect headers https://stackoverflow.com/a/68139743/225186 + + include(CMakePackageConfigHelpers) + include(CMakeDependentOption) + include(GNUInstallDirs) + + find_program(MEMORYCHECK_COMMAND valgrind) + set(VALGRIND_COMMAND_OPTIONS "-q --tool=memcheck --leak-check=yes --num-callers=51 --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all") + + # must go before `include(CTest)` + set(MEMORYCHECK_COMMAND_OPTIONS "-q --tool=memcheck --leak-check=yes --num-callers=52 --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all") + set(MEMORYCHECK_SUPPRESSIONS_FILE "${PROJECT_SOURCE_DIR}/.valgrind-suppressions") + + include(CTest) + + option(MULTI_BUILD_PACKAGE "Build package files as well" ON) + + cmake_dependent_option(MULTI_BUILD_TESTS "Enable multi tests" ON "BUILD_TESTING" OFF) + + cmake_dependent_option(MULTI_BUILD_PACKAGE_DEB "Create a DEB" ON "MULTI_BUILD_PACKAGE" OFF) + + add_library(multi INTERFACE) + #target_compile_features(multi PUBLIC cxx_std_17) + + target_include_directories(multi INTERFACE $ $) + target_compile_options(multi INTERFACE $<$: --expt-relaxed-constexpr --extended-lambda>) + + if(NOT CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + add_library(correaa::multi ALIAS multi) + endif() + + # https://youtrack.jetbrains.com/issue/CPP-25608 + target_compile_features(${PROJECT_NAME} INTERFACE $<$>:cxx_std_17>) + target_compile_options(${PROJECT_NAME} INTERFACE $<$:-std=c++17>) + + # this makes CM FetchContent friendly https://www.foonathan.net/2022/06/cmake-fetchcontent/ + if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + return() + endif() + + # Installation help + configure_package_config_file("${PROJECT_SOURCE_DIR}/cmake/multi-config.cmake.in" "${PROJECT_BINARY_DIR}/multi-config.cmake" INSTALL_DESTINATION "share/cmake/multi") + + write_basic_package_version_file("${PROJECT_BINARY_DIR}/multi-config-version.cmake" COMPATIBILITY SameMajorVersion ARCH_INDEPENDENT) + + message("current install prefix directory: ${CMAKE_INSTALL_PREFIX}") + + install( + TARGETS multi + EXPORT multi-targets + INCLUDES + DESTINATION "${CMAKE_INSTALL_DATADIR}") + + install( + EXPORT ${PROJECT_NAME}-targets + DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}" + NAMESPACE multi:: + FILE "${PROJECT_NAME}-targets.cmake") + + install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}") + + install(DIRECTORY "include/" TYPE INCLUDE) + + add_subdirectory(test) + + # if(MULTI_BUILD_TESTS) + find_package(Boost 1.65 COMPONENTS) # unit_test_framework) # 1.65 needed for BOOST_TEST_GLOBAL_FIXTURE, you can use your own Boost and use `cmake -DBOOST_ROOT=$HOME/local` + if(NOT Boost_FOUND) + message(WARNING "Cannot find Boost 1.65+, Multi library will not be fully tested.") + else() + enable_testing() + + add_subdirectory(include/boost/multi/adaptors/blas) + add_subdirectory(include/boost/multi/adaptors/complex) + add_subdirectory(include/boost/multi/adaptors/cuda) + add_subdirectory(include/boost/multi/adaptors/fftw) + + find_package(LAPACK) + if(LAPACK_FOUND) + add_subdirectory(include/boost/multi/adaptors/lapack) + endif() + + add_subdirectory(include/boost/multi/adaptors/thrust) + if(ENABLE_CUDA) + add_subdirectory(include/boost/multi/adaptors/cufft) + endif() + if(ENABLE_HIP) + add_subdirectory(include/boost/multi/adaptors/hipfft) + add_subdirectory(include/boost/multi/adaptors/hipthrust/test) + endif() + endif() + # endif() + + if(MULTI_BUILD_PACKAGE) + list(APPEND source-generators TBZ2 TGZ TXZ ZIP) + + if(CMAKE_HOST_WIN32) + list(APPEND binary-generators "WIX") + endif() + + if(MULTI_BUILD_PACKAGE_DEB) + list(APPEND binary-generators "DEB") + endif() + + if(MULTI_BUILD_RPM) + list(APPEND binary-generators "RPM") + endif() + + set(CPACK_PACKAGE_NAME ${PROJECT_NAME} + CACHE STRING "The resulting package name" + ) + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "all") + set(CPACK_PACKAGE_ARCHITECTURE "all") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Multidimensional arrays for C++" + CACHE STRING "Package description for the package metadata" + ) + + set(CPACK_PACKAGE_VENDOR "alfredo.correa@gmail.com") + # set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) + # SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/_packages") + # set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/some")#/${CMAKE_PROJECT_VERSION}") + + # set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) + # set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) + # set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) + + set(CPACK_PACKAGE_CONTACT "alfredo.correa@gmail.com") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Alfredo A. Correa") + + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + + set(CPACK_DEBIAN_FILE_NAME "multi_all.deb") + # set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)#ONE_PER_GROUP) + # set(CPACK_DEB_COMPONENT_INSTALL YES) + + set(CPACK_SOURCE_GENERATOR ${source-generators}) + set(CPACK_GENERATOR ${binary-generators}) + + # set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}") + # set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}") + + list( + APPEND + CPACK_SOURCE_IGNORE_FILES + /.git/ + /.build*/ + /build/ + .gitignore + .DS_Store) + + include(CPack) + endif() + +endif()