diff --git a/.circleci/config.yml b/.circleci/config.yml index 39807809..19a372fd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,33 +1,22 @@ version: 2.1 orbs: - mirci: libmir/upload_docs@0.0.0 - -jobs: - build: - docker: - - image: libmir/circle-dlang - steps: - - checkout - - run: git submodule sync && git submodule update --recursive --init - # - run: dub test - # - run: dub test -c dips - - run: meson -D with_test=true build - # - run: ninja -C build test -j1 - - run: make -f doc/Makefile html - - mirci/persist_docs: - from: web + mirci: libmir/upload_docs@0.3.0 workflows: version: 2 build-deploy: jobs: - - build - - mirci/upload_docs_job: - from: web + - mirci/test_and_build_docs: + filters: + tags: + only: /^v(\d)+(\.(\d)+)+$/ + - mirci/upload_docs: to: mir-algorithm.libmir.org requires: - - build + - mirci/test_and_build_docs filters: branches: - only: master + ignore: /.*/ + tags: + only: /^v(\d)+(\.(\d)+)+$/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..80bc988c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,185 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + # allow this workflow to be triggered manually + +# Only allow for one job to run at a time, and cancel any jobs currently in progress. +concurrency: + group: gh-actions-${{ github.actor }}-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + setup: + name: 'Load job configuration' + runs-on: ubuntu-20.04 + outputs: + compilers: ${{ steps.load-config.outputs.compilers }} + steps: + - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + # This step checks if we want to only run tests on a specific platform or + # if we want to skip CI entirely, then outputs the compilers to be used for + # each job. + - id: load-config + uses: actions/github-script@9ac08808f993958e9de277fe43a64532a609130e + with: + script: | + const base_compiler_config = require("./.github/workflows/compilers.json"); + const compilers = {"windows": [], "macos": [], "ubuntu": []}; + const {owner, repo} = context.repo; + let commit_sha = context.sha; + if (context.eventName == "pull_request") + { + commit_sha = context.payload.pull_request.head.sha; + } + + const commit = await github.rest.git.getCommit({ + owner, + repo, + commit_sha + }); + const head_commit_message = commit.data.message; + + if (head_commit_message.startsWith("[windows-only]")) + { + compilers.windows = base_compiler_config; + } + else if (head_commit_message.startsWith("[macos-only]")) + { + compilers.macos = base_compiler_config; + } + else if (head_commit_message.startsWith("[ubuntu-only]")) + { + compilers.ubuntu = base_compiler_config; + } + else if (!head_commit_message.startsWith("[skip-ci]")) + { + compilers.windows = base_compiler_config; + compilers.macos = base_compiler_config; + compilers.ubuntu = base_compiler_config; + } + core.setOutput("compilers", JSON.stringify(compilers)); + + macos: + name: '[macos] x86_64/${{ matrix.dc }}' + runs-on: macos-11 + needs: setup + # Only run if the setup phase explicitly defined compilers to be used + if: ${{ fromJSON(needs.setup.outputs.compilers).macos != '' && fromJSON(needs.setup.outputs.compilers).macos != '[]' }} + # Beta / master versions of any compiler are allowed to fail + continue-on-error: ${{ contains(matrix.dc, 'beta') || contains(matrix.dc, 'master') }} + env: + ARCH: x86_64 + strategy: + fail-fast: false + matrix: + dc: ${{ fromJSON(needs.setup.outputs.compilers).macos }} + steps: + - name: Checkout repo + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - name: Setup D compiler + uses: dlang-community/setup-dlang@763d869b4d67e50c3ccd142108c8bca2da9df166 + with: + compiler: ${{ matrix.dc }} + - name: Cache dub dependencies + uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed + with: + path: ~/.dub/packages + key: macos-latest-build-${{ hashFiles('**/dub.sdl', '**/dub.json') }} + restore-keys: | + macos-latest-build- + - name: Build / test + run: | + dub test --arch=$ARCH --build=unittest-cov + dub test --arch=$ARCH --combined + shell: bash + - name: Upload coverage data + uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b + + ubuntu: + name: '[ubuntu] ${{ matrix.arch }}/${{ matrix.dc }}' + runs-on: ubuntu-20.04 + needs: setup + # Only run if the setup phase explicitly defined compilers to be used + if: ${{ fromJSON(needs.setup.outputs.compilers).ubuntu != '' && fromJSON(needs.setup.outputs.compilers).ubuntu != '[]' }} + # Beta / master versions of any compiler are allowed to fail + continue-on-error: ${{ contains(matrix.dc, 'beta') || contains(matrix.dc, 'master') }} + env: + ARCH: ${{ matrix.arch }} + strategy: + fail-fast: false + matrix: + dc: ${{ fromJSON(needs.setup.outputs.compilers).ubuntu }} + arch: [x86, x86_64] + steps: + - name: Checkout repo + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - name: Setup D compiler + uses: dlang-community/setup-dlang@763d869b4d67e50c3ccd142108c8bca2da9df166 + with: + compiler: ${{ matrix.dc }} + - name: Install multi-lib for 32-bit systems + if: matrix.arch == 'x86' + run: sudo apt-get update && sudo apt-get install gcc-multilib + - name: Cache dub dependencies + uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed + with: + path: ~/.dub/packages + key: ubuntu-latest-build-${{ hashFiles('**/dub.sdl', '**/dub.json') }} + restore-keys: | + ubuntu-latest-build- + - name: Build / test + run: | + dub test --arch=$ARCH --build=unittest-cov + dub test --arch=$ARCH --combined + shell: bash + - name: Upload coverage data + uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b + + windows: + name: '[windows] x86_64/${{ matrix.dc }}' + runs-on: windows-2022 + needs: setup + # Only run if the setup phase explicitly defined compilers to be used + if: ${{ fromJSON(needs.setup.outputs.compilers).windows != '' && fromJSON(needs.setup.outputs.compilers).windows != '[]' }} + # Beta / master versions of any compiler are allowed to fail + continue-on-error: ${{ contains(matrix.dc, 'beta') || contains(matrix.dc, 'master') }} + env: + ARCH: x86_64 + strategy: + fail-fast: false + matrix: + dc: ${{ fromJSON(needs.setup.outputs.compilers).windows }} + steps: + - name: Checkout repo + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - name: Setup D compiler + uses: dlang-community/setup-dlang@763d869b4d67e50c3ccd142108c8bca2da9df166 + with: + compiler: ${{ matrix.dc }} + - name: Cache dub dependencies + uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed + with: + path: ~\AppData\Local\dub + key: windows-latest-build-${{ hashFiles('**/dub.sdl', '**/dub.json') }} + restore-keys: | + windows-latest-build- + # Tests are split up to work around OOM errors -- no combined testing is done + # as it's simply too big for the compiler to handle on Windows. + - name: Build / test + run: | + dub test --arch=$ARCH --build=unittest-ci -c ci-bignum-test + dub test --arch=$ARCH --build=unittest-ci -c ci-core-test + dub test --arch=$ARCH --build=unittest-ci -c ci-ndslice-test + dub test --arch=$ARCH --build=unittest-ci -c ci-test + shell: bash + - name: Upload coverage data + uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b + + diff --git a/.github/workflows/compilers.json b/.github/workflows/compilers.json new file mode 100644 index 00000000..b2ecda9c --- /dev/null +++ b/.github/workflows/compilers.json @@ -0,0 +1,8 @@ +[ + "dmd-master", + "dmd-latest", + "dmd-beta", + "ldc-master", + "ldc-latest", + "ldc-beta" +] \ No newline at end of file diff --git a/.gitignore b/.gitignore index e0f1a144..98af4e28 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,16 @@ mir-algorithm-test-default mir-algorithm-test-dips subprojects/mir-core mir-core +mir-algorithm-test-dip1008 +test +test.d +docgen +_build_dir_ +.VSCodeCounter +builddir/ +mir-algorithm-test-secure +*.pdb +bigint_benchmark/bigint_benchmark +bigint_benchmark/temp.d +mir-algorithm-test-silly +mir-algorithm-test-ci-bignum-test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2b48030c..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: d -d: - - ldc - - ldc-beta - - dmd-nightly - - dmd-beta - - dmd -branches: - only: - - master -env: - - ARCH="x86" - - ARCH="x86_64" -addons: - apt: - packages: - - gcc-multilib -matrix: - allow_failures: - - {d: dmd-nightly} - - {d: ldc-beta} - - {d: gdc} -script: - - travis_wait 100 dub test --arch "$ARCH" --build=unittest-cov - - travis_wait 100 dub test --arch "$ARCH" -c dips - - ./test_examples.sh -# - travis_wait 100 dub test --arch "$ARCH" --build=unittest-release - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/LICENSE b/LICENSE index 36b7cd93..5e20e311 100644 --- a/LICENSE +++ b/LICENSE @@ -1,23 +1,13 @@ -Boost Software License - Version 1.0 - August 17th, 2003 + Copyright 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. + http://www.apache.org/licenses/LICENSE-2.0 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..106654df --- /dev/null +++ b/NOTICE @@ -0,0 +1,3 @@ +### Ryu + +[Original Ryu algorithm](https://github.com/ulfjack/ryu) was developed by Ulf Adams. diff --git a/README.md b/README.md index 6210f99e..c8de29ef 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![codecov.io](https://codecov.io/github/libmir/mir-algorithm/coverage.svg?branch=master)](https://codecov.io/github/libmir/mir-algorithm?branch=master) -[![Build Status](https://travis-ci.org/libmir/mir-algorithm.svg?branch=master)](https://travis-ci.org/libmir/mir-algorithm) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/libmir/mir-algorithm/ci.yml?branch=master)](https://github.com/libmir/mir-algorithm/actions) [![Circle CI](https://circleci.com/gh/libmir/mir-algorithm.svg?style=svg)](https://circleci.com/gh/libmir/mir-algorithm) [![Dub downloads](https://img.shields.io/dub/dt/mir-algorithm.svg)](http://code.dlang.org/packages/mir-algorithm) @@ -10,17 +10,18 @@ Mir Algorithm ============= -Mir library with basic types and algorithms including multidimensional matrixes, thread-safe reference counted arrays and pointers. -### Links - - [API Documentation](http://mir-algorithm.libmir.org) - - [Lubeck](https://github.com/kaleidicassociates/lubeck) - Linear Algebra Library for Mir Algorithm - - [numir](https://github.com/libmir/numir) - mir extension with numpy-like API +#### [API Documentation](http://mir-algorithm.libmir.org) #### Blogs - - [Mir Blog](http://blog.mir.dlang.io/) + - Tasty D - [Multidimensional Arrays in D](https://tastyminerals.github.io/tasty-blog/dlang/2020/03/22/multidimensional_arrays_in_d.html) + - Tasty D - [Using External D Libraries in D Scripts and Projects](https://tastyminerals.github.io/tasty-blog/dlang/2020/03/01/how_to_use_external_libraries_in_d_project.html) + - Tasty D - [Pretty-printing D Arrays](https://tastyminerals.github.io/tasty-blog/dlang/2020/06/25/pretty_printing_arrays.html) - Shigeki Karita - [D言語で数値計算 mir-algorithm](https://shigekikarita.github.io/blog/2017/09/22/026.html) - Shigeki Karita - [D言語(mir)でNumPyを拡張する](https://qiita.com/ShigekiKarita/items/af84b0ef864608ee1f21) (mir-pybuffer integration) + - [Mir Blog](http://blog.mir.dlang.io/) (deprecated) + +#### [Mir Type System for .NET](https://github.com/libmir/mir.net) #### Example (3 sec) ```d @@ -40,8 +41,9 @@ void main() row[3] = 6; assert(matrix[2, 3] == 6); // D & C index order - import std.stdio; - matrix.writeln; // [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 6]] + import mir.stdio; + matrix.writeln; + // prints [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 6.0]] } ``` @@ -83,9 +85,3 @@ void main() ``` [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/67Gi6X) - -### Our sponsors - -[](http://symmetryinvestments.com/)         -[](https://github.com/kaleidicassociates) - diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6ad8ffe0..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,105 +0,0 @@ -platform: x64 -environment: - matrix: - # - DC: dmd - # DVersion: 2.080.0 - # arch: x64 - # - DC: dmd - # DVersion: 2.080.0 - # arch: x86 - - DC: ldc - DVersion: '1.16.0-beta2' - arch: x64 - -matrix: - allow_failures: - - {DC: dmd, arch: x86} - -skip_tags: true -branches: - only: - - master - - stable - -install: - - ps: function SetUpDCompiler - { - if($env:DC -eq "dmd"){ - if($env:arch -eq "x86"){ - $env:DConf = "m32"; - } - elseif($env:arch -eq "x64"){ - $env:DConf = "m64"; - } - echo "downloading ..."; - $env:toolchain = "msvc"; - $version = $env:DVersion; - Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z" -OutFile "c:\dmd.7z"; - echo "finished."; - pushd c:\\; - 7z x dmd.7z > $null; - popd; - } - elseif($env:DC -eq "ldc"){ - echo "downloading ..."; - if($env:arch -eq "x86"){ - $env:DConf = "m32"; - } - elseif($env:arch -eq "x64"){ - $env:DConf = "m64"; - } - $env:toolchain = "msvc"; - $version = $env:DVersion; - Invoke-WebRequest "https://github.com/ldc-developers/ldc/releases/download/v$($version)/ldc2-$($version)-windows-x64.7z" -OutFile "c:\ldc.7z"; - echo "finished."; - pushd c:\\; - 7z x ldc.7z > $null; - popd; - } - } - - ps: SetUpDCompiler - - powershell -Command Invoke-WebRequest https://code.dlang.org/files/dub-1.9.0-windows-x86.zip -OutFile dub.zip - - 7z x dub.zip -odub > nul - - set PATH=%CD%\%binpath%;%CD%\dub;%PATH% - - dub --version - -before_build: - - ps: if($env:arch -eq "x86"){ - $env:compilersetupargs = "x86"; - $env:Darch = "x86"; - } - elseif($env:arch -eq "x64"){ - $env:compilersetupargs = "amd64"; - $env:Darch = "x86_64"; - } - - ps : if($env:DC -eq "dmd"){ - $env:PATH += ";C:\dmd2\windows\bin;"; - } - elseif($env:DC -eq "ldc"){ - $version = $env:DVersion; - $env:PATH += ";C:\ldc2-$($version)-windows-x64\bin"; - $env:DC = "ldc2"; - } - - ps: $env:compilersetup = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall"; - - '"%compilersetup%" %compilersetupargs%' - -build_script: - - echo dummy build script - dont remove me - -test_script: - - echo %PLATFORM% - - echo %Darch% - - echo %DC% - - echo %PATH% - - '%DC% --version' - - dub test --arch=%Darch% --compiler=%DC% - -notifications: - - provider: Webhook - url: https://webhooks.gitter.im/e/56aae174f8cc81f4eda4 - - provider: Email - to: - - devteam@mir.rocks - on_build_success: false - on_build_failure: true - on_build_status_changed: true diff --git a/bigint_benchmark/dub.sdl b/bigint_benchmark/dub.sdl new file mode 100644 index 00000000..5b57848a --- /dev/null +++ b/bigint_benchmark/dub.sdl @@ -0,0 +1,3 @@ +name "bigint_benchmark" +dependency "mir-algorithm" path="../" +dependency "gmp-d" version="~master" diff --git a/bigint_benchmark/source/app.d b/bigint_benchmark/source/app.d new file mode 100644 index 00000000..d113422f --- /dev/null +++ b/bigint_benchmark/source/app.d @@ -0,0 +1,74 @@ +import mir.stdio; +import std.datetime.stopwatch; + +immutable ps = "E5B5B1EDC8DF0F307C2220151CFCBE31F69B15659A5D6FBA1E50F55A08B341218312D707CFC16ED86A1765F5AEAFA7E6A11C4431038914C76F0F398FE6BE031E289B220D13D9E02226C691D15BC6E1186EA18222D93F52A393BE1DA1A42853512419B5E6E304FD02E962A4C2D0ECDDB8F44AC094FACA8333AE94110A5B10DA539C24A96F08530E7699E3F705165CF14B7F90A2F32ED28D21615F91D7C808AC566D6EEEF6773450AB53542CDAC337C3124530CB16319752267C3422149D41543D8742586BAB578F4E06360745AE0BD8F0E800D1920DC1F3661287367A78967458383A82465C5D966E7299EFCF58BD860185F96655E1F8D300F6B096DFE883CF15"; +immutable qs = "D9757338E9A6B363F227F3104EDEF6240C0CAF53B7D509F48870553C4A821F460469AE5616301B9CC30FBF4598A176B84284AF3A41D697A34CDC2C8D88A4C4BE82AE8DB5347511FE5B4DD915CA6A728CCFD0444CE38FC7190824059D86A9083C273581EA5AD1D5E3A8D8EC6858F291A5EADA98B0F5FD7C8E8CA6226657B8B7955796B22899B087714E293A86C78D42A7021754A6220F1D0A9588C280DD9AEC376E421D539F30A3053D95C7D70F24B471D14ECF282FA3E0B1CED2C405BA22404F3B75CD961A46097D7C098324FC47281D298734DA0DFCD8AF82E685657C926672727296147867EAEDFDEF89A79DE81FF104CF7D9157EF65A1BC333C98A7FED685"; +immutable es = ps ~ qs; + +void testStd() +{ + import std.bigint; + BigInt p = "0x" ~ ps; + BigInt q = "0x" ~ qs; + BigInt m = p; + m *= q; + BigInt e = "0x" ~ es; + BigInt b = e; + b = powmod(b, e, m); + debug dout << b << endl; +} + +void testMir() +{ + import mir.bignum.integer; + auto p = BigInt!64.fromHexString(ps); + auto q = BigInt!64.fromHexString(qs); + BigInt!64 m = p; + m *= q; + auto e = BigInt!64.fromHexString(es); + BigInt!64 b = e; + b.powMod(e, m); + debug dout << b << endl; +} + +void testGmp() +{ + import gmp.z : BigInt = MpZ, powmod; + import std.algorithm.mutation : move; + auto p = BigInt.fromHexString(ps); + auto q = BigInt.fromHexString(qs); + BigInt m = p.move(); + m *= q; + auto e = BigInt.fromHexString(es); + BigInt b = e.dup; + b = b.powmod(e, m); + debug dout << b << endl; +} + +void main() +{ + version (assert) + { + testStd(); + testMir(); + testGmp(); + dout << "please compile with --build=release" << endl; + } + else + { + import std.system: os; + const res = 10.benchmark!(testStd, testMir, testGmp); + const mirRatio = double(res[0].total!"usecs") / res[1].total!"usecs"; + const gmpRatio = double(res[0].total!"usecs") / res[2].total!"usecs"; + dout + << "--------------------------------------------" << endl + << "gmp speedup = " << cast(int)((gmpRatio - 1) * 100_0) / 10.0 << "%" << endl + << "mir speedup = " << cast(int)((mirRatio - 1) * 100_0) / 10.0 << "%" << endl + << "std = " << res[0] << endl + << "mir = " << res[1] << endl + << "gmp = " << res[2] << endl + << " ............... " << size_t.sizeof * 8 << "bit " << os << " ............... " << endl + << "--------------------------------------------" + << endl; + } +} diff --git a/cpp_example/main.cpp b/cpp_example/main.cpp index 881078bb..8900d2ee 100644 --- a/cpp_example/main.cpp +++ b/cpp_example/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "mir/interpolate.h" #include "mir/series.h" #include "mir/rcarray.h" #include "mir/rcptr.h" @@ -48,6 +49,10 @@ int main() assert(al[1] == 6); assert(al[2] == 4); + assert(al.backward(2) == 5); + assert(al.backward(1) == 6); + assert(al.backward(0) == 4); + assert(av[0] == 5); assert(av[1] == 6); assert(av[2] == 4); diff --git a/cpp_example/meson.build b/cpp_example/meson.build index 519c83e3..25960408 100644 --- a/cpp_example/meson.build +++ b/cpp_example/meson.build @@ -1,6 +1,6 @@ mir_algorithm_cpp_test_exe = executable(meson.project_name() + '-test', ['eye.d', 'init_rcarray.d', 'main.cpp'], - include_directories: mir_algorithm_dir, + include_directories: directories, dependencies: mir_algorithm_dep, ) diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index b309df2a..00000000 --- a/doc/Makefile +++ /dev/null @@ -1,150 +0,0 @@ -########################################################### -# This file builds the mir documentation. -########################################################### - -# tags -LATEST:=$(shell git describe --abbrev=0 --tags | tr -d v) - -# binaries -DMD=dmd -RDMD=rdmd -DDOC=$(DMD) -w -c -o- -version=StdDdoc -version=Have_mir - -# folders -DOC_OUTPUT_DIR=web -DOC_SOURCE_DIR=doc -GENERATED=.generated -ALGORITHM_DIR=source -CORE_DIR=subprojects/mir-core/source -DLANGORG_DIR=$(DOC_SOURCE_DIR)/dlang.org -ARTWORK_DIR=$(DOC_SOURCE_DIR)/artwork - -########################################################### -# setup packages -########################################################## - -# Packages in mir. Just mention the package name here. The contents of package -# xy/zz is in variable PACKAGE_xy_zz. This allows automation in iterating -# packages and their modules. -MIR_PACKAGES = mir mir/rc mir/ndslice mir/ndslice/connect mir/math mir/math/func mir/array mir/interpolate mir/graph mir/combinatorics mir/container mir/algorithm - -PACKAGE_mir = range series numeric type_info small_string - -PACKAGE_mir_algorithm = iteration setops -PACKAGE_mir_array = allocation -PACKAGE_mir_cpp_export = numeric -PACKAGE_mir_combinatorics = package -PACKAGE_mir_container = binaryheap -PACKAGE_mir_graph = tarjan package -PACKAGE_mir_interpolate = package constant linear spline pchip utility polynomial -PACKAGE_mir_math = sum numeric -PACKAGE_mir_math_func = expdigamma -PACKAGE_mir_ndslice_connect = cpython -PACKAGE_mir_rc = package ptr array context - -PACKAGE_mir_ndslice = \ - allocation\ - chunks\ - concatenation\ - dynamic\ - field\ - fuse\ - iterator\ - mutation\ - ndfield\ - package\ - slice\ - sorting\ - topology\ - traits\ - -MOD_EXCLUDES=$(addprefix --ex=,) - -########################################################### -# Setup macros + generate dynamic info needed -########################################################### - -all: html - -DLANGORG_MACROS=$(addprefix $(DLANGORG_DIR)/, macros html dlang.org) -STDDOC=$(addsuffix .ddoc, ${DLANGORG_MACROS} ${GENERATED}/${LATEST} $(DLANGORG_DIR)/std $(DOC_SOURCE_DIR)/custom ${GENERATED}/mir) $(NODATETIME) - -${GENERATED}/${LATEST}.ddoc : - mkdir -p $(dir $@) - echo "LATEST=${LATEST}" >$@ - echo "LATEST_STABLE=$(shell git tag | grep -vE "(alpha|beta)" | tail -n1 | tr -d v)" >> $@ - -${GENERATED}/mir.ddoc : $(DOC_SOURCE_DIR)/gen_modlist.d $(ALGORITHM_DIR) - mkdir -p $(dir $@) - $(RDMD) --compiler=$(DMD) $< $(ALGORITHM_DIR) $(MOD_EXCLUDES) >$@ - -########################################################### -# Makefile bootstrapping -# It's mostly about the conversion from mir.foo -> mir_foo -########################################################### - -# Given one or more packages, returns the modules they contain -P2MODULES=$(foreach P,$1,$(addprefix $P/,$(PACKAGE_$(subst /,_,$P)))) -MIR_MODULES=$(call P2MODULES,$(MIR_PACKAGES)) -SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(MIR_MODULES)) - -# D file to html, e.g. mir/combinatorics -> mir_combinatorics.html -# But "package.d" is special cased: std/range/package.d -> std_range.html -D2HTML=$(foreach p,$1,$(if $(subst package.d,,$(notdir $p)),$(subst /,_,$(subst .d,.html,$p)),$(subst /,_,$(subst /package.d,.html,$p)))) - -HTMLS=$(addprefix $(DOC_OUTPUT_DIR)/, \ - $(call D2HTML, $(SRC_DOCUMENTABLES))) - -$(DOC_OUTPUT_DIR)/. : - mkdir -p $@ - -# everything except index.d needs a source path -ADDSOURCE=$(if $(subst index.d,,$1),$(ALGORITHM_DIR)/$1,$1) - -# For each module, define a rule e.g.: -# ../web/phobos/std_conv.html : std/conv.d $(STDDOC) ; ... -$(foreach p,$(SRC_DOCUMENTABLES),$(eval \ -$(DOC_OUTPUT_DIR)/$(call D2HTML,$p) : $(call ADDSOURCE,$p) $(STDDOC) ;\ - $(DDOC) $(STDDOC) -I$(ALGORITHM_DIR) -I$(CORE_DIR) -Df$$@ $$<)) - -########################################################### -# Setup all other resources needed by dlang.org -########################################################### - -IMAGES=images/mir.svg favicon.ico - -JAVASCRIPT=$(addsuffix .js, $(addprefix js/, \ - codemirror-compressed dlang ddox listanchors run run_examples jquery-1.7.2.min)) - -STYLES=$(addsuffix .css, $(addprefix css/, \ - style print custom codemirror)) - -ALL_FILES = $(addprefix $(DOC_OUTPUT_DIR)/, \ - $(STYLES) $(IMAGES) $(JAVASCRIPT)) - -$(DOC_OUTPUT_DIR)/css/custom.css: $(DOC_SOURCE_DIR)/custom.css - @mkdir -p $(dir $@) - cp $< $@ - -$(DOC_OUTPUT_DIR)/js/run_examples.js: $(DOC_SOURCE_DIR)/run_examples_custom.js - @mkdir -p $(dir $@) - cp $< $@ - -$(DOC_OUTPUT_DIR)/images/mir.svg: $(ARTWORK_DIR)/logo/mir_site_logo.svg - @mkdir -p $(dir $@) - cp $< $@ - -$(DOC_OUTPUT_DIR)/% : $(DLANGORG_DIR)/% - @mkdir -p $(dir $@) - cp $< $@ - -html : $(DOC_OUTPUT_DIR)/. $(HTMLS) $(ALL_FILES) - -clean: - rm -rf $(DOC_OUTPUT_DIR) - rm -rf $(GENERATED) - -# prints the listed modules and sources -debug: - @echo $(SRC_DOCUMENTABLES) - @echo $(STDDOC) diff --git a/doc/artwork b/doc/artwork deleted file mode 160000 index ca74c580..00000000 --- a/doc/artwork +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ca74c580fa20f9dc4b9e9aa9ebb7fb9c3b514388 diff --git a/doc/custom.css b/doc/custom.css deleted file mode 100644 index f5c0dcda..00000000 --- a/doc/custom.css +++ /dev/null @@ -1,56 +0,0 @@ -span#search-query { - width: 12em; -} - -/* -custom color for mir -*/ - -#top { - background: #181839; - border-bottom: 1px #000 solid; -} - -#top #cssmenu > ul > li > ul -{ - background: #181839; - border: 1px solid #181829; -} - -#top a:hover, #top #cssmenu li.open > a, #top #cssmenu li.active > a { - background: #181829; -} - -a, .question, .expand-toggle { - color: #116; -} - -a:hover, .question:hover, .expand-toggle:hover { - color: #181829; -} - -.d_decl { - border-left: 5px solid #181829; -} - -#top img#logo -{ - height: 2.533em; - vertical-align: right; - width: 2.533em; -} - -#top .logo -{ - margin-left: 0em; - margin-right: 0em; - padding-top: 0px; -} - -h1, h2, h3, h4, h5, h6 -{ - font-weight: normal; - line-height: normal; - text-align: left; - margin: 0.2em 0 0.1em; -} \ No newline at end of file diff --git a/doc/custom.ddoc b/doc/custom.ddoc deleted file mode 100644 index d619551f..00000000 --- a/doc/custom.ddoc +++ /dev/null @@ -1,136 +0,0 @@ -_= -META_KEYWORDS=algorithm mir libmir numeric blas array vector tensor numpy math stats -META_DESCRIPTION=Generic Linear Algebra Subprograms -ROOT_DIR = -SUBNAV= -$(SUBNAV_TEMPLATE - $(DIVC head, - $(H2 Library Reference) - $(P $(LINK2 index.html, overview)) - ) - $(UL $(MODULE_MENU)) -) -_= - -PROJECT=algorithm -PAGE_TOOLS= -$(DIVID tools, $(DIV, - $(DIVC tip smallprint, - $(HTMLTAG3 a, href="https://github.com/libmir/mir-algorithm/issues", Report a bug) - $(DIV, - If you spot a problem with this page, click here to create a Github issue. - ) - ) - $(DIVC tip smallprint, - Improve this page - $(DIV, - Quickly fork, edit online, and submit a pull request for this page. - Requires a signed-in GitHub account. This works well for small changes. - If you'd like to make larger changes you may want to consider using - a local clone. - ) - ) -)) -_= - -DDOC= - - - - - - -$(T title, $(FULL_TITLE)) -$(COMMON_HEADERS_DLANG) - - - - - - -$(EXTRA_HEADERS) - - -$(SCRIPT document.body.className += ' have-javascript'; -var currentVersion = "$(LATEST_STABLE)"; -) -$(DIVID top, $(DIVC helper, $(DIVC helper expand-container, - Menu - $(NAVIGATION) - $(DIVC search-container expand-container, - Search - $(SEARCH_BOX) - ) -))) -$(LAYOUT_PREFIX) -$(DIVC container, - $(SUBNAV) - $(DIVCID $(HYPHENATE), content, - $(PAGE_TOOLS) - $(LAYOUT_TITLE) - $(BODY_PREFIX) - $(BODY) - $(FOOTER) - ) -) -$(COMMON_SCRIPTS) -$(LAYOUT_SUFFIX) - - - -_= -SEARCH_BOX= - $(DIVID search-box, -
- - - - $(SPANID search-query, ) - $(SPANID search-submit, ) -
- ) -_= - -NAVIGATION= -$(DIVID cssmenu, $(UL - $(MENU https://github.com/libmir/mir-algorithm, Github) - $(MENU https://github.com/libmir/mir-algorithm/issues, Issues) - $(MENU https://gitter.im/libmir/public , Chat) - $(MENU_W_SUBMENU_LINK $(ROOT_DIR)mir.html, Mir Projects) - $(MIR_PROJECTS) - $(MENU https://dlang.org, Dlang) -)) -_= - -MIR_PROJECTS= -$(SUBMENU_MANUAL - $(SUBMENU_LINK http://docs.mir.dlang.io, Mir) - $(SUBMENU_LINK http://mir-algorithm.libmir.org, Mir Algorithm) - $(SUBMENU_LINK http://docs.cpuid.dlang.io, CPUID) - $(SUBMENU_LINK http://docs.glas.dlang.io, GLAS) - $(SUBMENU_LINK http://docs.random.dlang.io, Random Numbers Generators) -) -_= - -COMMON_HEADERS_DLANG= - -_= -COMMON_SCRIPTS = - $(SCRIPTLOAD https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js) - $(SCRIPT window.jQuery || document.write('\x3Cscript src="$(STATIC js/jquery-1.7.2.min.js)">\x3C/script>');$(EXTRA_JS)) - $(SCRIPTLOAD $(STATIC js/dlang.js)) - $(COMMON_SCRIPTS_DLANG) -_= -COMMON_SCRIPTS_DLANG = - $(SCRIPTLOAD $(STATIC js/codemirror-compressed.js)) - $(SCRIPTLOAD $(STATIC js/run.js)) - $(SCRIPTLOAD $(STATIC js/run_examples.js)) -_= - -COMMON_HEADERS_DLANG= -_= - -LAYOUT_SUFFIX = -$(SCRIPTLOAD js/listanchors.js) -$(SCRIPT jQuery(document).ready(listanchors);) -_= diff --git a/doc/dlang.org b/doc/dlang.org deleted file mode 160000 index 2df16889..00000000 --- a/doc/dlang.org +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2df1688937c7a6872784451885babbf5a63b76c7 diff --git a/doc/gen_modlist.d b/doc/gen_modlist.d deleted file mode 100644 index 4922e147..00000000 --- a/doc/gen_modlist.d +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env rdmd -/** - * Copied + modified from dlang.org - * Copyright: Martin Nowak 2015-. - * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: $(WEB code.dawg.eu, Martin Nowak) - */ -import std.algorithm, std.file, std.path, std.stdio, std.string, std.range; - -struct Tree -{ - ref Tree insert(R)(R parts) - { - if (parts.front == "package") - { - pkgMod = true; - return this; - } - - auto tail = leaves.find!((tr, pkg) => tr.name == pkg)(parts.front); - if (tail.empty) - { - leaves ~= Tree(parts.front); - tail = leaves[$-1 .. $]; - } - parts.popFront(); - return parts.empty ? this : tail.front.insert(parts); - } - - void sort() - { - leaves = leaves.sort!((a, b) => a.name < b.name).release; - foreach (ref l; leaves) - l.sort(); - } - - void dumpRoot() - { - writeln(); - writefln("$(MENU_W_SUBMENU $(TT %s))", name); - writefln("$(ITEMIZE"); - dumpChildren([name]); - writeln(")"); - writeln("$(MENU_W_SUBMENU_END)"); - } - - void dumpChildren(string[] pkgs) - { - foreach (i, ref l; leaves) - { - l.dump(pkgs); - writeln(i + 1 == leaves.length ? "" : ","); - } - } - - void dump(string[] pkgs) - { - if (leaves.empty) - { - writef("%s$(MODULE%s %-(%s, %), %s)", indent(pkgs.length), pkgs.length + 1, pkgs, name); - } - else - { - if (pkgMod) - writefln("%s$(PACKAGE $(MODULE%s %-(%s, %), %s),", indent(pkgs.length), pkgs.length + 1, pkgs, name); - else - writefln("%s$(PACKAGE $(PACKAGE_NAME %s),", indent(pkgs.length), name); - dumpChildren(pkgs ~ name); - writef("%s)", indent(pkgs.length)); - } - } - - ref Tree opIndex(string part) - { - auto tail = leaves.find!((tr, pkg) => tr.name == pkg)(part); - assert(!tail.empty, part); - return tail.front; - } - - static string indent(size_t len) - { - static immutable spaces = " "; - return spaces[0 .. 2 * len]; - } - - string name; - bool pkgMod; - Tree[] leaves; -} - -int main(string[] args) -{ - if (args.length < 2) - { - stderr.writeln("usage: ./modlist [--ex=std.internal.] [--ex=core.sys.]"); - return 1; - } - - auto phobos = args[1]; - auto excludes = args[2 .. $].map!(ex => ex.chompPrefix("--ex=")).array; - - bool included(string mod) - { - return !excludes.canFind!(e => mod.startsWith(e)); - } - - void add(string path, ref Tree tree) - { - auto files = dirEntries(path, "*.d", SpanMode.depth) - .filter!(de => de.isFile) - .map!(de => de.name.chompPrefix(path).chompPrefix(dirSeparator)) // relative to path - .map!(n => n.chomp(".d").replace(dirSeparator, ".")) // std/digest/sha.d => std.digest.sha - .filter!included; - - foreach (string name; files) - tree.insert(name.splitter(".")); - } - Tree tree; - add(phobos, tree); - tree.sort(); - - writeln("MODULE_MENU="); - tree["mir"].dumpRoot(); - writeln("_="); - return 0; -} diff --git a/doc/run_examples_custom.js b/doc/run_examples_custom.js deleted file mode 100644 index b6ef413f..00000000 --- a/doc/run_examples_custom.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Run all unittest examples - * - * Copyright 2016 by D Language Foundation - * - * License: http://boost.org/LICENSE_1_0.txt, Boost License 1.0 - */ - -// wraps a unittest into a runnable script -function wrapIntoMain(code) { - var currentPackage = $('body')[0].id; - // BUMP mir-algorithm image here: https://github.com/dlang-tour/core-exec/blob/master/Dockerfile - // run.dlang.io frontend: https://github.com/dlang-tour/core/blob/master/public/static/js/tour-controller.js#L398 - var codeOut = '/+dub.sdl:\ndependency "mir-algorithm" version="~>'+currentVersion+'"\n+/\n'; - - // dynamically wrap into main if needed - if (code.indexOf("void main") >= 0) { - codeOut += "import " + currentPackage + "; "; - codeOut += code; - } - else { - codeOut += "void main()\n{\n"; - codeOut += " import " + currentPackage + ";\n"; - // writing to the stdout is probably often used - codeOut += " import std.stdio: write, writeln, writef, writefln;\n "; - codeOut += code.split("\n").join("\n "); - codeOut += "\n}"; - } - return codeOut; -} - -$(document).ready(function() -{ - if ($('body')[0].id == "Home") - return; - - // only for std at the moment - if (!$('body').hasClass("std")) - return; - - // first selector is for ddoc - second for ddox - var codeBlocks = $('pre[class~=d_code]').add('pre[class~=code]'); - codeBlocks.each(function(index) - { - var currentExample = $(this); - var orig = currentExample.html(); - - // check whether it is from a ddoced unittest - // 1) check is for ddoc, 2) for ddox - // manual created tests most likely can't be run without modifications - if (!($(this).parent().parent().prev().hasClass("dlang_runnable") || - $(this).prev().children(":last").hasClass("dlang_runnable"))) - return; - - currentExample.replaceWith( - '
' - + '
' - + '
'+orig+'
' - + '
' - + '
' - + '' - + '
' - + '
' - + '
Edit
' - + '
Run
' - + '' - + '
' - + '
' - + '
Application output
Running...
' - + '
' - ); - }); - - $('textarea[class=d_code]').each(function(index) { - var parent = $(this).parent(); - var btnParent = parent.parent().children(".d_example_buttons"); - var outputDiv = parent.parent().children(".d_code_output"); - var editor = setupTextarea(this, { - parent: btnParent, - outputDiv: outputDiv, - stdin: false, - args: false, - transformOutput: wrapIntoMain, - defaultOutput: "All tests passed", - keepCode: true, - outputHeight: "auto", - backend: "tour" - }); - }); -}); diff --git a/dub.sdl b/dub.sdl index 36bc1e81..e8085ba7 100644 --- a/dub.sdl +++ b/dub.sdl @@ -1,28 +1,85 @@ name "mir-algorithm" -description "Mir Algorithm Collection" +description "Dlang Core Library" -authors "Ilya Yaroshenko" "Sebastian Wilzbach" "Andrei Alexandrescu and Phobos Team (original Phobos code)" "John Michael Hall" -copyright "Copyright © 2016 - 2018, Ilya Yaroshenko; see also information per file." -license "BSL-1.0" +authors "Ilia Ki" "John Michael Hall" "Shigeki Karita" "Sebastian Wilzbach" "And others" "mir.date and a bit of other code is based on Phobos" +copyright "2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments" +license "Apache-2.0" -dependency "mir-core" version=">=0.3.5 <0.4.0" +dependency "mir-core" version=">=1.6.0" + +// dflags "-version=MirNoSIMD" +// dflags "-mtriple=aarch64-linux-gnu" + +// versions "TeslAlgoM" buildType "unittest" { - buildOptions "unittests" "debugMode" "debugInfo" + buildOptions "unittests" "debugMode" "debugInfo" + versions "mir_bignum_test" "mir_bignum_test_llv" + versions "mir_ndslice_test" + versions "mir_test" + dflags "-lowmem" +} + +buildType "unittest-verbose" { + buildOptions "unittests" "debugMode" "debugInfo" + versions "mir_bignum_test" "mir_bignum_test_llv" + // versions "mir_ndslice_test" versions "mir_test" + dflags "-lowmem" "-checkaction=context" "-allinst" +} +buildType "unittest-dip1008" { + buildOptions "unittests" "debugMode" "debugInfo" + versions "mir_bignum_test" "mir_ndslice_test" "mir_test" "mir_bignum_test_llv" + dflags "-lowmem" "-preview=dip1008" +} +buildType "unittest-dip1000" { + buildOptions "unittests" "debugMode" "debugInfo" + versions "mir_bignum_test" "mir_ndslice_test" "mir_test" + dflags "-lowmem" "-preview=dip1000" } buildType "unittest-cov" { - buildOptions "unittests" "coverage" "debugMode" "debugInfo" - versions "mir_test" + buildOptions "unittests" "coverage" "debugMode" "debugInfo" + versions "mir_bignum_test" "mir_bignum_test_llv" "mir_ndslice_test" "mir_test" + dflags "-lowmem" +} +buildType "unittest-ci" { + buildOptions "unittests" "coverage" "debugMode" "debugInfo" + dflags "-lowmem" } buildType "unittest-release" { - buildOptions "unittests" "releaseMode" "optimize" "inline" "noBoundsCheck" - versions "mir_test" + buildOptions "unittests" "releaseMode" "optimize" "inline" "noBoundsCheck" + versions "mir_bignum_test" "mir_ndslice_test" "mir_test" + dflags "-lowmem" } configuration "default" { } +configuration "silly" { + dependency "silly" version="~>1.1.1" +} + +configuration "dip1008" { + dflags "-preview=dip1008" +} + configuration "dips" { - dflags "-dip25" "-dip1000" "-dip1008" + dflags "-preview=dip1000" "-preview=dip1008" +} + +configuration "ci-bignum-test" { + versions "mir_bignum_test" "mir_bignum_test_llv" + dflags "-lowmem" "-preview=dip1000" +} + +configuration "ci-core-test" { + versions "mir_core_test" +} + +configuration "ci-ndslice-test" { + versions "mir_ndslice_test" +} + +configuration "ci-test" { + versions "mir_test" } diff --git a/images/symmetry.png b/images/symmetry.png deleted file mode 100644 index 4dcca0f3..00000000 Binary files a/images/symmetry.png and /dev/null differ diff --git a/include/mir/interpolate.h b/include/mir/interpolate.h new file mode 100644 index 00000000..ddb39011 --- /dev/null +++ b/include/mir/interpolate.h @@ -0,0 +1,35 @@ +#pragma once + +namespace mir { + namespace interpolate + { + enum class SplineType + { + c2, + cardinal, + monotone, + doubleQuadratic, + akima, + makima + }; + + enum class SplineBoundaryType + { + periodic = -1, + notAKnot, + firstDerivative, + secondDerivative, + parabolic, + monotone, + akima, + makima + }; + + template + struct SplineBoundaryCondition + { + SplineBoundaryType type = SplineBoundaryType::notAKnot; + T value = 0; + }; + } +} diff --git a/include/mir/ndslice.h b/include/mir/ndslice.h index 62ec07f5..c5f23713 100644 --- a/include/mir/ndslice.h +++ b/include/mir/ndslice.h @@ -1,21 +1,21 @@ +#ifndef MIR_NDSLICE + +#define MIR_NDSLICE + /** ************ Mir-Algorithm ************ The module provides wrappers for $(SUBREF slice, Slice) that can be used as arguments for C++ functions. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2017-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki */ - -#ifndef MIR_NDSLICE - -#define MIR_NDSLICE - #include #include #include +#include #if INTPTR_MAX == INT32_MAX #define mir_size_t unsigned int @@ -40,6 +40,21 @@ enum class mir_slice_kind : int contiguous = 2 }; +template +struct mir_rci; + +namespace mir { + template < + typename T + > + const T* light_const(const T* ptr) { return ptr; } + + template < + typename T + > + mir_rci light_const(const mir_rci& s); +} + template < typename Iterator, mir_size_t N = 1, @@ -172,12 +187,12 @@ struct mir_slice auto begin() const noexcept { - return _iterator; + return mir::light_const(_iterator); } auto cbegin() const noexcept { - return _iterator; + return begin(); } Iterator end() noexcept @@ -187,12 +202,12 @@ struct mir_slice auto end() const noexcept { - return _iterator + _lengths[0] * _lengths[1]; + return mir::light_const(_iterator) + _lengths[0] * _lengths[1]; } auto cend() const noexcept { - return _iterator + _lengths[0] * _lengths[1]; + return end(); } }; @@ -205,6 +220,11 @@ struct mir_slice static const mir_ptrdiff_t _strides[0]; Iterator _iterator = nullptr; + using iterator = Iterator; + using const_iterator = Iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + template size_t size() const noexcept { @@ -232,6 +252,16 @@ struct mir_slice return _iterator[index]; } + auto&& backward(mir_size_t index) + { + return at(size() - 1 - index); + } + + auto&& backward(mir_size_t index) const + { + return at(size() - 1 - index); + } + auto&& operator[](mir_size_t index) { return at(index); @@ -247,6 +277,11 @@ struct mir_slice return _iterator; } + auto begin() const noexcept + { + return mir::light_const(_iterator); + } + auto cbegin() const noexcept { return _iterator; @@ -257,10 +292,22 @@ struct mir_slice return _iterator + _lengths[0]; } + auto end() const noexcept + { + return mir::light_const(_iterator) + _lengths[0]; + } + auto cend() const noexcept { - return _iterator + _lengths[0]; + return end(); } + + reverse_iterator rbegin() noexcept { return reverse_iterator(this->begin()); } + const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(this->begin()); } + const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(this->begin()); } + reverse_iterator rend() noexcept { return reverse_iterator(this->end()); } + const_reverse_iterator rend() const noexcept { return const_reverse_iterator(this->end()); } + const_reverse_iterator crend() const noexcept { return const_reverse_iterator(this->end()); } }; template < @@ -312,11 +359,6 @@ struct mir_slice namespace mir { - template < - typename T - > - const T* light_const(const T* ptr) { return ptr; } - template < typename T, mir_size_t N, diff --git a/include/mir/rcarray.h b/include/mir/rcarray.h index 69f8ed8f..78c5f5de 100644 --- a/include/mir/rcarray.h +++ b/include/mir/rcarray.h @@ -24,9 +24,7 @@ struct mir_rcarray private: T* _payload = nullptr; - using U = typename std::remove_all_extents::type; - static constexpr void (*destr)(U&) = std::is_destructible::value ? &mir::Destructor::destroy : nullptr; - static constexpr mir::type_info_g typeInfoT = {destr, sizeof(T)}; + using U = typename std::remove_const::type; void _cpp_copy_constructor(const mir_rcarray& rhs) noexcept; mir_rcarray& _cpp_assign(const mir_rcarray& rhs) noexcept; @@ -38,7 +36,7 @@ struct mir_rcarray { if (length == 0) return; - auto context = mir_rc_create((const mir_type_info*)&typeInfoT, length, nullptr, false, deallocate); + auto context = mir_rc_create(mir::typeInfoT_(), length, nullptr, false, deallocate); if (context == nullptr) throw std::bad_alloc(); _payload = (T*)(context + 1); @@ -159,6 +157,8 @@ struct mir_rcarray size_t empty() const noexcept { return size() == 0; } T& at(size_t index) { if (index >= this->size()) throw std::out_of_range("mir_rcarray: out of range"); return _payload[index]; } const T& at(size_t index) const { if (index >= this->size()) throw std::out_of_range("mir_rcarray: out of range"); return _payload[index]; } + T& backward(size_t index) { if (index >= this->size()) throw std::out_of_range("mir_rcarray: out of range"); return _payload[size() - 1 - index]; } + const T& backward(size_t index) const { if (index >= this->size()) throw std::out_of_range("mir_rcarray: out of range"); return _payload[size() - 1 - index]; } T& operator[](size_t index) { if (index >= this->size()) throw std::out_of_range("mir_rcarray: out of range"); return _payload[index]; } const T& operator[](size_t index) const { if (index >= this->size()) throw std::out_of_range("mir_rcarray: out of range"); return _payload[index]; } T* data() noexcept { return _payload; } @@ -185,6 +185,12 @@ struct mir_rci using Iterator = T*; using Array = mir_rcarray; + using difference_type = ptrdiff_t; + using value_type = T; + using pointer = T*; + using reference = T&; + using iterator_category = std::random_access_iterator_tag; + Iterator _iterator = nullptr; mir_rcarray _array; @@ -235,13 +241,13 @@ struct mir_rci return *this; } - mir_rci& operator+=(mir_ptrdiff_t shift) + mir_rci& operator+=(ptrdiff_t shift) { _iterator += shift; return *this; } - mir_rci& operator-=(mir_ptrdiff_t shift) + mir_rci& operator-=(ptrdiff_t shift) { _iterator -= shift; return *this; @@ -261,6 +267,11 @@ struct mir_rci return ret; } + ptrdiff_t operator-(const mir_rci& shift) const + { + return _iterator - shift._iterator; + } + T& operator[](size_t index) { auto ptr = _iterator + index; @@ -275,26 +286,16 @@ struct mir_rci return _iterator[index]; } - mir_rci operator+(ptrdiff_t index) + mir_rci operator+(ptrdiff_t index) const { return mir_rci(_iterator + index, _array); } - mir_rci operator-(ptrdiff_t index) + mir_rci operator-(ptrdiff_t index) const { return mir_rci(_iterator - index, _array); } - mir_rci operator+(ptrdiff_t index) const - { - return mir_rci(_iterator + index, _array); - } - - mir_rci operator-(ptrdiff_t index) const - { - return mir_rci(_iterator - index, _array); - } - bool operator==(const mir_rci& rhs) const { return _iterator == rhs._iterator; } bool operator!=(const mir_rci& rhs) const { return _iterator != rhs._iterator; } bool operator<(const mir_rci& rhs) const { return _iterator < rhs._iterator; } diff --git a/include/mir/rcptr.h b/include/mir/rcptr.h index e5a18e0f..9959eceb 100644 --- a/include/mir/rcptr.h +++ b/include/mir/rcptr.h @@ -33,7 +33,7 @@ extern "C" const mir_type_info* typeInfo, size_t length, const void* payload = nullptr, - bool initialise = true, + bool initialize = true, bool deallocate = true ); } @@ -72,6 +72,13 @@ namespace mir // template::value>::type> + template + static const mir_type_info* typeInfoT_() + { + static constexpr void (*destr)(U&) = std::is_destructible::value ? &Destructor::destroy : nullptr; + static constexpr type_info_g value = {destr, sizeof(U)}; + return (const mir_type_info*)&value; + } } // Does not support allocators for now @@ -82,9 +89,7 @@ struct mir_rcptr T* _payload = nullptr; mir_rc_context* _context = nullptr; - using U = typename std::remove_all_extents::type; - static constexpr void (*destr)(U&) = std::is_destructible::value ? &mir::Destructor::destroy : nullptr; - static constexpr mir::type_info_g typeInfoT = {destr, sizeof(T)}; + using U = typename std::remove_const::type; public: @@ -114,7 +119,7 @@ struct mir_rcptr using U = typename std::remove_const::type; static_assert( std::is_constructible::value, "Can't construct object in mir_rcptr constructor" ); mir_rcptr ret; - ret._context = mir_rc_create((const mir_type_info*)&typeInfoT, 1); + ret._context = mir_rc_create(mir::typeInfoT_(), 1); if (ret._context == nullptr) throw std::bad_alloc(); ret._payload = (T*)(ret._context + 1); @@ -200,7 +205,7 @@ struct mir_rcptr T* _payload = nullptr; mir_rc_context* _context = nullptr; - using U = typename std::remove_all_extents::type; + using U = typename std::remove_const::type; static constexpr void (*destr)(U&) = std::is_destructible::value ? &mir::Destructor::destroy : nullptr; static constexpr mir::type_info_g typeInfoT = {destr, sizeof(T)}; @@ -317,7 +322,7 @@ struct mir_rcptr T* _payload = nullptr; mir_rc_context* _context = nullptr; - using U = typename std::remove_all_extents::type; + using U = typename std::remove_const::type; static constexpr void (*destr)(U&) = std::is_destructible::value ? &mir::Destructor::destroy : nullptr; static constexpr mir::type_info_g typeInfoT = {destr, sizeof(T)}; diff --git a/include/mir/series.h b/include/mir/series.h index 77cbfbd2..c7e39ddf 100644 --- a/include/mir/series.h +++ b/include/mir/series.h @@ -2,6 +2,7 @@ #define MIR_SERIES +#include #include #include #include "mir/ndslice.h" @@ -32,6 +33,9 @@ struct mir_series using Index = typename std::remove_reference::type; /// Data / Value type aliases using Data = typename std::remove_reference::type; + + using UnqualIndex = typename std::remove_const::type; + using UnqualData = typename std::remove_const::type; using Observation = std::pair; // using ConstObservation = std::pair; @@ -71,13 +75,28 @@ struct mir_series return {_index[index], _data[index]}; } + Observation backward(mir_size_t index) const noexcept + { + return {_index[size() - 1 - index], _data[size() - 1 - index]}; + } + Observation operator[](mir_size_t index) const noexcept { return {_index[index], _data[index]}; } - template - size_t transition_index_less(const T& val) const + mir_series slice(mir_size_t a, mir_size_t b) + { + if (a > b) + throw std::out_of_range("series::slice: a > b"); + if (b > size()) + throw std::out_of_range("series::slice: b > size()"); + auto newIndex = _index; + newIndex += a; + return { {{b - a}, _data._iterator}, std::move(newIndex) }; + } + + size_t transition_index_less(const Index& val) const { size_t first = 0, count = size(); while (count > 0) @@ -96,8 +115,7 @@ struct mir_series return first; } - template - size_t transition_index_less_or_equal(const T& val) const + size_t transition_index_less_or_equal(const Index& val) const { size_t first = 0, count = size(); while (count > 0) @@ -116,15 +134,13 @@ struct mir_series return first; } - template - bool contains(const T& key) const + bool contains(const Index& key) const { size_t idx = transition_index_less(key); return idx < _data._lengths[0] && _index[idx] == key; } - template - bool try_get(const T& key, Value& val) const + bool try_get(const Index& key, UnqualData& val) const { size_t idx = transition_index_less(key); auto cond = idx < _data._lengths[0] && _index[idx] == key; @@ -133,8 +149,7 @@ struct mir_series return cond; } - template - const Data* try_get_ptr(const T& key) const + const Data* try_get_ptr(const Index& key) const { size_t idx = transition_index_less(key); auto cond = idx < _data._lengths[0] && _index[idx] == key; @@ -143,8 +158,7 @@ struct mir_series return nullptr; } - template - auto&& get(const T& key) + auto&& get(const Index& key) { size_t idx = transition_index_less(key); auto cond = idx < _data._lengths[0] && _index[idx] == key; @@ -153,8 +167,7 @@ struct mir_series throw std::out_of_range("series::get: key not found"); } - template - auto&& get(const T& key) const + auto&& get(const Index& key) const { size_t idx = transition_index_less(key); auto cond = idx < _data._lengths[0] && _index[idx] == key; @@ -163,8 +176,7 @@ struct mir_series throw std::out_of_range("series::get: key not found"); } - template - bool try_get_next(const T& key, Value& val) const + bool try_get_next(const Index& key, UnqualData& val) const { size_t idx = transition_index_less(key); auto cond = idx < _data._lengths[0]; @@ -173,8 +185,7 @@ struct mir_series return cond; } - template - bool try_get_next_update_key(T& key, Value& val) const + bool try_get_next_update_key(UnqualIndex& key, UnqualData& val) const { size_t idx = transition_index_less(key); auto cond = idx < _data._lengths[0]; @@ -186,8 +197,7 @@ struct mir_series return cond; } - template - bool try_get_prev(const T& key, Value& val) const + bool try_get_prev(const Index& key, UnqualData& val) const { size_t idx = transition_index_less_or_equal(key) - 1; auto cond = 0 <= (ptrdiff_t) idx; @@ -196,8 +206,7 @@ struct mir_series return cond; } - template - bool try_get_prev_update_key(T& key, Value& val) const + bool try_get_prev_update_key(UnqualIndex& key, UnqualData& val) const { size_t idx = transition_index_less_or_equal(key) - 1; auto cond = 0 <= (ptrdiff_t) idx; @@ -209,8 +218,7 @@ struct mir_series return cond; } - template - bool try_get_first(const T& lowerBound, const T& upperBound, Value& val) const + bool try_get_first(const Index& lowerBound, const Index& upperBound, UnqualData& val) const { size_t idx = transition_index_less(lowerBound); auto cond = idx < _data._lengths[0] && _index[idx] <= upperBound; @@ -219,8 +227,7 @@ struct mir_series return cond; } - template - bool try_get_first_update_lower(T& lowerBound, const T& upperBound, Value& val) const + bool try_get_first_update_lower(UnqualIndex& lowerBound, const Index& upperBound, UnqualData& val) const { size_t idx = transition_index_less(lowerBound); auto cond = idx < _data._lengths[0] && _index[idx] <= upperBound; @@ -232,8 +239,7 @@ struct mir_series return cond; } - template - bool try_get_last(const T& lowerBound, const T& upperBound, Value& val) const + bool try_get_last(const Index& lowerBound, const Index& upperBound, UnqualData& val) const { size_t idx = transition_index_less_or_equal(upperBound) - 1; auto cond = 0 <= (ptrdiff_t) idx && lowerBound <= _index[idx]; @@ -242,8 +248,7 @@ struct mir_series return cond; } - template - bool try_get_last_update_upper(const T& lowerBound, T& upperBound, Value& val) const + bool try_get_last_update_upper(const Index& lowerBound, UnqualIndex& upperBound, UnqualData& val) const { size_t idx = transition_index_less_or_equal(upperBound) - 1; auto cond = 0 <= (ptrdiff_t) idx && lowerBound <= _index[idx]; @@ -257,6 +262,12 @@ struct mir_series struct ThisIterator { + using value_type = Data; + using difference_type = mir_ptrdiff_t; + using reference = Observation; + using pointer = void; + using iterator_category = std::input_iterator_tag; + mir_size_t _index = 0; mir_series _series; ThisIterator& operator++() noexcept { ++_index; return *this;} @@ -268,7 +279,7 @@ struct mir_series bool operator>=(const ThisIterator& rhs) const noexcept { return _index >= rhs._index; } bool operator<=(const ThisIterator& rhs) const noexcept { return _index <= rhs._index; } - Observation operator*() noexcept + reference operator*() noexcept { static_assert(kind == mir_slice_kind::contiguous && N == 1, "The method is defined only for 1-dimensional slice."); return _series[_index]; diff --git a/include/mir/slim_rcptr.h b/include/mir/slim_rcptr.h index 81229738..8a04c8e3 100644 --- a/include/mir/slim_rcptr.h +++ b/include/mir/slim_rcptr.h @@ -11,9 +11,7 @@ struct mir_slim_rcptr private: T* _payload = nullptr; - using U = typename std::remove_all_extents::type; - static constexpr void (*destr)(U&) = std::is_destructible::value ? &mir::Destructor::destroy : nullptr; - static constexpr mir::type_info_g typeInfoT = {destr, sizeof(T)}; + using U = typename std::remove_const::type; public: @@ -49,7 +47,7 @@ struct mir_slim_rcptr using U = typename std::remove_const::type; static_assert( std::is_constructible::value, "Can't construct object in mir_slim_rcptr constructor" ); mir_slim_rcptr ret; - auto context = mir_rc_create((const mir_type_info*)&typeInfoT, 1); + auto context = mir_rc_create(mir::typeInfoT_(), 1); if (context == nullptr) throw std::bad_alloc(); ret._payload = (T*)(context + 1); diff --git a/include/mir/small_string.h b/include/mir/small_string.h index c191ac7a..73d677bf 100644 --- a/include/mir/small_string.h +++ b/include/mir/small_string.h @@ -1,3 +1,4 @@ +#pragma once // Self contained generic small string implementaton. #include #include @@ -23,7 +24,7 @@ namespace mir SmallString(const std::string& str) : SmallString((std::string_view)str) {} SmallString(const char* str) : SmallString(std::string_view(str)) {} - std::string_view str() const noexcept { return std::string_view(_data, _data[maxLength] ? maxLength : std::strlen(_data)); } + std::string_view str() const noexcept { return std::string_view(_data, _data[maxLength - 1] ? maxLength : std::strlen(_data)); } operator std::string_view() const noexcept { return str(); } operator bool() const noexcept { return _data[0] != 0; } bool operator !() const noexcept { return _data[0] == 0; } diff --git a/index.d b/index.d index 96dfb501..0b863783 100644 --- a/index.d +++ b/index.d @@ -10,38 +10,64 @@ $(BOOKTABLE , $(TH Modules) $(TH Description) ) + $(LEADINGROW Accessories) + $(TR $(TDNW $(MREF mir,algebraic_alias,ion)) $(TD Mutable Amazon's Ion value)) + $(TR $(TDNW $(MREF mir,algebraic_alias,json)) $(TD Mutable JSON value)) + $(TR $(TDNW $(MREF mir,algebraic_alias,transform)) $(TD Mutation algorithms for Ion/JSON-like values)) + $(TR $(TDNW $(MREF mir,annotated)) $(TD Mutable generic Annotated value)) + $(TR $(TDNW $(MREF mir,array,allocation)) $(TD `std.array` reworked for Mir)) + $(TR $(TDNW $(MREF mir,format)) $(TD @nogc Formatting Utilities)) + $(TR $(TDNW $(MREF mir,lob)) $(TD Binar and Char Large Objects )) + $(TR $(TDNW $(MREF mir,parse)) $(TD @nogc Parsing Utilities)) + $(TR $(TDNW $(MREF mir,range)) $(TD Ranges)) + $(TR $(TDNW $(MREF mir,serde)) $(TD Utilities for serialization libraries )) + $(TR $(TDNW $(MREF mir,small_array)) $(TD Generic Small Arrays)) + $(TR $(TDNW $(MREF mir,small_string)) $(TD Generic Small Strings)) + $(TR $(TDNW $(MREF mir,string_map)) $(TD Ordered string-value associative array with fast lookup)) + $(TR $(TDNW $(MREF mir,test)) $(TD Testing utilities)) + $(LEADINGROW Date and time) + $(TR $(TDNW $(MREF mir,date)) $(TD Fast BetterC Date type with Boost ABI and mangling compatability)) + $(TR $(TDNW $(MREF mir,timestamp)) $(TD General purpose timestamp implementation with arbitrary precision )) $(LEADINGROW NDarrays and Algorithms) - $(TR $(TDNW $(MREF mir,ndslice)★) $(TD Package for ndarrays and iteration algorithms.)) - $(TR $(TDNW $(MREF mir,range)) $(TD Additoinal primitive ranges. See also $(MREF mir,ndslice), which contains a lot of range constructos.)) - $(TR $(TDNW $(MREF mir,algorithm,setops)) $(TD Mir & BetterC rework of Phobos.)) $(TR $(TDNW $(MREF mir,algorithm,iteration)) $(TD Mir & BetterC rework of Phobos.)) + $(TR $(TDNW $(MREF mir,algorithm,setops)) $(TD Mir & BetterC rework of Phobos.)) + $(TR $(TDNW $(MREF mir,ndslice)★) $(TD Package for ndarrays and iteration algorithms.)) + $(TR $(TDNW $(MREF mir,range)) $(TD Additional primitive ranges. See also $(MREF mir,ndslice), which contains a lot of range constructos.)) $(LEADINGROW Math) - $(TR $(TDNW $(MREF mir,numeric)) $(TD Basic numeric optimisations)) + $(TR $(TDNW $(MREF mir,interpolate)★) $(TD Interpolation algorithms)) $(TR $(TDNW $(MREF mir,math,numeric)) $(TD Simple numeric algorithms)) - $(TR $(TDNW $(MREF mir,math,sum)★) $(TD Various precise summation algorithms)) - $(TR $(TDNW $(MREF mir,math,constant)) $(TD Math constants)) + $(TR $(TDNW $(MREF mir,math,stat)) $(TD Basic API for statistics)) + $(TR $(TDNW $(MREF mir,math,sum)) $(TD Various precise summation algorithms)) + $(TR $(TDNW $(MREF mir,numeric)) $(TD Basic numeric optimisations)) + $(TR $(TDNW $(MREF mir,polynomial)) $(TD Polynomial ref-counted structure)) + $(TR $(TDNW $(MREF mir,ediff)) $(TD Expression differentiation)) + $(TR $(TDNW $(MREF mir,math,func,expdigamma)) $(TD `exp(digamma(x))`)) + $(TR $(TDNW $(MREF mir,math,func,normal)) $(TD Normal Distribution API)) $(LEADINGROW Reference counting) $(TR $(TDNW $(MREF mir,rc,array)) $(TD Thread safe reference count array and the iterator to adopt it to ndslice.)) - $(TR $(TDNW $(MREF mir,rc,ptr)) $(TD Thread safe reference count pointer for strucs and objects.)) + $(TR $(TDNW $(MREF mir,rc,ptr)) $(TD Thread safe reference count pointer with polymorphism support for strucs and objects.)) + $(TR $(TDNW $(MREF mir,rc,slim_ptr)) $(TD Thread safe reference count pointer for strucs and objects.)) + $(TR $(TDNW $(MREF mir,rc)) $(TD Reference counting package and RC conversion utilities.)) $(LEADINGROW Containers) - $(TR $(TDNW $(MREF mir,series)★) $(TD Generic series suitable for time-series or semi-immutable ordered maps with CPU cache friendly binary search.)) + $(TR $(TDNW $(MREF mir,appender)) $(TD Scoped Buffer.)) $(TR $(TDNW $(MREF mir,container,binaryheap)★) $(TD Mir & BetterC rework of Phobos.)) + $(TR $(TDNW $(MREF mir,series)★) $(TD Generic series suitable for time-series or semi-immutable ordered maps with CPU cache friendly binary search.)) $(LEADINGROW Graphs) - $(TR $(TDNW $(MREF mir,graph)) $(TD Basic routines to work with graphs)) $(TR $(TDNW $(MREF mir,graph,tarjan)★) $(TD Tarjan's strongly connected components algorithm)) - $(LEADINGROW Interpolation) - $(TR $(TDNW $(MREF mir,interpolate)★) $(TD Interpolation algorithms)) + $(TR $(TDNW $(MREF mir,graph)) $(TD Basic routines to work with graphs)) + $(LEADINGROW Big Numbers (partial implementation)) + $(TR $(TDNW $(MREF mir,bignum, decimal)) $(TD Stack-allocated decimal type)) + $(TR $(TDNW $(MREF mir,bignum, fixed)) $(TD Stack-allocated fixed length unsigned integer type (like 256bit integers).)) + $(TR $(TDNW $(MREF mir,bignum, fp)) $(TD Stack-allocated fixed length software floating point type.)) + $(TR $(TDNW $(MREF mir,bignum, integer)) $(TD Stack-allocated integer type.)) + $(TR $(TDNW $(MREF mir,bignum, low_level_view)) $(TD Low-level universal number representation formats.)) $(LEADINGROW Combinatrorics) $(TR $(TDNW $(MREF mir,combinatorics)★) $(TD Combinations, combinations with repeats, cartesian power, permutations.)) $(LEADINGROW Interconnection with other languages) $(TR $(TDNW $(MREF mir,ndslice,connect,cpython)) $(TD Utilities for $(HTTPS docs.python.org/3/c-api/buffer.html, Python Buffer Protocol))) - $(LEADINGROW Accessories) - $(TR $(TDNW $(MREF mir,small_string)) Generic Small Strings) - $(TR $(TDNW $(MREF mir,array,allocation)) $(TD `std.array` reworked for Mir)) - $(TR $(TDNW $(MREF mir,range)) $(TD Ranges)) ) -Copyright: Copyright © 2016-, Ilya Yaroshenko. +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments Macros: TITLE=Mir Algorithm diff --git a/meson.build b/meson.build index a59d99f0..263170b8 100644 --- a/meson.build +++ b/meson.build @@ -1,109 +1,152 @@ -project('mir-algorithm', 'cpp', 'd', version : '3.4.0', license: 'BSL-1.0', +project('mir-algorithm', 'cpp', 'd', version : '3.7.0', license: 'Apache-2.0', default_options : ['cpp_std=c++1z']) +description = 'Mir Algorithm - Dlang Core Library' + +subprojects = ['mir-core'] + +has_cpp_headers = true + +sources_list = [ + 'mir/algebraic_alias/ion', + 'mir/algebraic_alias/json', + 'mir/algebraic_alias/transform', + 'mir/algorithm/iteration', + 'mir/algorithm/setops', + 'mir/annotated', + 'mir/appender', + 'mir/array/allocation', + 'mir/base64', + 'mir/bignum/decimal', + 'mir/bignum/fixed', + 'mir/bignum/fp', + 'mir/bignum/integer', + 'mir/bignum/internal/dec2float_table', + 'mir/bignum/internal/dec2float', + 'mir/bignum/internal/kernel', + 'mir/bignum/internal/phobos_kernel', + 'mir/bignum/internal/ryu/generic_128', + 'mir/bignum/internal/ryu/table', + 'mir/bignum/low_level_view', + 'mir/combinatorics/package', + 'mir/container/binaryheap', + 'mir/cpp_export/numeric', + 'mir/date', + 'mir/ediff', + 'mir/format_impl', + 'mir/format', + 'mir/graph/package', + 'mir/graph/tarjan', + 'mir/interpolate/constant', + 'mir/interpolate/extrapolate', + 'mir/interpolate/generic', + 'mir/interpolate/linear', + 'mir/interpolate/mod', + 'mir/interpolate/package', + 'mir/interpolate/polynomial', + 'mir/interpolate/spline', + 'mir/interpolate/utility', + 'mir/lob', + 'mir/math/func/expdigamma', + 'mir/math/func/hermite', + 'mir/math/func/normal', + 'mir/math/numeric', + 'mir/math/stat', + 'mir/math/sum', + 'mir/ndslice/allocation', + 'mir/ndslice/chunks', + 'mir/ndslice/concatenation', + 'mir/ndslice/connect/cpython', + 'mir/ndslice/dynamic', + 'mir/ndslice/field', + 'mir/ndslice/filling', + 'mir/ndslice/fuse', + 'mir/ndslice/internal', + 'mir/ndslice/iterator', + 'mir/ndslice/mutation', + 'mir/ndslice/ndfield', + 'mir/ndslice/package', + 'mir/ndslice/slice', + 'mir/ndslice/sorting', + 'mir/ndslice/topology', + 'mir/ndslice/traits', + 'mir/numeric', + 'mir/parse', + 'mir/polynomial', + 'mir/range', + 'mir/rc/array', + 'mir/rc/context', + 'mir/rc/package', + 'mir/rc/ptr', + 'mir/rc/slim_ptr', + 'mir/serde', + 'mir/series', + 'mir/small_array', + 'mir/small_string', + 'mir/string_map', + 'mir/test', + 'mir/timestamp', + 'mir/type_info', +] + +sources = [] +foreach s : sources_list + sources += 'source/' + s + '.d' +endforeach add_project_arguments([ - '-dip25', - '-dip1000', - '-dip1008', - ], language: 'd') - - -mir_algorithm_dir = include_directories('source/', 'include/') - -mir_core_dep = dependency('mir-core', fallback : ['mir-core', 'mir_core_dep']) - -required_deps = [mir_core_dep] - -mir_algorithm_src = [ - 'source/mir/algorithm/iteration.d', - 'source/mir/algorithm/setops.d', - 'source/mir/array/allocation.d', - 'source/mir/combinatorics/package.d', - 'source/mir/container/binaryheap.d', - 'source/mir/cpp_export/numeric.d', - 'source/mir/graph/package.d', - 'source/mir/graph/tarjan.d', - 'source/mir/interpolate/constant.d', - 'source/mir/interpolate/linear.d', - 'source/mir/interpolate/package.d', - 'source/mir/interpolate/pchip.d', - 'source/mir/interpolate/spline.d', - 'source/mir/interpolate/utility.d', - 'source/mir/math/func/expdigamma.d', - 'source/mir/math/numeric.d', - 'source/mir/math/sum.d', - 'source/mir/ndslice/allocation.d', - 'source/mir/ndslice/chunks.d', - 'source/mir/ndslice/concatenation.d', - 'source/mir/ndslice/connect/cpython.d', - 'source/mir/ndslice/dynamic.d', - 'source/mir/ndslice/field.d', - 'source/mir/ndslice/fuse.d', - 'source/mir/ndslice/internal.d', - 'source/mir/ndslice/iterator.d', - 'source/mir/ndslice/mutation.d', - 'source/mir/ndslice/ndfield.d', - 'source/mir/ndslice/package.d', - 'source/mir/ndslice/slice.d', - 'source/mir/ndslice/sorting.d', - 'source/mir/ndslice/topology.d', - 'source/mir/ndslice/traits.d', - 'source/mir/numeric.d', - 'source/mir/range.d', - 'source/mir/rc/array.d', - 'source/mir/rc/context.d', - 'source/mir/rc/package.d', - 'source/mir/rc/ptr.d', - 'source/mir/rc/slim_ptr.d', - 'source/mir/series.d', - 'source/mir/small_string.d', - 'source/mir/type_info.d', -] + '-preview=dip1008', + '-lowmem', +], language: 'd') + +required_deps = [] + +foreach p : subprojects + required_deps += dependency(p, fallback : [p, 'this_dep']) +endforeach + +directories = ['source'] + +if has_cpp_headers + directories += 'include' +endif + +directories = include_directories(directories) -mir_algorithm_lib = library(meson.project_name(), - mir_algorithm_src, - include_directories: mir_algorithm_dir, +this_lib = library(meson.project_name(), + sources, + include_directories: directories, install: true, version: meson.project_version(), dependencies: required_deps, ) -mir_algorithm_dep = declare_dependency( - link_with: [mir_algorithm_lib], - include_directories: mir_algorithm_dir, +this_dep = declare_dependency( + link_with: [this_lib], + include_directories: directories, dependencies: required_deps, ) -install_subdir('include/', - strip_directory :true, - install_dir: 'include/', -) +test_versions = ['mir_test', 'mir_bignum_test', 'mir_bignum_test_llv', 'mir_ndslice_test', 'mir_secure_memory'] + +if has_cpp_headers + install_subdir('include/', + strip_directory :true, + install_dir: 'include/', + ) +endif install_subdir('source/', strip_directory : true, install_dir: 'include/d/' + meson.project_name(), ) -import('pkgconfig').generate(mir_algorithm_lib, - description: 'Mir Algorithm - Dlang Core Library for Math and Finance.', +import('pkgconfig').generate(this_lib, + description: description, subdirs: 'd/' + meson.project_name(), ) -if get_option('with_test') +mir_algorithm_dep = this_dep +mir_algorithm_lib = this_lib - mir_algorithm_test_exe = executable(meson.project_name() + '-test', - mir_algorithm_src, - include_directories: mir_algorithm_dir, - d_unittest: true, - d_module_versions: ['mir_test'], - link_args: '-main', - # d_args: ['-d-debug'], - dependencies: required_deps, - ) - - test(meson.project_name() + '-test', mir_algorithm_test_exe) - - subdir('cpp_example') - -endif +test_subdirs = ['cpp_example'] diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index 27602cf6..00000000 --- a/meson_options.txt +++ /dev/null @@ -1 +0,0 @@ -option('with_test', type : 'boolean', value : false) diff --git a/source/mir/algebraic_alias/ion.d b/source/mir/algebraic_alias/ion.d new file mode 100644 index 00000000..c98e8971 --- /dev/null +++ b/source/mir/algebraic_alias/ion.d @@ -0,0 +1,175 @@ +/++ +$(H1 Mutable Ion value) + +This module contains a single alias definition and doesn't provide Ion serialization API. + +See_also: Ion library $(MIR_PACKAGE mir-ion) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki +Macros: ++/ +module mir.algebraic_alias.ion; + +import mir.algebraic: Algebraic, This; +/// +public import mir.annotated: Annotated; +/// +public import mir.lob: Clob, Blob; +/// +public import mir.string_map: StringMap; +/// +public import mir.timestamp: Timestamp; + + +/++ +Definition union for $(LREF IonAlgebraic). ++/ +union Ion_ +{ + /// + typeof(null) null_; + /// + bool boolean; + /// + long integer; + /// + double float_; + /// + immutable(char)[] string; + /// + Blob blob; + /// + Clob clob; + /// + Timestamp timestamp; + /// Self alias in array. + This[] array; + /// Self alias in $(MREF mir,string_map). + StringMap!This object; + /// Self alias in $(MREF mir,annotated). + Annotated!This annotated; +} + +/++ +Ion tagged algebraic alias. + +The example below shows only the basic features. Advanced API to work with algebraic types can be found at $(GMREF mir-core, mir,algebraic). +See also $(MREF mir,string_map) - ordered string-value associative array. ++/ +alias IonAlgebraic = Algebraic!Ion_; + +/// +@safe pure +version(mir_test) +unittest +{ + import mir.test: should; + import mir.ndslice.topology: map; + import mir.array.allocation: array; + + IonAlgebraic value; + + StringMap!IonAlgebraic object; + + // Default + assert(value.isNull); + assert(value.kind == IonAlgebraic.Kind.null_); + + // Boolean + value = object["bool"] = true; + assert(!value.isNull); + assert(value == true); + assert(value.kind == IonAlgebraic.Kind.boolean); + // access + assert(value.boolean == true); + assert(value.get!bool == true); + assert(value.get!"boolean" == true); + assert(value.get!(IonAlgebraic.Kind.boolean) == true); + // nothrow access + assert(value.trustedGet!bool == true); + assert(value.trustedGet!"boolean" == true); + assert(value.trustedGet!(IonAlgebraic.Kind.boolean) == true); + // checks + assert(!value._is!string); + assert(value._is!bool); + assert(value._is!"boolean"); + assert(value._is!(IonAlgebraic.Kind.boolean)); + + // Null + value = object["null"] = null; + assert(value.isNull); + assert(value == null); + assert(value.kind == IonAlgebraic.Kind.null_); + // access + assert(value.null_ == null); + assert(value.get!(typeof(null)) == null); + assert(value.get!(IonAlgebraic.Kind.null_) == null); + + // String + value = object["string"] = "s"; + assert(value.kind == IonAlgebraic.Kind.string); + assert(value == "s"); + // access + // Yep, `string` here is an alias to `get!(immutable(char)[])` method + assert(value.string == "s"); + // `string` here is an alias of type `immutable(char)[]` + assert(value.get!string == "s"); + assert(value.get!"string" == "s"); + // finally, `string` here is an enum meber + assert(value.get!(IonAlgebraic.Kind.string) == "s"); + + // Integer + value = object["integer"] = 4; + assert(value.kind == IonAlgebraic.Kind.integer); + assert(value == 4); + assert(value != 4.0); + assert(value.integer == 4); + + // Float + value = object["float"] = 3.0; + assert(value.kind == IonAlgebraic.Kind.float_); + assert(value != 3); + assert(value == 3.0); + assert(value.float_ == 3.0); + + // Array + IonAlgebraic[] arr = [0, 1, 2, 3, 4].map!IonAlgebraic.array; + + value = object["array"] = arr; + assert(value.kind == IonAlgebraic.Kind.array); + assert(value == arr); + assert(value == [0, 1, 2, 3, 4].map!IonAlgebraic.array);// by value + assert(value.array[3] == 3); + + // Object + assert(object.keys == ["bool", "null", "string", "integer", "float", "array"]); + object.values[0] = "false"; + assert(object["bool"] == "false"); // it is a string now + object.remove("bool"); // remove the member + + value = object["array"] = object; + assert(value.kind == IonAlgebraic.Kind.object); + assert(value.object.keys is object.keys); + + IonAlgebraic[string] aa = object.toAA; + object = aa.StringMap!IonAlgebraic; + + IonAlgebraic fromAA = ["a" : IonAlgebraic(3), "b" : IonAlgebraic("b")]; + assert(fromAA.object["a"] == 3); + assert(fromAA.object["b"] == "b"); + + // object foreach iteration + long sum; + foreach (ref key, ref val; fromAA.object) + if (key == "a") + sum += val.get!long; + sum.should == 3; + + // annotations + auto annotated = Annotated!IonAlgebraic(["birthday"], Timestamp("2001-01-01")); + value = annotated; + assert(value == annotated); + value = annotated.IonAlgebraic; + assert(value == annotated); +} diff --git a/source/mir/algebraic_alias/json.d b/source/mir/algebraic_alias/json.d new file mode 100644 index 00000000..64daf39f --- /dev/null +++ b/source/mir/algebraic_alias/json.d @@ -0,0 +1,152 @@ +/++ +$(H1 Mutable JSON value) + +This module contains a single alias definition and doesn't provide JSON serialization API. + +See_also: JSON libraries $(MIR_PACKAGE mir-ion) and $(MIR_PACKAGE asdf); + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki +Macros: ++/ +module mir.algebraic_alias.json; + +import mir.algebraic: Algebraic, This; +/// +public import mir.string_map: StringMap; + +/++ +Definition union for $(LREF JsonAlgebraic). ++/ +union Json_ +{ + /// + typeof(null) null_; + /// + bool boolean; + /// + long integer; + /// + double float_; + /// + immutable(char)[] string; + /// Self alias in array. + This[] array; + /// Self alias in $(MREF mir,string_map). + StringMap!This object; +} + +/++ +JSON tagged algebraic alias. + +The example below shows only the basic features. Advanced API to work with algebraic types can be found at $(GMREF mir-core, mir,algebraic). +See also $(MREF mir,string_map) - ordered string-value associative array. ++/ +alias JsonAlgebraic = Algebraic!Json_; + +/// +version(mir_test) +@safe pure +unittest +{ + import mir.test: should; + import mir.ndslice.topology: map; + import mir.array.allocation: array; + + JsonAlgebraic value; + + StringMap!JsonAlgebraic object; + + // Default + assert(value.isNull); + assert(value.kind == JsonAlgebraic.Kind.null_); + + // Boolean + value = object["bool"] = true; + assert(!value.isNull); + assert(value == true); + assert(value.kind == JsonAlgebraic.Kind.boolean); + // access + assert(value.boolean == true); + assert(value.get!bool == true); + assert(value.get!"boolean" == true); + assert(value.get!(JsonAlgebraic.Kind.boolean) == true); + // nothrow access + assert(value.trustedGet!bool == true); + assert(value.trustedGet!"boolean" == true); + assert(value.trustedGet!(JsonAlgebraic.Kind.boolean) == true); + // checks + assert(!value._is!string); + assert(value._is!bool); + assert(value._is!"boolean"); + assert(value._is!(JsonAlgebraic.Kind.boolean)); + + // Null + value = object["null"] = null; + assert(value.isNull); + assert(value == null); + assert(value.kind == JsonAlgebraic.Kind.null_); + // access + assert(value.null_ == null); + assert(value.get!(typeof(null)) == null); + assert(value.get!(JsonAlgebraic.Kind.null_) == null); + + // String + value = object["string"] = "s"; + assert(value.kind == JsonAlgebraic.Kind.string); + assert(value == "s"); + // access + // Yep, `string` here is an alias to `get!(immutable(char)[])` method + assert(value.string == "s"); + // `string` here is an alias of type `immutable(char)[]` + assert(value.get!string == "s"); + assert(value.get!"string" == "s"); + // finally, `string` here is an enum meber + assert(value.get!(JsonAlgebraic.Kind.string) == "s"); + + // Integer + value = object["integer"] = 4; + assert(value.kind == JsonAlgebraic.Kind.integer); + assert(value == 4); + assert(value != 4.0); + assert(value.integer == 4); + + // Float + value = object["float"] = 3.0; + assert(value.kind == JsonAlgebraic.Kind.float_); + assert(value != 3); + assert(value == 3.0); + assert(value.float_ == 3.0); + + // Array + JsonAlgebraic[] arr = [0, 1, 2, 3, 4].map!JsonAlgebraic.array; + + value = object["array"] = arr; + assert(value.kind == JsonAlgebraic.Kind.array); + assert(value == arr); + assert(value.array[3] == 3); + + // Object + assert(object.keys == ["bool", "null", "string", "integer", "float", "array"]); + object.values[0] = "false"; + assert(object["bool"] == "false"); // it is a string now + object.remove("bool"); // remove the member + + value = object["array"] = object; + assert(value.kind == JsonAlgebraic.Kind.object); + assert(value.object.keys is object.keys); + + JsonAlgebraic[string] aa = object.toAA; + object = aa.StringMap!JsonAlgebraic; + + JsonAlgebraic fromAA = ["a" : JsonAlgebraic(3), "b" : JsonAlgebraic("b")]; + assert(fromAA.object["a"] == 3); + assert(fromAA.object["b"] == "b"); + + // object foreach iteration + long sum; + foreach (ref key, ref val; fromAA.object) + if (key == "a") + sum += val.get!long; + sum.should == 3; +} diff --git a/source/mir/algebraic_alias/transform.d b/source/mir/algebraic_alias/transform.d new file mode 100644 index 00000000..80c8f487 --- /dev/null +++ b/source/mir/algebraic_alias/transform.d @@ -0,0 +1,156 @@ +/++ +$(H1 Transformation utilities for JSON-like values) + +See_also: JSON libraries $(MIR_PACKAGE mir-ion) and $(MIR_PACKAGE asdf); + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki +Macros: ++/ +module mir.algebraic_alias.transform; + +import mir.algebraic: Algebraic, tryVisit, visit, optionalVisit; +import mir.functional: naryFun; +private alias AliasSeq(T...) = T; + +/++ +Transforms algebraics leafs recursively in place, +ensuring that all leaf types are handled by the visiting functions. + +Recursion is done for `This[]`, `StringMap!This`, `This[string]`, and `Annotated!This` types. ++/ +alias transformLeafs(visitors...) = transformLeafsImpl!(visit, naryFun!visitors); + +/// +version(mir_test) +unittest +{ + import mir.format: text; + import mir.algebraic_alias.json; + JsonAlgebraic value = ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, null.JsonAlgebraic].JsonAlgebraic]; + + // converts all leavs to a text form + value.transformLeafs!text; + assert(value == ["key" : ["str".JsonAlgebraic, "2.32".JsonAlgebraic, "null".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic); + + value = ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, true.JsonAlgebraic].JsonAlgebraic].JsonAlgebraic; + + /// converts only bool values + value.transformLeafs!( + (bool b) => b.text, + v => v, // other values are copied as is + ); + + assert(value == ["key" : ["str".JsonAlgebraic, 2.32.JsonAlgebraic, "true".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic); +} + +/++ +Behaves as $(LREF transformLeafs) but doesn't enforce at compile time that all types can be handled by the visiting functions. + +Throws: Exception if `naryFun!visitors` can't be called with provided arguments ++/ +alias tryTransformLeafs(visitors...) = transformLeafsImpl!(tryVisit, naryFun!visitors); + +/// +version(mir_test) +unittest +{ + import mir.format: text; + import mir.algebraic_alias.json; + JsonAlgebraic value = ["key" : [true.JsonAlgebraic, 100.JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic]; + + // converts long and double numbers to a text form, bool values is converted to `long` + value.tryTransformLeafs!((double v) => v.text, (long v) => v.text); + assert(value == ["key" : ["1".JsonAlgebraic, "100".JsonAlgebraic, "2.32".JsonAlgebraic].JsonAlgebraic].JsonAlgebraic); +} + +/++ +Behaves as $(LREF transformLeafs) but doesn't enforce at compile time that all types can be handled by the visiting functions. + +The function ignores leafs that can't be handled by the visiting functions. ++/ +alias optionalTransformLeafs(visitors...) = transformLeafsImpl!(optionalVisit, naryFun!visitors); + +/// +version(mir_test) +unittest +{ + import mir.format: text; + import mir.algebraic_alias.json; + JsonAlgebraic value = ["key" : [null.JsonAlgebraic, true.JsonAlgebraic, 100.JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic]; + + // converts long numbers to a text form, ignores other types + value.optionalTransformLeafs!( + (long v) => v.text, + (bool b) => b, // needs special overload for bool to get rid of implicit converion to long/double + ); + assert(value == ["key" : [null.JsonAlgebraic, true.JsonAlgebraic, "100".JsonAlgebraic, 2.32.JsonAlgebraic].JsonAlgebraic].JsonAlgebraic); +} + +/// +template transformLeafsImpl(alias handler, alias visitor) +{ + /// + ref Algebraic!Types transformLeafsImpl(Types...)(ref return Algebraic!Types value) + { + import core.lifetime: move; + import mir.algebraic: visit; + import mir.annotated: Annotated; + import mir.string_map: StringMap; + alias T = Algebraic!Types; + static if (is(T.AllowedTypes[0] == typeof(null))) + { + enum nullCompiles = __traits(compiles, value = visitor(null)); + static if (nullCompiles || __traits(isSame, handler, visit)) + { + alias nullHandler = (typeof(null)) { + static if (nullCompiles) + value = visitor(null); + else + assert(0, "Null " ~ T.stringof); + }; + } + else + { + alias nullHandler = AliasSeq!(); + } + } + else + { + alias nullHandler = AliasSeq!(); + } + handler!( + (T[] v) { + foreach (ref e; v) + transformLeafsImpl(e); + }, + (StringMap!T v) { + foreach (ref e; v.values) + transformLeafsImpl(e); + }, + (T[string] v) { + foreach (key, ref e; v) + transformLeafsImpl(e); + }, + (Annotated!T v) { + transformLeafsImpl(v.value); + }, + nullHandler, + (ref v) { // auto for typeof(null) support + static if (__traits(compiles, value = visitor(move(v)))) + value = visitor(move(v)); + else + value = visitor(v); + } + )(value); + return value; + } + + /// ditto + Algebraic!Types transformLeafsImpl(Types...)(Algebraic!Types value) + { + import core.lifetime: move; + transformLeafsImpl(value); + return move(value); + } +} diff --git a/source/mir/algorithm/iteration.d b/source/mir/algorithm/iteration.d index 63af62b6..a0f5b125 100644 --- a/source/mir/algorithm/iteration.d +++ b/source/mir/algorithm/iteration.d @@ -9,35 +9,44 @@ $(T2 all, Checks if all elements satisfy to a predicate.) $(T2 any, Checks if at least one element satisfy to a predicate.) $(T2 cmp, Compares two slices.) $(T2 count, Counts elements in a slices according to a predicate.) -$(T2 each, Iterates all elements.) +$(T2 each, Iterates elements.) $(T2 eachLower, Iterates lower triangle of matrix.) +$(T2 eachOnBorder, Iterates elementes on tensors borders and corners.) $(T2 eachUploPair, Iterates upper and lower pairs of elements in square matrix.) $(T2 eachUpper, Iterates upper triangle of matrix.) $(T2 equal, Compares two slices for equality.) +$(T2 filter, Filters elements in a range or an ndslice.) $(T2 find, Finds backward index.) $(T2 findIndex, Finds index.) +$(T2 fold, Accumulates all elements (different parameter order than `reduce`).) $(T2 isSymmetric, Checks if the matrix is symmetric.) +$(T2 maxElement, Returns the maximum.) $(T2 maxIndex, Finds index of the maximum.) $(T2 maxPos, Finds backward index of the maximum.) +$(T2 minElement, Returns the minimum.) $(T2 minIndex, Finds index of the minimum.) -$(T2 minmaxIndex, Finds indexes of the minimum and the maximum.) -$(T2 minmaxPos, Finds backward indexes of the minimum and the maximum.) +$(T2 minmaxIndex, Finds indices of the minimum and the maximum.) +$(T2 minmaxPos, Finds backward indices of the minimum and the maximum.) $(T2 minPos, Finds backward index of the minimum.) $(T2 nBitsToCount, Сount bits until set bit count is reached.) $(T2 reduce, Accumulates all elements.) -$(T2 uniq, Iterates over the unique elements in a range, which is assumed sorted.) +$(T2 Chequer, Chequer color selector to work with $(LREF each) .) +$(T2 uniq, Iterates over the unique elements in a range or an ndslice, which is assumed sorted.) ) +Transform function is represented by $(NDSLICEREF topology, map). + All operators are suitable to change slices using `ref` argument qualification in a function declaration. Note, that string lambdas in Mir are `auto ref` functions. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016-2018, Ilya Yaroshenko, 2018-, Mir community -Authors: Ilya Yaroshenko, John Michael Hall, Andrei Alexandrescu (original Phobos code) +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki, John Michael Hall, Andrei Alexandrescu (original Phobos code) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments -Copyright: Andrei Alexandrescu 2008-. Ilya Yaroshenko 2017- -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Authors: , Ilya Yaroshenko (Mir & BetterC rework). +Authors: , Ilia Ki (Mir & BetterC rework). Source: $(PHOBOSSRC std/algorithm/_iteration.d) Macros: NDSLICEREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -47,7 +56,7 @@ module mir.algorithm.iteration; import mir.functional: naryFun; import mir.internal.utility; -import mir.math.common: optmath; +import mir.math.common: fmamath; import mir.ndslice.field: BitField; import mir.ndslice.internal; import mir.ndslice.iterator: FieldIterator, RetroIterator; @@ -55,11 +64,46 @@ import mir.ndslice.slice; import mir.primitives; import mir.qualifier; import std.meta; -import std.range.primitives: isInputRange, isBidirectionalRange, isInfinite, isForwardRange, ElementType; import std.traits; -@optmath: +/++ +Chequer color selector to work with $(LREF each) ++/ +enum Chequer : bool +{ + /// Main diagonal color + black, + /// First sub-diagonal color + red, +} + +/// +version(mir_test) unittest +{ + import mir.ndslice.allocation: slice; + auto s = [5, 4].slice!int; + + Chequer.black.each!"a = 1"(s); + assert(s == [ + [1, 0, 1, 0], + [0, 1, 0, 1], + [1, 0, 1, 0], + [0, 1, 0, 1], + [1, 0, 1, 0], + ]); + + Chequer.red.each!((ref b) => b = 2)(s); + assert(s == [ + [1, 2, 1, 2], + [2, 1, 2, 1], + [1, 2, 1, 2], + [2, 1, 2, 1], + [1, 2, 1, 2], + ]); + +} +@fmamath: /+ Bitslice representation for accelerated bitwise algorithm. @@ -77,7 +121,7 @@ private struct BitSliceAccelerator(Field, I = typeof(Field.init[size_t.init])) import mir.ndslice.field: BitField; /// - alias U = typeof(I + 1u); + alias U = typeof(I.init + 1u); /// body bits chunks static if (isIterator!Field) Slice!Field bodyChunks; @@ -88,7 +132,7 @@ private struct BitSliceAccelerator(Field, I = typeof(Field.init[size_t.init])) /// tail length int tailLength; -@optmath: +@fmamath: this(Slice!(FieldIterator!(BitField!(Field, I))) slice) { @@ -416,6 +460,7 @@ scope const: sizediff_t nBitsToCount(size_t count) { + pragma(inline, false); size_t ret; if (count == 0) return count; @@ -503,19 +548,36 @@ scope const: Сount bits until set bit count is reached. Works with ndslices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). Returns: bit count if set bit count is reached or `-1` otherwise. +/ -sizediff_t nBitsToCount(Field, I)(scope Slice!(FieldIterator!(BitField!(Field, I))) bitSlice, size_t count) +sizediff_t nBitsToCount(Field, I)(Slice!(FieldIterator!(BitField!(Field, I))) bitSlice, size_t count) { return BitSliceAccelerator!(Field, I)(bitSlice).nBitsToCount(count); } ///ditto -sizediff_t nBitsToCount(Field, I)(scope Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))) bitSlice, size_t count) +sizediff_t nBitsToCount(Field, I)(Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))) bitSlice, size_t count) { import mir.ndslice.topology: retro; return BitSliceAccelerator!(Field, I)(bitSlice.retro).retroNBitsToCount(count); } +/++ +Сount bits starting from the end until set bit count is reached. Works with ndslices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). +Returns: bit count if set bit count is reached or `-1` otherwise. ++/ +sizediff_t retroNBitsToCount(Field, I)(Slice!(FieldIterator!(BitField!(Field, I))) bitSlice, size_t count) +{ + return BitSliceAccelerator!(Field, I)(bitSlice).retroNBitsToCount(count); +} + +///ditto +sizediff_t retroNBitsToCount(Field, I)(Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))) bitSlice, size_t count) +{ + import mir.ndslice.topology: retro; + return BitSliceAccelerator!(Field, I)(bitSlice.retro).nBitsToCount(count); +} + /// +version(mir_test) pure unittest { import mir.ndslice.allocation: bitSlice; @@ -534,10 +596,9 @@ private void checkShapesMatch( string fun = __FUNCTION__, string pfun = __PRETTY_FUNCTION__, Slices...) - (scope ref const Slices slices) + (Slices slices) if (Slices.length > 1) { - enum msg = "all arguments must be slices" ~ tailErrorMessage!(fun, pfun); enum msgShape = "all slices must have the same shape" ~ tailErrorMessage!(fun, pfun); enum N = slices[0].shape.length; foreach (i, Slice; Slices) @@ -551,33 +612,23 @@ private void checkShapesMatch( { import mir.ndslice.fuse: fuseShape; static assert(slices[i].fuseShape.length >= N); - assert(slices[i].fuseShape[0 .. N] == slices[0].shape, msgShape); + assert(cast(size_t[N])slices[i].fuseShape[0 .. N] == slices[0].shape, msgShape); } } } -template frontOf(size_t N) -{ - static if (N == 0) - enum frontOf = ""; - else - { - enum i = N - 1; - enum frontOf = frontOf!i ~ "slices[" ~ i.stringof ~ "].front, "; - } -} package(mir) template allFlattened(args...) { static if (args.length) { alias arg = args[0]; - @optmath @property ls()() + @fmamath @property allFlattenedMod()() { import mir.ndslice.topology: flattened; return flattened(arg); } - alias allFlattened = AliasSeq!(ls, allFlattened!(args[1..$])); + alias allFlattened = AliasSeq!(allFlattenedMod, allFlattened!(args[1..$])); } else alias allFlattened = AliasSeq!(); @@ -587,11 +638,19 @@ private template areAllContiguousSlices(Slices...) { import mir.ndslice.traits: isContiguousSlice; static if (allSatisfy!(isContiguousSlice, Slices)) - enum areAllContiguousSlices = Slices[0].N > 1; + enum areAllContiguousSlices = Slices[0].N > 1 && areAllContiguousSlicesImpl!(Slices[0].N, Slices[1 .. $]); else enum areAllContiguousSlices = false; } +private template areAllContiguousSlicesImpl(size_t N, Slices...) +{ + static if (Slices.length == 0) + enum areAllContiguousSlicesImpl = true; + else + enum areAllContiguousSlicesImpl = Slices[0].N == N && areAllContiguousSlicesImpl!(N, Slices[1 .. $]); +} + version(LDC) {} else version(GNU) {} else version (Windows) {} @@ -621,11 +680,11 @@ version(Mir_disable_inlining_in_reduce) private template nonInlinedNaryFun(alias fun) { - import mir.math.common : optmath; + import mir.math.common : fmamath; static if (is(typeof(fun) : string)) { /// Specialization for string lambdas - @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) + @fmamath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) if (args.length <= 26) { pragma(inline,false); @@ -635,7 +694,7 @@ version(Mir_disable_inlining_in_reduce) } else static if (is(typeof(fun.opCall) == function)) { - @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) + @fmamath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) if (is(typeof(fun.opCall(args)))) { pragma(inline,false); @@ -644,7 +703,7 @@ version(Mir_disable_inlining_in_reduce) } else { - @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) + @fmamath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) if (is(typeof(fun(args)))) { pragma(inline,false); @@ -658,14 +717,14 @@ else private enum Mir_disable_inlining_in_reduce = false; } -S reduceImpl(alias fun, S, Slices...)(S seed, scope Slices slices) +S reduceImpl(alias fun, S, Slices...)(S seed, Slices slices) { do { static if (DimensionCount!(Slices[0]) == 1) - seed = mixin("fun(seed, " ~ frontOf!(Slices.length) ~ ")"); + mixin(`seed = fun(seed,` ~ frontOf!(Slices.length) ~ `);`); else - seed = mixin(".reduceImpl!fun(seed," ~ frontOf!(Slices.length) ~ ")"); + mixin(`seed = .reduceImpl!fun(seed,` ~ frontOf!(Slices.length) ~ `);`); foreach_reverse(ref slice; slices) slice.popFront; } @@ -705,7 +764,7 @@ template reduce(alias fun) Returns: the accumulated `result` +/ - @optmath auto reduce(S, Slices...)(S seed, scope Slices slices) + @fmamath auto reduce(S, Slices...)(S seed, Slices slices) if (Slices.length) { static if (Slices.length > 1) @@ -728,7 +787,7 @@ template reduce(alias fun) } else version(Mir_disable_inlining_in_reduce) //As above, but with inlining disabled. - @optmath auto reduce(S, Slices...)(S seed, scope Slices slices) + @fmamath auto reduce(S, Slices...)(S seed, Slices slices) if (Slices.length) { static if (Slices.length > 1) @@ -809,9 +868,9 @@ version(mir_test) unittest import std.numeric : dotProduct; import mir.ndslice.allocation : slice; import mir.ndslice.topology : as, iota, zip, universal; - import mir.math.common : optmath; + import mir.math.common : fmamath; - static @optmath T fmuladd(T, Z)(const T a, Z z) + static @fmamath T fmuladd(T, Z)(const T a, Z z) { return a + z.a * z.b; } @@ -839,9 +898,9 @@ unittest { import mir.ndslice.allocation : slice; import mir.ndslice.topology : as, iota; - import mir.math.common : optmath; + import mir.math.common : fmamath; - static @optmath T fun(T)(const T a, ref T b) + static @fmamath T fun(T)(const T a, ref T b) { return a + b++; } @@ -913,59 +972,202 @@ version(mir_test) unittest assert(a == 7); } -void eachImpl(alias fun, Slices...)(scope Slices slices) +void eachImpl(alias fun, Slices...)(Slices slices) { foreach(ref slice; slices) assert(!slice.empty); do { static if (DimensionCount!(Slices[0]) == 1) - mixin("fun(" ~ frontOf!(Slices.length) ~ ");"); + mixin(`fun(`~ frontOf!(Slices.length) ~ `);`); else - mixin(".eachImpl!fun(" ~ frontOf!(Slices.length) ~ ");"); + .eachImpl!fun(frontOf2!slices); foreach_reverse(i; Iota!(Slices.length)) slices[i].popFront; } while(!slices[0].empty); } +void chequerEachImpl(alias fun, Slices...)(Chequer color, Slices slices) +{ + foreach(ref slice; slices) + assert(!slice.empty); + static if (DimensionCount!(Slices[0]) == 1) + { + if (color) + { + foreach_reverse(i; Iota!(Slices.length)) + slices[i].popFront; + if (slices[0].empty) + return; + } + eachImpl!fun(strideOf!slices); + } + else + { + do + { + mixin(`.chequerEachImpl!fun(color,` ~ frontOf!(Slices.length) ~ `);`); + color = cast(Chequer)!color; + foreach_reverse(i; Iota!(Slices.length)) + slices[i].popFront; + } + while(!slices[0].empty); + } +} + /++ The call `each!(fun)(slice1, ..., sliceN)` evaluates `fun` for each set of elements `x1, ..., xN` in -`slice1, ..., sliceN` respectively. +the borders of `slice1, ..., sliceN` respectively. `each` allows to iterate multiple slices in the lockstep. + Params: fun = A function. Note: $(NDSLICEREF dynamic, transposed) and $(NDSLICEREF topology, pack) can be used to specify dimensions. -See_Also: - This is functionally similar to $(LREF reduce) but has not seed. +/ -template each(alias fun) +template eachOnBorder(alias fun) { import mir.functional: naryFun; static if (__traits(isSame, naryFun!fun, fun)) /++ Params: - slices = One or more slices, ranges, and arrays. + slices = One or more slices. +/ - @optmath auto each(Slices...)(scope Slices slices) - if (Slices.length) + @fmamath void eachOnBorder(Slices...)(Slices slices) + if (allSatisfy!(isSlice, Slices)) { + import mir.ndslice.traits: isContiguousSlice; static if (Slices.length > 1) slices.checkShapesMatch; - static if (areAllContiguousSlices!Slices) + if (!slices[0].anyEmpty) { - import mir.ndslice.topology: flattened; - .each!fun(allFlattened!(allLightScope!slices)); + alias N = DimensionCount!(Slices[0]); + static if (N == 1) + { + mixin(`fun(`~ frontOf!(Slices.length) ~ `);`); + if (slices[0].length > 1) + fun(backOf!slices); + } + else + static if (anySatisfy!(isContiguousSlice, Slices)) + { + import mir.ndslice.topology: canonical; + template f(size_t i) + { + static if (isContiguousSlice!(Slices[i])) + auto f () { return canonical(slices[i]); } + else + alias f = slices[i]; + } + eachOnBorder(staticMap!(f, Iota!(Slices.length))); + } + else + { + foreach (dimension; Iota!N) + { + eachImpl!fun(frontOfD!(dimension, slices)); + foreach_reverse(ref slice; slices) + slice.popFront!dimension; + if (slices[0].empty!dimension) + return; + eachImpl!fun(backOfD!(dimension, slices)); + foreach_reverse(ref slice; slices) + slice.popBack!dimension; + if (slices[0].empty!dimension) + return; + } + } } - else + } + else + alias eachOnBorder = .eachOnBorder!(naryFun!fun); +} + +/// +@safe pure nothrow +version(mir_test) unittest +{ + import mir.ndslice.allocation : slice; + import mir.ndslice.topology : repeat, iota; + + auto sl = [3, 4].iota.slice; + auto zeros = repeat(0, [3, 4]); + + sl.eachOnBorder!"a = b"(zeros); + + assert(sl == + [[0, 0, 0 ,0], + [0, 5, 6, 0], + [0, 0, 0 ,0]]); + + sl.eachOnBorder!"a = 1"; + sl[0].eachOnBorder!"a = 2"; + + assert(sl == + [[2, 1, 1, 2], + [1, 5, 6, 1], + [1, 1, 1 ,1]]); +} + +/++ +The call `each!(fun)(slice1, ..., sliceN)` +evaluates `fun` for each set of elements `x1, ..., xN` in +`slice1, ..., sliceN` respectively. + +`each` allows to iterate multiple slices in the lockstep. +Params: + fun = A function. +Note: + $(NDSLICEREF dynamic, transposed) and + $(NDSLICEREF topology, pack) can be used to specify dimensions. +See_Also: + This is functionally similar to $(LREF reduce) but has not seed. ++/ +template each(alias fun) +{ + import mir.functional: naryFun; + static if (__traits(isSame, naryFun!fun, fun)) + { + /++ + Params: + slices = One or more slices, ranges, and arrays. + +/ + @fmamath auto each(Slices...)(Slices slices) + if (Slices.length && !is(Slices[0] : Chequer)) + { + static if (Slices.length > 1) + slices.checkShapesMatch; + static if (areAllContiguousSlices!Slices) + { + import mir.ndslice.topology: flattened; + .each!fun(allFlattened!(allLightScope!slices)); + } + else + { + if (slices[0].anyEmpty) + return; + eachImpl!fun(allLightScope!slices); + } + } + + /++ + Iterates elements of selected $(LREF Chequer) color. + Params: + color = $(LREF Chequer). + slices = One or more slices. + +/ + @fmamath auto each(Slices...)(Chequer color, Slices slices) + if (Slices.length && allSatisfy!(isSlice, Slices)) { + static if (Slices.length > 1) + slices.checkShapesMatch; if (slices[0].anyEmpty) return; - eachImpl!fun(allLightScope!slices); + chequerEachImpl!fun(color, allLightScope!slices); } } else @@ -1071,12 +1273,12 @@ template eachUploPair(alias fun, bool includeDiagonal = false) Params: matrix = Square matrix. +/ - auto eachUploPair(Iterator, SliceKind kind)(scope Slice!(Iterator, 2, kind) matrix) + auto eachUploPair(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix) in { assert(matrix.length!0 == matrix.length!1, "matrix must be square."); } - body + do { static if (kind == Contiguous) { @@ -1262,6 +1464,7 @@ template isSymmetric(alias fun = "a == b") version(mir_test) unittest { + import mir.ndslice.slice: sliced; import mir.ndslice.topology: iota; assert(iota(2, 2).isSymmetric == false); @@ -1362,23 +1565,24 @@ template minmaxPos(alias pred = "a < b") Returns: 2 subslices with minimal and maximal `first` elements. +/ - @optmath Slice!(Iterator, N, kind == Contiguous && N > 1 ? Canonical : kind)[2] + @fmamath Slice!(Iterator, N, kind == Contiguous && N > 1 ? Canonical : kind)[2] minmaxPos(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) { typeof(return) pret; if (!slice.anyEmpty) { size_t[2][N] ret; - auto it = slice._iterator; - Iterator[2] iterator = [it, it]; - minmaxPosImpl!(pred, Iterator, N, kind)(ret, iterator, lightScope(slice)); + auto scopeSlice = lightScope(slice); + auto it = scopeSlice._iterator; + LightScopeOf!Iterator[2] iterator = [it, it]; + minmaxPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); foreach (i; Iota!N) { pret[0]._lengths[i] = ret[i][0]; pret[1]._lengths[i] = ret[i][1]; } - pret[0]._iterator = iterator[0]; - pret[1]._iterator = iterator[1]; + pret[0]._iterator = slice._iterator + (iterator[0] - scopeSlice._iterator); + pret[1]._iterator = slice._iterator + (iterator[1] - scopeSlice._iterator); } auto strides = slice.strides; foreach(i; Iota!(0, pret[0].S)) @@ -1396,6 +1600,7 @@ template minmaxPos(alias pred = "a < b") version(mir_test) unittest { + import mir.ndslice.slice: sliced; auto s = [ 2, 6, 4, -3, 0, -4, -3, 3, @@ -1414,8 +1619,8 @@ unittest } /++ -Finds a backward indexes such that -`slice[indexes[0]]` is minimal and `slice[indexes[1]]` is maximal elements in the slice. +Finds a backward indices such that +`slice[indices[0]]` is minimal and `slice[indices[1]]` is maximal elements in the slice. Params: pred = A predicate. @@ -1436,7 +1641,7 @@ template minmaxIndex(alias pred = "a < b") Returns: Subslice with minimal (maximal) `first` element. +/ - @optmath size_t[N][2] minmaxIndex(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + @fmamath size_t[N][2] minmaxIndex(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) { typeof(return) pret = size_t.max; if (!slice.anyEmpty) @@ -1447,9 +1652,10 @@ template minmaxIndex(alias pred = "a < b") { ret[i][1] = ret[i][0] = shape[i]; } - auto it = slice._iterator; - Iterator[2] iterator = [it, it]; - minmaxPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, lightScope(slice)); + auto scopeSlice = lightScope(slice); + auto it = scopeSlice._iterator; + LightScopeOf!Iterator[2] iterator = [it, it]; + minmaxPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); foreach (i; Iota!N) { pret[0][i] = slice._lengths[i] - ret[i][0]; @@ -1466,17 +1672,18 @@ template minmaxIndex(alias pred = "a < b") version(mir_test) unittest { + import mir.ndslice.slice: sliced; auto s = [ 2, 6, 4, -3, 0, -4, -3, 3, -3, -2, 7, 8, ].sliced(3, 4); - auto indexes = s.minmaxIndex; + auto indices = s.minmaxIndex; - assert(indexes == [[1, 1], [2, 3]]); - assert(s[indexes[0]] == -4); - assert(s[indexes[1]] == 8); + assert(indices == [[1, 1], [2, 3]]); + assert(s[indices[0]] == -4); + assert(s[indices[1]] == 8); } /++ @@ -1503,13 +1710,15 @@ template minPos(alias pred = "a < b") Multidimensional backward index such that element is minimal(maximal). Backward index equals zeros, if slice is empty. +/ - @optmath Slice!(Iterator, N, kind == Contiguous && N > 1 ? Canonical : kind) + @fmamath Slice!(Iterator, N, kind == Contiguous && N > 1 ? Canonical : kind) minPos(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) { - typeof(return) ret = { _iterator : slice._iterator }; + typeof(return) ret; + auto iterator = slice.lightScope._iterator; if (!slice.anyEmpty) { - minPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret._lengths, ret._iterator, lightScope(slice)); + minPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret._lengths, iterator, lightScope(slice)); + ret._iterator = slice._iterator + (iterator - slice.lightScope._iterator); } auto strides = slice.strides; foreach(i; Iota!(0, ret.S)) @@ -1533,6 +1742,7 @@ template maxPos(alias pred = "a < b") version(mir_test) unittest { + import mir.ndslice.slice: sliced; auto s = [ 2, 6, 4, -3, 0, -4, -3, 3, @@ -1575,13 +1785,15 @@ template minIndex(alias pred = "a < b") Multidimensional index such that element is minimal(maximal). Index elements equal to `size_t.max`, if slice is empty. +/ - @optmath size_t[N] minIndex(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + @fmamath size_t[N] minIndex(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) { size_t[N] ret = size_t.max; if (!slice.anyEmpty) { ret = slice.shape; - minPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, slice._iterator, lightScope(slice)); + auto scopeSlice = lightScope(slice); + auto iterator = scopeSlice._iterator; + minPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); foreach (i; Iota!N) ret[i] = slice._lengths[i] - ret[i]; } @@ -1602,6 +1814,7 @@ template maxIndex(alias pred = "a < b") version(mir_test) unittest { + import mir.ndslice.slice: sliced; auto s = [ 2, 6, 4, -3, 0, -4, -3, 3, @@ -1623,6 +1836,7 @@ unittest version(mir_test) unittest { + import mir.ndslice.slice: sliced; auto s = [ -8, 6, 4, -3, 0, -4, -3, 3, @@ -1638,6 +1852,7 @@ unittest version(mir_test) unittest { + import mir.ndslice.slice: sliced; auto s = [ 0, 1, 2, 3, 4, 5, 6, 7, @@ -1678,7 +1893,7 @@ bool findImpl(alias fun, size_t N, Slices...)(scope ref size_t[N] backwardIndex, { static if (DimensionCount!(Slices[0]) == 1) { - if (mixin("fun(" ~ frontOf!(Slices.length) ~ ")")) + if (mixin(`fun(`~ frontOf!(Slices.length) ~ `)`)) { backwardIndex[0] = slices[0].length; return true; @@ -1686,7 +1901,7 @@ bool findImpl(alias fun, size_t N, Slices...)(scope ref size_t[N] backwardIndex, } else { - if (mixin("findImpl!fun(backwardIndex[1 .. $], " ~ frontOf!(Slices.length) ~ ")")) + if (mixin(`findImpl!fun(backwardIndex[1 .. $],` ~ frontOf!(Slices.length) ~ `)`)) { backwardIndex[0] = slices[0].length; return true; @@ -1722,11 +1937,11 @@ template findIndex(alias pred) slices = One or more slices. Returns: Multidimensional index such that the predicate is true. - Index equals `size_t.max`, if the predicate evaluates `false` for all indexes. + Index equals `size_t.max`, if the predicate evaluates `false` for all indices. Constraints: All slices must have the same shape. +/ - @optmath Select!(DimensionCount!(Slices[0]) > 1, size_t[DimensionCount!(Slices[0])], size_t) findIndex(Slices...)(Slices slices) + @fmamath Select!(DimensionCount!(Slices[0]) > 1, size_t[DimensionCount!(Slices[0])], size_t) findIndex(Slices...)(Slices slices) if (Slices.length) { static if (Slices.length > 1) @@ -1789,8 +2004,7 @@ Optimization: use the last dimension (row index). This will slightly optimize the code. -------- -// $-1 instead of 0 -if (backwardIndex[$-1]) +if (backwardIndex) { auto elem1 = slice1.backward(backwardIndex); //... @@ -1819,11 +2033,11 @@ template find(alias pred) slices = One or more slices. Returns: Multidimensional backward index such that the predicate is true. - Backward index equals zeros, if the predicate evaluates `false` for all indexes. + Backward index equals zeros, if the predicate evaluates `false` for all indices. Constraints: All slices must have the same shape. +/ - @optmath Select!(DimensionCount!(Slices[0]) > 1, size_t[DimensionCount!(Slices[0])], size_t) find(Slices...)(auto ref Slices slices) + @fmamath Select!(DimensionCount!(Slices[0]) > 1, size_t[DimensionCount!(Slices[0])], size_t) find(Slices...)(Slices slices) if (Slices.length && allSatisfy!(hasShape, Slices)) { static if (Slices.length > 1) @@ -1930,9 +2144,10 @@ version(mir_test) unittest assert(bi == [1, 1]); assert(sl.backward(bi) == 5); + import mir.test; // sl was changed - assert(sl == [[8, 8, 8], - [8, 8, 5]]); + sl.should == [[8, 8, 8], + [8, 8, 5]]; } @safe pure nothrow @@ -1945,7 +2160,7 @@ version(mir_test) unittest assert(bi == [0, 0]); } -size_t anyImpl(alias fun, Slices...)(scope Slices slices) +size_t anyImpl(alias fun, Slices...)(Slices slices) if (Slices.length) { static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) @@ -1965,12 +2180,12 @@ size_t anyImpl(alias fun, Slices...)(scope Slices slices) { static if (DimensionCount!(Slices[0]) == 1) { - if (mixin("fun(" ~ frontOf!(Slices.length) ~ ")")) + if (mixin(`fun(`~ frontOf!(Slices.length) ~ `)`)) return true; } else { - if (mixin("anyImpl!fun(" ~ frontOf!(Slices.length) ~ ")")) + if (anyImpl!fun(frontOf2!slices)) return true; } foreach_reverse(ref slice; slices) @@ -2001,7 +2216,7 @@ template any(alias pred = "a") Constraints: All slices must have the same shape. +/ - @optmath bool any(Slices...)(scope Slices slices) + @fmamath bool any(Slices...)(Slices slices) if ((Slices.length == 1 || !__traits(isSame, pred, "a")) && Slices.length) { static if (Slices.length > 1) @@ -2105,7 +2320,7 @@ version(mir_test) unittest [8, 8, 5]]); } -size_t allImpl(alias fun, Slices...)(scope Slices slices) +size_t allImpl(alias fun, Slices...)(Slices slices) if (Slices.length) { static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) @@ -2125,12 +2340,12 @@ size_t allImpl(alias fun, Slices...)(scope Slices slices) { static if (DimensionCount!(Slices[0]) == 1) { - if (!mixin("fun(" ~ frontOf!(Slices.length) ~ ")")) + if (!mixin(`fun(`~ frontOf!(Slices.length) ~ `)`)) return false; } else { - if (!mixin("allImpl!fun(" ~ frontOf!(Slices.length) ~ ")")) + if (!allImpl!fun(frontOf2!slices)) return false; } foreach_reverse(ref slice; slices) @@ -2161,7 +2376,7 @@ template all(alias pred = "a") Constraints: All slices must have the same shape. +/ - @optmath bool all(Slices...)(scope Slices slices) + @fmamath bool all(Slices...)(Slices slices) if ((Slices.length == 1 || !__traits(isSame, pred, "a")) && Slices.length) { static if (Slices.length > 1) @@ -2291,7 +2506,7 @@ template count(alias fun) Constraints: All slices must have the same shape. +/ - @optmath size_t count(Slices...)(scope Slices slices) + @fmamath size_t count(Slices...)(Slices slices) if (Slices.length) { static if (Slices.length > 1) @@ -2374,6 +2589,7 @@ unittest assert(sl2[41 .. $ - 1].count!"a" == 1); } +version(mir_test) unittest { import mir.ndslice.allocation: slice; @@ -2403,32 +2619,49 @@ template equal(alias pred = "a == b") { import mir.functional: naryFun; static if (__traits(isSame, naryFun!pred, pred)) - /++ - Params: - slices = Two or more slices, slices, ranges, and arrays. - - Returns: - `true` any of the elements verify `pred` and `false` otherwise. - +/ - bool equal(Slices...)(scope Slices slices) - if (Slices.length >= 2) { - enum msg = "all arguments must be slices" ~ tailErrorMessage!(); - enum msgShape = "all slices must have the same dimension count" ~ tailErrorMessage!(); - import mir.internal.utility; - foreach (i, Slice; Slices) + /++ + Params: + slices = Two or more ndslices, ranges, and arrays. + + Returns: + `true` any of the elements verify `pred` and `false` otherwise. + +/ + bool equal(Slices...)(Slices slices) @safe + if (Slices.length >= 2) { - // static assert (isSlice!Slice, msg); - static if (i) + import mir.internal.utility; + static if (allSatisfy!(hasShape, Slices)) + { + auto shape0 = slices[0].shape; + enum N = DimensionCount!(Slices[0]); + foreach (ref slice; slices[1 .. $]) + { + if (slice.shape != shape0) + goto False; + } + return all!pred(allLightScope!slices); + } + else { - static assert (DimensionCount!(Slices[i]) == DimensionCount!(Slices[0])); - foreach (j; Iota!(DimensionCount!(Slices[0]))) - if (slices[i].shape[j] != slices[0].shape[j]) + for(;;) + { + auto empty = slices[0].empty; + foreach (ref slice; slices[1 .. $]) + { + if (slice.empty != empty) + goto False; + } + if (empty) + return true; + if (!mixin(`pred(`~ frontOf!(Slices.length) ~ `)`)) goto False; + foreach (ref slice; slices) + slice.popFront; + } } + False: return false; } - return all!pred(allLightScope!slices); - False: return false; } else alias equal = .equal!(naryFun!pred); @@ -2460,7 +2693,7 @@ version(mir_test) unittest assert(equal(sl1, sl1)); assert(sl1 == sl1); //can also use opEquals for two Slices assert(equal!"2 * a == b + c"(sl1, sl1, sl1)); - + assert(equal!"a < b"(sl1, sl2)); assert(!equal(sl1[0 .. $ - 1], sl1)); @@ -2470,14 +2703,13 @@ version(mir_test) unittest @safe pure nothrow @nogc version(mir_test) unittest { - import mir.algorithm.iteration: equal; import mir.math.common: approxEqual; import mir.ndslice.allocation: rcslice; import mir.ndslice.topology: as, iota; - + auto x = 5.iota.as!double.rcslice; auto y = x.rcslice; - + assert(equal(x, y)); assert(equal!approxEqual(x, y)); } @@ -2537,7 +2769,7 @@ template cmp(alias pred = "a < b") Positive value if the first differing element of `sl2` is less than the corresponding element of `sl1` according to `pred`. +/ - ptrdiff_t cmp(A, B) + auto cmp(A, B) (scope A sl1, scope B sl2) if (DimensionCount!A == DimensionCount!B) { @@ -2549,8 +2781,8 @@ template cmp(alias pred = "a < b") auto sh1 = sl1.shape; auto sh2 = sl2.shape; foreach (i; Iota!(DimensionCount!A)) - if (ptrdiff_t ret = sh1[i] - sh2[i]) - return ret; + if (sh1[i] != sh2[i]) + return sh1[i] > sh2[i] ? 1 : -1; return 0; } if (b) @@ -2613,7 +2845,7 @@ version(mir_test) unittest assert(cmp(sl1[0 .. $ - 1, 0 .. $ - 3], sl1[0 .. $, 0 .. $ - 3]) < 0); } -size_t countImpl(alias fun, Slices...)(scope Slices slices) +size_t countImpl(alias fun, Slices...)(Slices slices) { size_t ret; alias S = Slices[0]; @@ -2636,11 +2868,11 @@ size_t countImpl(alias fun, Slices...)(scope Slices slices) { static if (DimensionCount!(Slices[0]) == 1) { - if(mixin("fun(" ~ frontOf!(Slices.length) ~ ")")) + if(mixin(`fun(`~ frontOf!(Slices.length) ~ `)`)) ret++; } else - ret += mixin(".countImpl!fun(" ~ frontOf!(Slices.length) ~ ")"); + ret += .countImpl!fun(frontOf2!slices); foreach_reverse(ref slice; slices) slice.popFront; } @@ -2648,30 +2880,6 @@ size_t countImpl(alias fun, Slices...)(scope Slices slices) return ret; } -private template selectBackOf(size_t N, string input) -{ - static if (N == 0) - enum selectBackOf = ""; - else - { - enum i = N - 1; - enum selectBackOf = selectBackOf!(i, input) ~ - "lightScope(slices[" ~ i.stringof ~ "]).selectBack!0(" ~ input ~ "), "; - } -} - -private template frontSelectFrontOf(size_t N, string input) -{ - static if (N == 0) - enum frontSelectFrontOf = ""; - else - { - enum i = N - 1; - enum frontSelectFrontOf = frontSelectFrontOf!(i, input) ~ - "lightScope(slices[" ~ i.stringof ~ "]).front!0.selectFront!0(" ~ input ~ "), "; - } -} - /++ Returns: max length across all dimensions. +/ @@ -2719,8 +2927,8 @@ template eachLower(alias fun) function applied. +/ void eachLower(Inputs...)(scope Inputs inputs) - if (((Inputs.length > 1) && - (isIntegral!(Inputs[$ - 1]))) || + if (((Inputs.length > 1) && + (isIntegral!(Inputs[$ - 1]))) || (Inputs.length)) { import mir.ndslice.traits : isMatrix; @@ -2756,7 +2964,7 @@ template eachLower(alias fun) if ((n + k) < m) { val = m - (n + k); - mixin(".eachImpl!fun(" ~ selectBackOf!(Slices.length, "val") ~ ");"); + .eachImpl!fun(selectBackOf!(val, slices)); } size_t i; @@ -2771,7 +2979,7 @@ template eachLower(alias fun) do { val = i - k + 1; - mixin(".eachImpl!fun(" ~ frontSelectFrontOf!(Slices.length, "val") ~ ");"); + .eachImpl!fun(frontSelectFrontOf!(val, slices)); foreach(ref slice; slices) slice.popFront!0; @@ -3127,30 +3335,6 @@ version(mir_test) unittest [ 6, 7, 18]]); } -private template frontSelectBackOf(size_t N, string input) -{ - static if (N == 0) - enum frontSelectBackOf = ""; - else - { - enum i = N - 1; - enum frontSelectBackOf = frontSelectBackOf!(i, input) ~ - "lightScope(slices[" ~ i.stringof ~ "]).front.selectBack!0(" ~ input ~ "), "; - } -} - -private template selectFrontOf(size_t N, string input) -{ - static if (N == 0) - enum selectFrontOf = ""; - else - { - enum i = N - 1; - enum selectFrontOf = selectFrontOf!(i, input) ~ - "lightScope(slices[" ~ i.stringof ~ "]).selectFront!0(" ~ input ~ "), "; - } -} - /++ The call `eachUpper!(fun)(slice1, ..., sliceN)` evaluates `fun` on the upper triangle in `slice1, ..., sliceN`, respectively. @@ -3184,8 +3368,8 @@ template eachUpper(alias fun) function applied. +/ void eachUpper(Inputs...)(scope Inputs inputs) - if (((Inputs.length > 1) && - (isIntegral!(Inputs[$ - 1]))) || + if (((Inputs.length > 1) && + (isIntegral!(Inputs[$ - 1]))) || (Inputs.length)) { import mir.ndslice.traits : isMatrix; @@ -3223,7 +3407,7 @@ template eachUpper(alias fun) if (k < 0) { val = -k; - mixin(".eachImpl!fun(" ~ selectFrontOf!(Slices.length, "val") ~ ");"); + .eachImpl!fun(selectFrontOf!(val, slices)); foreach(ref slice; slices) slice.popFrontExactly!0(-k); @@ -3233,7 +3417,7 @@ template eachUpper(alias fun) do { val = (n - k) - i; - mixin(".eachImpl!fun(" ~ frontSelectBackOf!(Slices.length, "val") ~ ");"); + .eachImpl!fun(frontSelectBackOf!(val, slices)); foreach(ref slice; slices) slice.popFront; @@ -3600,29 +3784,48 @@ bidirectional, $(D uniq) also yields a `std,range,primitives`. Params: pred = Predicate for determining equivalence between range elements. - r = An input range of elements to filter. -Returns: - An input range of - consecutively unique elements in the original range. If `r` is also a - forward range or bidirectional range, the returned range will be likewise. */ -Uniq!(naryFun!pred, Range) uniq(alias pred = "a == b", Range)(auto ref Range r) -if (isInputRange!Range && is(typeof(naryFun!pred(r.front, r.front)) == bool)) +template uniq(alias pred = "a == b") { - return typeof(return)(r); + static if (__traits(isSame, naryFun!pred, pred)) + { + /++ + Params: + r = An input range of elements to filter. + Returns: + An input range of + consecutively unique elements in the original range. If `r` is also a + forward range or bidirectional range, the returned range will be likewise. + +/ + Uniq!(naryFun!pred, Range) uniq(Range)(Range r) + if (isInputRange!Range && !isSlice!Range) + { + import core.lifetime: move; + return typeof(return)(r.move); + } + + /// ditto + auto uniq(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + { + import mir.ndslice.topology: flattened; + import core.lifetime: move; + auto r = slice.move.flattened; + return Uniq!(pred, typeof(r))(move(r)); + } + } + else + alias uniq = .uniq!(naryFun!pred); } /// @safe version(mir_test) unittest { - import std.algorithm.comparison : equal; - import std.algorithm.mutation : copy; - int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; - assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ][])); + assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ])); + import std.algorithm.mutation : copy; // Filter duplicates in-place using copy - arr.length -= arr.uniq().copy(arr).length; + arr.length -= arr.uniq.copy(arr).length; assert(arr == [ 1, 2, 3, 4, 5 ]); // Note that uniqueness is only determined consecutively; duplicated @@ -3631,25 +3834,28 @@ if (isInputRange!Range && is(typeof(naryFun!pred(r.front, r.front)) == bool)) assert(equal(uniq([ 1, 1, 2, 1, 1, 3, 1]), [1, 2, 1, 3, 1])); } +/// N-dimensional case +version(mir_test) +@safe pure unittest +{ + import mir.ndslice.fuse; + import mir.ndslice.topology: byDim, map, iota; + + auto matrix = [ [1, 2, 2], [2, 2, 3], [4, 4, 4] ].fuse; + + assert(matrix.uniq.equal([ 1, 2, 3, 4 ])); + + // unique elements for each row + assert(matrix.byDim!0.map!uniq.equal!equal([ [1, 2], [2, 3], [4] ])); +} + /++ -Authros: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilya Yaroshenko (betterC rework) +Authros: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilia Ki (betterC rework) +/ struct Uniq(alias pred, Range) { Range _input; - // this()(auto ref Range input) - // { - // alias AliasSeq(T...) = T; - // import mir.functional: forward; - // AliasSeq!_input = forward!input; - // } - - ref opSlice() inout - { - return this; - } - void popFront() scope { assert(!empty, "Attempting to popFront an empty uniq."); @@ -3661,12 +3867,14 @@ struct Uniq(alias pred, Range) while (!_input.empty && pred(last, _input.front)); } - @property ElementType!Range front() + auto ref front() @property { assert(!empty, "Attempting to fetch the front of an empty uniq."); return _input.front; } + import std.range.primitives: isBidirectionalRange; + static if (isBidirectionalRange!Range) { void popBack() scope @@ -3680,7 +3888,7 @@ struct Uniq(alias pred, Range) while (!_input.empty && pred(last, _input.back)); } - @property ElementType!Range back() scope return + auto ref back() return scope @property { assert(!empty, "Attempting to fetch the back of an empty uniq."); return _input.back; @@ -3696,9 +3904,22 @@ struct Uniq(alias pred, Range) @property bool empty() const { return _input.empty; } } + ref opIndex()() scope return + { + return this; + } + + auto opIndex()() const return scope + { + return Filter!(typeof(_input[]))(_input[]); + } + + import std.range.primitives: isForwardRange; + static if (isForwardRange!Range) { - @property typeof(this) save() scope return { + @property typeof(this) save() return scope + { return typeof(this)(_input.save); } } @@ -3707,7 +3928,6 @@ struct Uniq(alias pred, Range) version(none) @safe version(mir_test) unittest { - import std.algorithm.comparison : equal; import std.internal.test.dummyrange; import std.range; @@ -3735,8 +3955,565 @@ version(none) @safe version(mir_test) unittest // https://issues.dlang.org/show_bug.cgi?id=17264 { - import std.algorithm.comparison : equal; - const(int)[] var = [0, 1, 1, 2]; assert(var.uniq.equal([0, 1, 2])); } + +@safe version(mir_test) unittest { + import mir.ndslice.allocation; + import mir.math.common: approxEqual; + auto x = rcslice!double(2); + auto y = rcslice!double(2); + x[] = [2, 3]; + y[] = [2, 3]; + assert(equal!approxEqual(x,y)); +} + +/++ +Implements the higher order filter function. The predicate is passed to +`mir.functional.naryFun`, and can either accept a string, or any callable +that can be executed via `pred(element)`. +Params: + pred = Function to apply to each element of range +Returns: + `filter!(pred)(range)` returns a new range containing only elements `x` in `range` for + which `pred(x)` returns `true`. +See_Also: + $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) +Note: + $(RED User and library code MUST call `empty` method ahead each call of pair or one of `front` and `popFront` methods.) ++/ +template filter(alias pred = "a") +{ + static if (__traits(isSame, naryFun!pred, pred)) + { + /++ + Params: + r = An input range of elements to filter. + Returns: + A new range containing only elements `x` in `range` for which `predicate(x)` returns `true`. + +/ + Filter!(naryFun!pred, Range) filter(Range)(Range r) + if (isInputRange!Range && !isSlice!Range) + { + import core.lifetime: move; + return typeof(return)(r.move); + } + + /// ditto + auto filter(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + { + import mir.ndslice.topology: flattened; + import core.lifetime: move; + auto r = slice.move.flattened; + return Filter!(pred, typeof(r))(move(r)); + } + } + else + alias filter = .filter!(naryFun!pred); +} + +/// ditto +struct Filter(alias pred, Range) +{ + Range _input; + version(assert) bool _freshEmpty; + + void popFront() scope + { + assert(!_input.empty, "Attempting to popFront an empty Filter."); + version(assert) assert(_freshEmpty, "Attempting to pop the front of a Filter without calling '.empty' method ahead."); + version(assert) _freshEmpty = false; + _input.popFront; + } + + auto ref front() @safe @property return scope + { + assert(!_input.empty, "Attempting to fetch the front of an empty Filter."); + version(assert) assert(_freshEmpty, "Attempting to fetch the front of a Filter without calling '.empty' method ahead."); + return _input.front; + } + + bool empty() @safe scope @property + { + version(assert) _freshEmpty = true; + for (;;) + { + if (auto r = _input.empty) + return true; + if (pred(_input.front)) + return false; + _input.popFront; + } + } + + import std.range.primitives: isForwardRange; + static if (isForwardRange!Range) + { + @property typeof(this) save() return scope + { + return typeof(this)(_input.save); + } + } + + ref opIndex()() scope return + { + return this; + } + + auto opIndex()() const return scope + { + return Filter!(pred, typeof(_input[]))(_input[]); + } +} + +/// +version(mir_test) +@safe pure nothrow unittest +{ + int[] arr = [ 0, 1, 2, 3, 4, 5 ]; + + // Filter below 3 + auto small = filter!(a => a < 3)(arr); + assert(equal(small, [ 0, 1, 2 ])); + + // Filter again, but with Uniform Function Call Syntax (UFCS) + auto sum = arr.filter!(a => a < 3); + assert(equal(sum, [ 0, 1, 2 ])); + + // Filter with the default predicate + auto nonZeros = arr.filter; + assert(equal(nonZeros, [ 1, 2, 3, 4, 5 ])); + + // In combination with concatenation() to span multiple ranges + import mir.ndslice.concatenation; + + int[] a = [ 3, -2, 400 ]; + int[] b = [ 100, -101, 102 ]; + auto r = concatenation(a, b).filter!(a => a > 0); + assert(equal(r, [ 3, 400, 100, 102 ])); + + // Mixing convertible types is fair game, too + double[] c = [ 2.5, 3.0 ]; + auto r1 = concatenation(c, a, b).filter!(a => cast(int) a != a); + assert(equal(r1, [ 2.5 ])); +} + +/// N-dimensional filtering +version(mir_test) +@safe pure unittest +{ + import mir.ndslice.fuse; + import mir.ndslice.topology: byDim, map; + + auto matrix = + [[ 3, -2, 400 ], + [ 100, -101, 102 ]].fuse; + + alias filterPositive = filter!"a > 0"; + + // filter all elements in the matrix + auto r = filterPositive(matrix); + assert(equal(r, [ 3, 400, 100, 102 ])); + + // filter all elements for each row + auto rr = matrix.byDim!0.map!filterPositive; + assert(equal!equal(rr, [ [3, 400], [100, 102] ])); + + // filter all elements for each column + auto rc = matrix.byDim!1.map!filterPositive; + assert(equal!equal(rc, [ [3, 100], [], [400, 102] ])); +} + +/// N-dimensional filtering based on value in specific row/column +version(mir_test) +@safe pure +unittest +{ + import mir.ndslice.fuse; + import mir.ndslice.topology: byDim; + + auto matrix = + [[ 3, 2, 400 ], + [ 100, -101, 102 ]].fuse; + + // filter row based on value in index 1 + auto r1 = matrix.byDim!0.filter!("a[1] > 0"); + assert(equal!equal(r1, [ [3, 2, 400] ])); + + // filter column based on value in index 1 + auto r2 = matrix.byDim!1.filter!("a[1] > 0"); + assert(equal!equal(r2, [ [3, 100], [400, 102] ])); +} + +/// Filter out NaNs +version(mir_test) +@safe pure nothrow +unittest { + import mir.algorithm.iteration: equal, filter; + import mir.ndslice.slice: sliced; + import std.math.traits: isNaN; + + static immutable result1 = [1.0, 2]; + + double x; + auto y = [1.0, 2, x].sliced; + auto z = y.filter!(a => !isNaN(a)); + assert(z.equal(result1)); +} + +/// Filter out NaNs by row and by column +version(mir_test) +@safe pure +unittest { + import mir.algorithm.iteration: equal, filter; + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: byDim, map; + import std.math.traits: isNaN; + + static immutable result1 = [[1.0, 2], [3.0, 4, 5]]; + static immutable result2 = [[1.0, 3], [2.0, 4], [5.0]]; + + double x; + auto y = [[1.0, 2, x], [3.0, 4, 5]].fuse; + + // by row + auto z1 = y.byDim!0.map!(filter!(a => !isNaN(a))); + assert(z1.equal!equal(result1)); + // by column + auto z2 = y.byDim!1.map!(filter!(a => !isNaN(a))); + assert(z2.equal!equal(result2)); +} + +/// Filter entire rows/columns that have NaNs +version(mir_test) +@safe pure +unittest { + import mir.algorithm.iteration: equal, filter; + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: byDim, map; + import std.math.traits: isNaN; + + static immutable result1 = [[3.0, 4, 5]]; + static immutable result2 = [[1.0, 3], [2.0, 4]]; + + double x; + auto y = [[1.0, 2, x], [3.0, 4, 5]].fuse; + + // by row + auto z1 = y.byDim!0.filter!(a => a.all!(b => !isNaN(b))); + assert(z1.equal!equal(result1)); + // by column + auto z2 = y.byDim!1.filter!(a => a.all!(b => !isNaN(b))); + assert(z2.equal!equal(result2)); +} + +// Ensure filter works with each +version(mir_test) +@safe pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + int[] result = [3, 4]; + + int[] x = [1, 2, 3]; + auto y = x.filter!"a >= 2"; + y.each!"a++"; + + assert(y.equal(result)); +} + +/++ +Implements the higher order filter and map function. The predicate and map functions are passed to +`mir.functional.naryFun`, and can either accept a string, or any callable +that can be executed via `pred(element)` and `map(element)`. +Params: + pred = Filter function to apply to each element of range (optional) + map = Map function to apply to each element of range +Returns: + `rcfilter!(pred)(range)` returns a new RCArray containing only elements `map(x)` in `range` for + which `pred(x)` returns `true`. +See_Also: + $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) ++/ +template rcfilter(alias pred = "a", alias map = "a") +{ + static if (__traits(isSame, naryFun!pred, pred) && __traits(isSame, naryFun!map, map)) + { + /++ + Params: + r = An input range of elements to filter. + Returns: + A new range containing only elements `x` in `range` for which `predicate(x)` returns `true`. + +/ + auto rcfilter(Range)(Range r) + if (isIterable!Range && (!isSlice!Range || DimensionCount!Range == 1)) + { + import core.lifetime: forward; + import mir.appender: scopedBuffer; + import mir.primitives: isInputRange; + import mir.rc.array: RCArray; + + alias T = typeof(map(r.front)); + auto buffer = scopedBuffer!T; + foreach (ref e; r) + { + if (pred(e)) + { + static if (__traits(isSame, naryFun!"a", map)) + buffer.put(forward!e); + else + buffer.put(map(forward!e)); + } + } + return () @trusted + { + auto ret = RCArray!T(buffer.length); + buffer.moveDataAndEmplaceTo(ret[]); + return ret; + } (); + } + + /// ditto + auto rcfilter(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + if (N > 1) + { + import mir.ndslice.topology: flattened; + import core.lifetime: move; + return rcfilter(slice.move.flattened); + } + } + else + alias rcfilter = .rcfilter!(naryFun!pred, naryFun!map); +} + +/// +version(mir_test) +@safe pure nothrow @nogc unittest +{ + import mir.ndslice.topology: iota; + + auto val = 3; + auto factor = 5; + // Filter iota 2x3 matrix below 3 + assert(iota(2, 3).rcfilter!(a => a < val).moveToSlice.equal(val.iota)); + // Filter and map below 3 + assert(6.iota.rcfilter!(a => a < val, a => a * factor).moveToSlice.equal(val.iota * factor)); +} + +version(mir_test) +@safe pure nothrow @nogc unittest +{ + import mir.ndslice.topology: iota, as; + + auto val = 3; + auto factor = 5; + // Filter iota 2x3 matrix below 3 + assert(iota(2, 3).as!(const int).rcfilter!(a => a < val).moveToSlice.equal(val.iota)); + // Filter and map below 3 + assert(6.iota.as!(immutable int).rcfilter!(a => a < val, a => a * factor).moveToSlice.equal(val.iota * factor)); +} + +/++ +Implements the homonym function (also known as `accumulate`, $(D +compress), `inject`, or `foldl`) present in various programming +languages of functional flavor. The call `fold!(fun)(slice, seed)` +first assigns `seed` to an internal variable `result`, +also called the accumulator. Then, for each element `x` in $(D +slice), `result = fun(result, x)` gets evaluated. Finally, $(D +result) is returned. + +Params: + fun = the predicate function to apply to the elements + +See_Also: + $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) + $(LREF sum) is similar to `fold!((a, b) => a + b)` that offers + precise summing of floating point numbers. + This is functionally equivalent to $(LREF reduce) with the argument order + reversed. ++/ +template fold(alias fun) +{ + /++ + Params: + slice = A slice, range, and array. + seed = An initial accumulation value. + Returns: + the accumulated result + +/ + @fmamath auto fold(Slice, S)(scope Slice slice, S seed) + { + import core.lifetime: move; + return reduce!fun(seed, slice.move); + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: map; + + auto arr = [1, 2, 3, 4, 5].sliced; + + // Sum all elements + assert(arr.fold!((a, b) => a + b)(0) == 15); + assert(arr.fold!((a, b) => a + b)(6) == 21); + + // Can be used in a UFCS chain + assert(arr.map!(a => a + 1).fold!((a, b) => a + b)(0) == 20); + + // Return the last element of any range + assert(arr.fold!((a, b) => b)(0) == 5); +} + +/// Works for matrices +version(mir_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + + auto arr = [ + [1, 2, 3], + [4, 5, 6] + ].fuse; + + assert(arr.fold!((a, b) => a + b)(0) == 21); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.topology: map; + + int[] arr = [1, 2, 3, 4, 5]; + + // Sum all elements + assert(arr.fold!((a, b) => a + b)(0) == 15); + assert(arr.fold!((a, b) => a + b)(6) == 21); + + // Can be used in a UFCS chain + assert(arr.map!(a => a + 1).fold!((a, b) => a + b)(0) == 20); + + // Return the last element of any range + assert(arr.fold!((a, b) => b)(0) == 5); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + int[] arr = [1]; + static assert(!is(typeof(arr.fold!()(0)))); + static assert(!is(typeof(arr.fold!(a => a)(0)))); + static assert(is(typeof(arr.fold!((a, b) => a)(0)))); + assert(arr.length == 1); +} + +version(mir_test) +unittest +{ + import mir.rc.array: RCArray; + import mir.algorithm.iteration: minmaxPos, minPos, maxPos, minmaxIndex, minIndex, maxIndex; + + static immutable a = [0.0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; + + auto x = RCArray!double(12); + foreach(i, ref e; x) + e = a[i]; + auto y = x.asSlice; + auto z0 = y.minmaxPos; + auto z1 = y.minPos; + auto z2 = y.maxPos; + auto z3 = y.minmaxIndex; + auto z4 = y.minIndex; + auto z5 = y.maxIndex; +} + +/++ +Returns the minimal(maximal) element of a multidimensional slice. + +Params: + pred = A predicate. + +See_also: + $(LREF minIndex), + $(LREF maxElement), + $(LREF maxIndex), + $(LREF maxPos). ++/ +template minElement(alias pred = "a < b") +{ + import mir.functional: naryFun; + static if (__traits(isSame, naryFun!pred, pred)) + /++ + Params: + slice = ndslice. + Returns: + Minimal(maximal) element of a multidimensional slice + +/ + @fmamath DeepElementType!(Slice!(Iterator, N, kind)) minElement(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + { + return slice[slice.minIndex!pred]; + } + else + alias minElement = .minElement!(naryFun!pred); +} + +/// ditto +template maxElement(alias pred = "a < b") +{ + import mir.functional: naryFun, reverseArgs; + alias maxElement = minElement!(reverseArgs!(naryFun!pred)); +} + +/// +@safe pure nothrow +version(mir_test) +unittest +{ + import mir.ndslice.slice: sliced; + auto s = [ + 2, 6, 4, -3, + 0, -4, -3, 3, + -3, -2, 7, 8, + ].sliced(3, 4); + + assert(s.minElement == -4); + assert(s.maxElement == 8); +} + +/// +@safe pure nothrow +version(mir_test) +unittest +{ + import mir.ndslice.slice: sliced; + auto s = [ + -8, 6, 4, -3, + 0, -4, -3, 3, + -3, -2, 7, 8, + ].sliced(3, 4); + + assert(s.minElement == -8); +} + +@safe pure nothrow +version(mir_test) +unittest +{ + import mir.ndslice.slice: sliced; + auto s = [ + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11 + ].sliced(3, 4); + + assert(s.minElement == 0); + assert(s.maxElement == 11); +} diff --git a/source/mir/algorithm/setops.d b/source/mir/algorithm/setops.d index edc8382b..201a091f 100644 --- a/source/mir/algorithm/setops.d +++ b/source/mir/algorithm/setops.d @@ -2,13 +2,18 @@ /** This is a submodule of $(MREF mir, algorithm). It contains `nothrow` `@nogc` BetterC alternative to `MultiwayMerge` from `std.algorithm.setops`. -Copyright: Andrei Alexandrescu 2008-. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilya Yaroshenko (Mir & BetterC rework, optimization). +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments + +Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilia Ki (Mir & BetterC rework, optimization). */ module mir.algorithm.setops; +import core.lifetime: move; import mir.functional: naryFun; +import mir.primitives; +import mir.qualifier; +import std.range.primitives: isRandomAccessRange; /** Merges multiple sets. The input sets are passed as a @@ -43,10 +48,10 @@ want to pass a duplicate to $(D MultiwayMerge) (and perhaps cache the duplicate in between calls). */ struct MultiwayMerge(alias less, RangeOfRanges) + if (isRandomAccessRange!RangeOfRanges) { import mir.primitives; import mir.container.binaryheap; - import std.range.primitives: ElementType; /// @disable this(); @@ -64,14 +69,25 @@ struct MultiwayMerge(alias less, RangeOfRanges) BinaryHeap!(compFront, RangeOfRanges) _heap; /// - this(RangeOfRanges ror) + this(scope return RangeOfRanges ror) { - import std.algorithm.mutation : remove, SwapStrategy; - // Preemptively get rid of all empty ranges in the input // No need for stability either + auto temp = ror.lightScope; + for (;!temp.empty;) + { + if (!temp.front.empty) + { + temp.popFront; + continue; + } + import mir.utility: swap; + () @trusted {swap(temp.back, temp.front);} (); + temp.popBack; + ror.popBack; + } //Build the heap across the range - _heap = typeof(_heap)(ror.remove!("a.empty", SwapStrategy.unstable)); + _heap = typeof(_heap)(ror.move); } /// @@ -98,15 +114,15 @@ struct MultiwayMerge(alias less, RangeOfRanges) /// Ditto MultiwayMerge!(naryFun!less, RangeOfRanges) multiwayMerge (alias less = "a < b", RangeOfRanges) -(RangeOfRanges ror) +(scope RangeOfRanges ror) { - return typeof(return)(ror); + return typeof(return)(move(ror)); } /// @safe nothrow @nogc version(mir_test) unittest { - import std.algorithm.comparison : equal; + import mir.algorithm.iteration: equal; static a = [ @@ -147,18 +163,18 @@ Returns: A range of the union of the ranges in `ror`. See also: $(LREF multiwayMerge) */ -auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) +auto multiwayUnion(alias less = "a < b", RangeOfRanges)(scope RangeOfRanges ror) { import mir.functional: not; import mir.algorithm.iteration : Uniq; - return Uniq!(not!less, typeof(multiwayMerge!less(ror)))(multiwayMerge!less(ror)); + return Uniq!(not!less, typeof(multiwayMerge!less(ror)))(multiwayMerge!less(move(ror))); } /// -@system version(mir_test) unittest +@safe version(mir_test) unittest { - import std.algorithm.comparison : equal; + import mir.algorithm.iteration: equal; // sets double[][] a = @@ -196,10 +212,10 @@ Returns: A length of the union of the ranges in `ror`. +/ pragma(inline, false) -size_t unionLength(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) +size_t unionLength(alias less = "a < b", RangeOfRanges)(scope RangeOfRanges ror) { size_t length; - auto u = ror.multiwayUnion!less; + auto u = move(ror).multiwayUnion!less; if (!u.empty) do { length++; u.popFront; diff --git a/source/mir/annotated.d b/source/mir/annotated.d new file mode 100644 index 00000000..7b2d7c81 --- /dev/null +++ b/source/mir/annotated.d @@ -0,0 +1,302 @@ +/++ +$(H1 Annotated value) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki +Macros: ++/ +module mir.annotated; + +import mir.internal.meta: basicElementType; +import mir.serde: serdeRegister, serdeAnnotation, serdeIsDynamicAlgebraic; + +static immutable excMsg = "At least one annotation is required to create an annotated value."; +version (D_Exceptions) + static immutable exc = new Exception(excMsg); + +/++ +A convenience definition of an annotated value. + +A structure that behaves like a recursive algebraic type should define `enum _serdeRecursiveAlgebraic;` member. ++/ +@serdeRegister +@serdeAnnotation +struct Annotated(T) { + /// + @serdeAnnotation + string[] annotations; + + static if (!(is(T == union) || is(T == struct))) + private enum _alloc_ = false; + else + static if (__traits(hasMember, T, "_serdeRecursiveAlgebraic")) + private enum _alloc_ = true; + else + { + import mir.algebraic: isVariant; + static if (isVariant!T) + private enum _alloc_ = true; + else + private enum _alloc_ = false; + } + + static if (_alloc_) + { + /// + private T* _value; + /// + ref inout(T) value() inout @property + in(_value) + { + return *_value; + } + + /// + ref T value(T value) @property return scope + { + if (_value is null) + { + _value = new T; + import core.lifetime: move; + *_value = move(value); + } + return *_value; + } + + /// + bool opEquals(scope const Annotated rhs) scope const + { + return annotations == rhs.annotations && value == rhs.value; + } + + size_t toHash() scope @trusted const pure nothrow @nogc + { + static if (__traits(compiles, hashOf(value))) + return hashOf(value); + else + { + debug pragma(msg, "Mir warning: can't compute hash. Expexted `size_t toHash() scope @safe const pure nothrow @nogc` method for " ~ T.stringof); + return cast(size_t)_value; + } + } + } + else + { + /// + T value; + } + + + /++ + Params: + annotations = non-empty array of annotations + args = arguments to construct value with + +/ + this(Args...)(string[] annotations, Args args) @safe pure { + if (annotations.length == 0) + { + version (D_Exceptions) + { import mir.exception : toMutable; throw exc.toMutable; } + else + assert(0, excMsg); + } + import core.lifetime: forward; + this.annotations = annotations; + static if (_alloc_) + this._value = new T(forward!args); + else + static if (__traits(compiles, value = args)) + this.value = args; + else + static if (is(T == class)) + this.value = new T(forward!args); + else + this.value = T(forward!args); + } + + // private alias E = .basicElementType!T; + + import std.traits: isAssociativeArray, isAggregateType; + /// + int opCmp()(scope const typeof(this) rhs) scope const pure nothrow @nogc @system + // if (!isAssociativeArray!E && (!isAggregateType!E || __traits(hasMember, E, "opCmp"))) + { + if (auto d = __cmp(annotations, rhs.annotations)) + return d; + + static if (__traits(compiles, __cmp(value, rhs.value))) + return __cmp(value, rhs.value); + else + static if (__traits(hasMember, value, "opCmp") && !is(T[i] == U*, U)) + return value.opCmp(rhs.value); + else + return value < rhs.value ? -1 : value > rhs.value ? +1 : 0; + } +} + +/// +version(mir_test) +unittest +{ + auto annotations = ["annotation"]; + static struct S {double x;} + auto as = Annotated!S(annotations, 5); + assert(as.annotations == annotations); + assert(as.value.x == 5); + + static struct C {double x;} + auto ac = Annotated!S(annotations, 5); + assert(ac.annotations == annotations); + assert(ac.value.x == 5); +} + +/// +version(mir_test) +unittest +{ + import mir.algebraic; + auto annotations = ["annotation"]; + static struct S {double x;} + auto as = Annotated!(Variant!S)(annotations, 5); + assert(as.annotations == annotations); + assert(as.value.x == 5); + + static struct C {double x;} + auto ac = Annotated!(Variant!S)(annotations, 5); + assert(ac.annotations == annotations); + assert(ac.value.x == 5); +} + +/++ +A convenience definition of an annotated value. + +A structure that behaves like a recursive algebraic type should define `enum _serdeRecursiveAlgebraic;` member. ++/ +@serdeRegister +@serdeAnnotation +struct AnnotatedOnce(T) { + /// + @serdeAnnotation + string annotation; + + static if (!(is(T == union) || is(T == struct))) + private enum _alloc_ = false; + else + static if (__traits(hasMember, T, "_serdeRecursiveAlgebraic")) + private enum _alloc_ = true; + else + { + import mir.algebraic: isVariant; + static if (isVariant!T) + private enum _alloc_ = true; + else + private enum _alloc_ = false; + } + + static if (_alloc_) + { + /// + private T* _value; + /// + ref inout(T) value() inout @property + { + return *_value; + } + + /// + ref T value(T value) @property + { + if (_value is null) + { + _value = new T; + import core.lifetime: move; + *_value = move(value); + } + return *_value; + } + + /// + bool opEquals(scope const AnnotatedOnce rhs) scope const + { + return annotation == rhs.annotation && value == rhs.value; + } + } + else + { + /// + T value; + } + + + /++ + Params: + annotation = non-empty array of annotation + args = arguments to construct value with + +/ + this(Args...)(string annotation, Args args) @safe pure { + import core.lifetime: forward; + this.annotation = annotation; + static if (_alloc_) + this._value = new T(forward!args); + else + static if (__traits(compiles, value = args)) + this.value = args; + else + static if (is(T == class)) + this.value = new T(forward!args); + else + this.value = T(forward!args); + } + + // private alias E = .basicElementType!T; + + import std.traits: isAssociativeArray, isAggregateType; + // static if (!isAssociativeArray!E && (!isAggregateType!E || __traits(hasMember, E, "opCmp"))) + /// + int opCmp()(scope const typeof(this) rhs) scope const @system pure nothrow @nogc + { + if (auto d = __cmp(annotation, rhs.annotation)) + return d; + + static if (__traits(compiles, __cmp(value, rhs.value))) + return __cmp(value, rhs.value); + else + static if (__traits(hasMember, value, "opCmp") && !is(T[i] == U*, U)) + return value.opCmp(rhs.value); + else + return value < rhs.value ? -1 : value > rhs.value ? +1 : 0; + } +} + +/// +version(mir_test) +unittest +{ + auto annotation = "annotation"; + static struct S {double x;} + auto as = AnnotatedOnce!S(annotation, 5); + assert(as.annotation == annotation); + assert(as.value.x == 5); + + static struct C {double x;} + auto ac = AnnotatedOnce!S(annotation, 5); + assert(ac.annotation == annotation); + assert(ac.value.x == 5); +} + +/// +version(mir_test) +unittest +{ + import mir.algebraic; + auto annotation = "annotation"; + static struct S {double x;} + auto as = AnnotatedOnce!(Variant!S)(annotation, 5); + assert(as.annotation == annotation); + assert(as.value.x == 5); + + static struct C {double x;} + auto ac = AnnotatedOnce!(Variant!S)(annotation, 5); + assert(ac.annotation == annotation); + assert(ac.value.x == 5); +} diff --git a/source/mir/appender.d b/source/mir/appender.d new file mode 100644 index 00000000..605c1280 --- /dev/null +++ b/source/mir/appender.d @@ -0,0 +1,406 @@ +/++ +$(H1 Scoped Buffer) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki ++/ +module mir.appender; + +// import std.traits: isAssignable, hasElaborateDestructorhasElaborateCopyConstructor, hasElaborateAssign; +import mir.conv: _mir_destroy = xdestroy; + +private extern(C) @system nothrow @nogc pure void* memcpy(scope void* s1, scope const void* s2, size_t n); + + +/++ +The buffer uses stack memory and C Runtime to allocate temporal memory. + +Shouldn't store references to GC allocated data. ++/ +struct ScopedBuffer(T, size_t bytes = 4096) + if (bytes && T.sizeof <= bytes) +{ + import std.traits: Unqual, isMutable, isIterable, hasElaborateAssign, isAssignable, isArray; + import mir.primitives: hasLength; + import mir.conv: emplaceRef; + + private enum size_t _bufferLength = bytes / T.sizeof + (bytes % T.sizeof != 0); + private T[] _buffer; + size_t _currentLength; + + version (mir_secure_memory) + private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload; + else + private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload = void; + + private ref inout(T[_bufferLength]) _scopeBuffer() inout @trusted scope + { + return *cast(inout(T[_bufferLength])*)&_scopeBufferPayload; + } + + /// Reserve `n` more elements. + void reserve(size_t n) @safe scope + { + prepare(n); + _currentLength -= n; + } + + /// Return a slice to `n` more elements. + T[] prepare(size_t n) @trusted scope + { + import mir.internal.memory: realloc, malloc; + _currentLength += n; + if (_buffer.length == 0) + { + if (_currentLength <= _bufferLength) + { + return _scopeBuffer[0 .. _currentLength]; + } + else + { + const newLen = _currentLength << 1; + if (auto p = malloc(T.sizeof * newLen)) + { + _buffer = (cast(T*)p)[0 .. newLen]; + } + else assert(0); + version (mir_secure_memory) + { + (cast(ubyte[])_buffer)[] = 0; + } + memcpy(cast(void*)_buffer.ptr, _scopeBuffer.ptr, T.sizeof * (_currentLength - n)); + } + } + else + if (_currentLength > _buffer.length) + { + const newLen = _currentLength << 1; + if (auto p = realloc(cast(void*)_buffer.ptr, T.sizeof * newLen)) + { + _buffer = (cast(T*)p)[0 .. newLen]; + } + else assert(0); + version (mir_secure_memory) + { + (cast(ubyte[])_buffer[_currentLength .. $])[] = 0; + } + } + return _buffer[0 .. _currentLength]; + } + + static if (isAssignable!(T, const T)) + private alias R = const T; + else + private alias R = T; + + /// Copy constructor is enabled only if `T` is mutable type without eleborate assign. + static if (isMutable!T && !hasElaborateAssign!T) + this(this) + { + import mir.internal.memory: malloc; + if (_buffer.ptr) + { + typeof(_buffer) buffer; + if (auto p = malloc(T.sizeof * _buffer.length)) + { + buffer = (cast(T*)p)[0 .. T.sizeof * _buffer.length]; + } + else assert(0); + version (mir_secure_memory) + { + (cast(ubyte[])buffer)[] = 0; + } + buffer[0 .. _currentLength] = _buffer[0 .. _currentLength]; + _buffer = buffer; + } + } + else + @disable this(this); + + /// + ~this() + { + import mir.internal.memory: free; + data._mir_destroy; + version(mir_secure_memory) + _currentLength = 0; + (() @trusted { if (_buffer.ptr) free(cast(void*)_buffer.ptr); })(); + } + + /// + void shrinkTo(size_t length) + { + assert(length <= _currentLength); + data[length .. _currentLength]._mir_destroy; + _currentLength = length; + } + + /// + size_t length() scope const @property + { + return _currentLength; + } + + /// + void popBackN(size_t n) + { + sizediff_t t = _currentLength - n; + if (t < 0) + assert(0, "ScopedBffer.popBackN: n is too large."); + data[t .. _currentLength]._mir_destroy; + _currentLength = t; + } + + /// + void put(T e) @safe scope + { + auto cl = _currentLength; + auto d = ()@trusted {return prepare(1);} (); + static if (isMutable!T) + { + import core.lifetime: moveEmplace; + ()@trusted{moveEmplace(e, d[cl]);}(); + } + else + { + emplaceRef!(Unqual!T)(d[cl], e); + } + } + + static if (T.sizeof > 8 || hasElaborateAssign!T) + /// + void put(ref R e) scope + { + auto cl = _currentLength; + auto d = ()@trusted {return prepare(1);} (); + emplaceRef!(Unqual!T)(d[cl], e); + } + + static if (!hasElaborateAssign!T) + /// + void put(scope R[] e) scope + { + auto cl = _currentLength; + auto d = ()@trusted {return prepare(e.length);} (); + if (!__ctfe) + (()@trusted=>memcpy(cast(void*)(d.ptr + cl), e.ptr, e.length * T.sizeof))(); + else + static if (isMutable!T) + (()@trusted=> d[cl .. cl + e.length] = e)(); + else + assert(0); + } + + /// + void put(Iterable)(Iterable range) scope + if (isIterable!Iterable && !__traits(isStaticArray, Iterable) && (!isArray!Iterable || hasElaborateAssign!T)) + { + static if (hasLength!Iterable) + { + auto cl = _currentLength; + auto d = ()@trusted {return prepare(range.length);} (); + foreach(ref e; range) + emplaceRef!(Unqual!T)(d[cl++], e); + assert(_currentLength == cl); + } + else + { + foreach(ref e; range) + put(e); + } + } + + /// + alias opOpAssign(string op : "~") = put; + + /// + void reset() @trusted scope nothrow + { + this.__dtor; + _currentLength = 0; + _buffer = null; + } + + /// + void initialize() @system scope nothrow @nogc + { + _currentLength = 0; + _buffer = null; + } + + /// + inout(T)[] data() inout @property @trusted scope return + { + return _buffer.length ? _buffer[0 .. _currentLength] : _scopeBuffer[0 .. _currentLength]; + } + + /++ + Copies data into an array of the same length using `memcpy` C routine. + Shrinks the length to `0`. + +/ + void moveDataAndEmplaceTo(T[] array) @system + in { + assert(array.length == _currentLength); + } + do { + memcpy(cast(void*)array.ptr, data.ptr, _currentLength * T.sizeof); + _currentLength = 0; + } +} + +/// ditto +auto scopedBuffer(T, size_t bytes = 4096)() @trusted +{ + ScopedBuffer!(T, bytes) buffer = void; + buffer.initialize; + return buffer; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto buf = scopedBuffer!char; + buf.put('c'); + buf.put("str"); + assert(buf.data == "cstr"); + + buf.popBackN(2); + assert(buf.data == "cs"); +} + +/// immutable +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto buf = scopedBuffer!(immutable char); + buf.put('c'); + buf.put("str"); + assert(buf.data == "cstr"); + + buf.popBackN(2); + assert(buf.data == "cs"); +} + +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto buf = scopedBuffer!(char, 3); + buf.put('c'); + buf.put("str"); + assert(buf.data == "cstr"); + + buf.popBackN(2); + assert(buf.data == "cs"); +} + +@safe pure nothrow @nogc +version (mir_test) unittest +{ + alias T = char; + const n = 3; + + auto buf = scopedBuffer!(T, n * T.sizeof); + assert(buf._scopeBuffer.length == n); // stack + assert(buf._buffer.length == 0); // unset + + buf.reserve(n + 1); // transition to heap + assert(buf._buffer.length >= n + 1); // heap + + buf ~= 'c'; + buf ~= "str"; + assert(buf.data == "cstr"); + + buf.popBackN(2); + assert(buf.data == "cs"); +} + +/// +struct UnsafeArrayBuffer(T) +{ + import std.traits: isImplicitlyConvertible; + + /// + T[] buffer; + /// + size_t length; + + /// + void put(T a) + { + import core.lifetime: move; + assert(length < buffer.length); + buffer[length++] = move(a); + } + + static if (isImplicitlyConvertible!(const T, T)) + private alias E = const T; + else + private alias E = T; + + /// + void put(E[] a) + { + import core.lifetime: move; + assert(buffer.length >= a.length + length); + buffer[length .. length + a.length] = a; + length += a.length; + } + + /// + inout(T)[] data() inout @property @safe scope + { + return buffer[0 .. length]; + } + + /// + void popBackN(size_t n) + { + sizediff_t t = length - n; + if (t < 0) + assert(0, "UnsafeBuffer.popBackN: n is too large."); + buffer[t .. length]._mir_destroy; + length = t; + } +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + char[4] array; + auto buf = UnsafeArrayBuffer!char(array); + buf.put('c'); + buf.put("str"); + assert(buf.data == "cstr"); + + buf.popBackN(2); + assert(buf.data == "cs"); +} + +version(mir_bignum_test) // for DIP1000 +@safe pure nothrow +unittest +{ + import mir.conv: to; + import mir.algebraic : Algebraic; + static struct S + { + @safe pure nothrow @nogc: + @property string toString() scope const + { + return "_"; + } + } + Algebraic!(int, string, double) x; + x = 42; + auto s = x.to!string; + assert(s == "42"); + x = "abc"; + assert(x.to!string == "abc"); + x = 42.0; + assert(x.to!string == "42.0"); + Algebraic!S y; + y = S(); + assert(y.to!string == "_"); +} diff --git a/source/mir/array/allocation.d b/source/mir/array/allocation.d index 48729ff3..f1ef2b23 100644 --- a/source/mir/array/allocation.d +++ b/source/mir/array/allocation.d @@ -12,11 +12,11 @@ $(TR $(TH Function Name) $(TH Description) )) ) -Copyright: Copyright Andrei Alexandrescu 2008-, Jonathan M Davis 2011-, and Ilya Yaroshenko (Mir rework) 2018- +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) -Authors: $(HTTP erdani.org, Andrei Alexandrescu) and Jonathan M Davis +Authors: $(HTTP erdani.org, Andrei Alexandrescu) and Jonathan M Davis Source: $(PHOBOSSRC std/_array.d) */ @@ -24,9 +24,7 @@ module mir.array.allocation; import mir.functional; import mir.primitives; - import std.traits; -import std.range.primitives: isInfinite, isInputRange, ElementType; /** * Allocates an array and initializes it with copies of the elements @@ -40,7 +38,7 @@ import std.range.primitives: isInfinite, isInputRange, ElementType; * allocated and initialized array */ auto array(Range)(Range r) -if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isStaticArray!Range || isPointer!Range && isIterable!(PointerTarget!Range)) +if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !__traits(isStaticArray, Range) || isPointer!Range && (isInputRange!(PointerTarget!Range) || isIterable!(PointerTarget!Range))) { static if (isIterable!Range) alias E = ForeachType!Range; @@ -102,9 +100,10 @@ if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isStaticAr else { auto it = result; - foreach(ref f; r) + foreach (f; r) { - emplaceRef!E(it[0], f); + import mir.functional: forward; + emplaceRef!E(it[0], forward!f); it = it[1 .. $]; } } @@ -113,18 +112,24 @@ if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isStaticAr } else { - import std.array: appender; - auto a = appender!(E[])(); + import std.array: std_appender = appender; + + auto a = std_appender!(E[]); + static if (isInputRange!Range) for (; !r.empty; r.popFront) a.put(r.front); else static if (isPointer!Range) + { foreach (e; *r) - a.put(e); + a.put(forward!e); + } else + { foreach (e; r) - a.put(e); + a.put(forward!e); + } return a.data; } } @@ -215,7 +220,7 @@ if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isStaticAr // @system due to array!string import std.conv : to; - static struct TestArray { int x; string toString() @safe { return to!string(x); } } + static struct TestArray { int x; string toString() scope const @safe { return to!string(x); } } static struct OpAssign { diff --git a/source/mir/base64.d b/source/mir/base64.d new file mode 100644 index 00000000..7743d22d --- /dev/null +++ b/source/mir/base64.d @@ -0,0 +1,461 @@ +/++ +$(H1 @nogc Simple Base64 parsing) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Harrison Ford +Copyright: 2021 Harrison Ford, Symmetry Investments ++/ +module mir.base64; + +import mir.exception: toMutable; + +package static immutable base64DecodeInvalidCharMsg = "base64: Invalid character encountered."; +package static immutable base64DecodeInvalidLenMsg = "Cannot decode a buffer with given length (not a multiple of 4, missing padding?)"; +version(D_Exceptions) { + package static immutable base64DecodeInvalidCharException = new Exception(base64DecodeInvalidCharMsg); + package static immutable base64DecodeInvalidLenException = new Exception(base64DecodeInvalidLenMsg); +} + +// NOTE: I do not know if this would work on big-endian systems. +// Needs further testing to figure out if it *does* work on them. + +// Technique borrowed from http://0x80.pl/notesen/2016-01-12-sse-base64-encoding.html#branchless-code-for-lookup-table +private char lookup_encoding(ubyte i, char plusChar = '+', char slashChar = '/') @safe @nogc pure { + assert(i < 64); + + ubyte shift; + + if (i < 26) + { + // range A-Z + shift = 'A'; + } + else if (i >= 26 && i < 52) + { + // range a-z + shift = 'a' - 26; + } + else if (i >= 52 && i < 62) + { + // range 0-9 + shift = cast(ubyte)('0' - 52); + } + else if (i == 62) + { + // character plus + shift = cast(ubyte)(plusChar - 62); + } + else if (i == 63) + { + // character slash + shift = cast(ubyte)(slashChar - 63); + } + + return cast(char)(i + shift); +} + +// Do the inverse of above (convert an ASCII value into the Base64 character set) +private ubyte lookup_decoding(char i, char plusChar = '+', char slashChar = '/') @safe @nogc pure +{ + // Branching bad, but this isn't performance sensitive + if (i <= 'Z' && i >= 'A') { + return cast(ubyte)(i - 'A'); + } + else if (i <= 'z' && i >= 'a') { + return cast(ubyte)(i - 'a' + 26); + } + else if (i <= '9' && i >= '0') { + return cast(ubyte)(i - '0' + 52); + } + else if (i == plusChar) { + return 62; + } + else if (i == slashChar) { + return 63; + } + // Just return 0 for padding, + // as it typically means nothing. + else if (i == '=') { + return 0; + } + else { + version(D_Exceptions) { + throw base64DecodeInvalidCharException.toMutable; + } else { + assert(0, base64DecodeInvalidCharMsg); + } + } + +} + +/++ +Decode a Base64 encoded value, returning the buffer. ++/ +ubyte[] decodeBase64(scope const(char)[] data, char plusChar = '+', char slashChar = '/') @safe pure +{ + import mir.appender : scopedBuffer; + auto app = scopedBuffer!ubyte; + decodeBase64(data, app, plusChar, slashChar); + return app.data.dup; +} + +/++ +Decode a Base64 encoded value, placing the result onto an Appender. ++/ +void decodeBase64(Appender)(scope const(char)[] data, + scope ref Appender appender, + char plusChar = '+', + char slashChar = '/') @safe pure +{ + // We expect data should be well-formed (with padding), + // so we should throw if it is not well-formed. + if (data.length % 4 != 0) + { + version(D_Exceptions) { + throw base64DecodeInvalidLenException.toMutable; + } else { + assert(0, base64DecodeInvalidLenMsg); + } + } + + ubyte[3] decodedByteGroup; + ubyte sz = 0; + + // We can't use mir.ndslice.chunk.chunks here, as it violates + // the scope requirements. + for (size_t i = 0; i < data.length; i += 4) + { + auto group = data[i .. (i + 4)]; + + ubyte[4] decodedBytes; + decodedBytes[0] = lookup_decoding(group[0], plusChar, slashChar); + decodedBytes[1] = lookup_decoding(group[1], plusChar, slashChar); + + uint transformed_group = (decodedBytes[0] << 26) | (decodedBytes[1] << 20); + + // According to RFC4648 Section 3.3, we don't have to accept extra padding characters, + // and we can safely throw (and stay within spec). + // x=== is also invalid, so we can just throw on that here. + if (group[0] == '=' || group[1] == '=') + { + version(D_Exceptions) + throw base64DecodeInvalidCharException.toMutable; + else + assert(0, base64DecodeInvalidCharMsg); + } + + // xx=(=)? + if (group[2] == '=') + { + // If we are not at the end of a string, according to RFC4648, + // we can safely treat a padding character as "non-alphabet data", + // and as such, we should throw. See RFC4648 Section 3.3 for more information + if ((i / 4) != ((data.length / 4) - 1)) + { + version(D_Exceptions) + throw base64DecodeInvalidCharException.toMutable; + else + assert(0, base64DecodeInvalidCharMsg); + } + + if (group[3] == '=') + { + // xx== + sz = 1; + } + // xx=x (invalid) + // Padding should not be in the middle of a chunk + else + { + version(D_Exceptions) + throw base64DecodeInvalidCharException.toMutable; + else + assert(0, base64DecodeInvalidCharMsg); + } + } + // xxx= + else if (group[3] == '=') + { + // If we are not at the end of a string, according to RFC4648, + // we can safely treat a padding character as "non-alphabet data", + // and as such, we should throw. See RFC4648 Section 3.3 for more information + if ((i / 4) != ((data.length / 4) - 1)) + { + version(D_Exceptions) + throw base64DecodeInvalidCharException.toMutable; + else + assert(0, base64DecodeInvalidCharMsg); + } + + decodedBytes[2] = lookup_decoding(group[2], plusChar, slashChar); + transformed_group |= (decodedBytes[2] << 14); + sz = 2; + } + // xxxx + else + { + decodedBytes[2] = lookup_decoding(group[2], plusChar, slashChar); + decodedBytes[3] = lookup_decoding(group[3], plusChar, slashChar); + transformed_group |= ((decodedBytes[2] << 14) | (decodedBytes[3] << 8)); + sz = 3; + } + + decodedByteGroup[0] = (transformed_group >> 24) & 0xff; + decodedByteGroup[1] = (transformed_group >> 16) & 0xff; + decodedByteGroup[2] = (transformed_group >> 8) & 0xff; + + // Only emit the transformed bytes that we got data for. + appender.put(decodedByteGroup[0 .. sz]); + } +} + +/// Test decoding of data which has a length which can be +/// cleanly decoded. +version(mir_test) +@safe pure unittest +{ + { + enum data = "QUJD"; + assert(data.decodeBase64 == "ABC"); + } + + { + enum data = "QQ=="; + assert(data.decodeBase64 == "A"); + } + + { + enum data = "YSBiIGMgZCBlIGYgZyBoIGkgaiBrIGwgbSBuIG8gcCBxIHIgcyB0IHUgdiB3IHggeSB6"; + assert(data.decodeBase64 == "a b c d e f g h i j k l m n o p q r s t u v w x y z"); + } + + { + enum data = "LCAuIDsgLyBbICcgXSBcID0gLSAwIDkgOCA3IDYgNSA0IDMgMiAxIGAgfiAhIEAgIyAkICUgXiAmICogKCApIF8gKyB8IDogPCA+ID8="; + assert(data.decodeBase64 == ", . ; / [ ' ] \\ = - 0 9 8 7 6 5 4 3 2 1 ` ~ ! @ # $ % ^ & * ( ) _ + | : < > ?"); + } + + { + enum data = "AAA="; + assert(data.decodeBase64 == "\x00\x00"); + } + + { + enum data = "AAAABBCC"; + assert(data.decodeBase64 == "\x00\x00\x00\x04\x10\x82"); + } + + { + enum data = "AA=="; + assert(data.decodeBase64 == "\x00"); + } + + { + enum data = "AA/="; + assert(data.decodeBase64 == "\x00\x0f"); + } +} + +/// Test decoding invalid data +version(mir_test) +@safe pure unittest +{ + void testFail(const(char)[] input) @safe pure + { + bool thrown = false; + try { + ubyte[] decoded = input.decodeBase64; + } catch (Exception t) { + thrown = true; + } + + assert(thrown); + } + + testFail("===A"); + testFail("A="); + testFail("AA="); + testFail("A=AA"); + testFail("AA=A"); + testFail("AA=A===="); + testFail("=AAA"); + testFail("AAA=QUJD"); + // This fails because we don't allow extra padding (than what is necessary) + testFail("AA======"); + // This fails because we don't allow padding before the end of the string (otherwise we'd have a side-channel) + testFail("QU==QUJD"); + testFail("QU======QUJD"); + // Invalid data that's out of the alphabet + testFail("!@##@@!@"); +} + +/++ +Encode a ubyte array as Base64, returning the encoded value. ++/ +string encodeBase64(scope const(ubyte)[] buf, char plusChar = '+', char slashChar = '/') @safe pure +{ + import mir.appender : scopedBuffer; + auto app = scopedBuffer!char; + encodeBase64(buf, app, plusChar, slashChar); + return app.data.idup; +} + +/++ +Encode a ubyte array as Base64, placing the result onto an Appender. ++/ +void encodeBase64(Appender)(scope const(ubyte)[] input, + scope ref Appender appender, + char plusChar = '+', + char slashChar = '/') @safe pure +{ + import core.bitop : bswap; + import mir.ndslice.topology : bytegroup, map; + // Slice our input array so that n % 3 == 0 (we have a multiple of 3) + // If we have less then 3, then this is effectively a no-op (will result in a 0-length slice) + char[4] encodedByteGroup; + const(ubyte)[] window = input[0 .. input.length - (input.length % 3)]; + foreach(group; window.bytegroup!(3, uint).map!bswap) { + const(ubyte) a = (group >> 26) & 0x3f; + const(ubyte) b = (group >> 20) & 0x3f; + const(ubyte) c = (group >> 14) & 0x3f; + const(ubyte) d = (group >> 8) & 0x3f; + + encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); + encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); + encodedByteGroup[2] = c.lookup_encoding(plusChar, slashChar); + encodedByteGroup[3] = d.lookup_encoding(plusChar, slashChar); + appender.put(encodedByteGroup[]); + } + + // If it's a clean multiple of 3, then it requires no padding. + // If not, then we need to add padding. + if (input.length % 3 != 0) + { + window = input[window.length .. input.length]; + + uint group = (window[0] << 24); + + if (window.length == 1) { + const(ubyte) a = (group >> 26) & 0x3f; + const(ubyte) b = (group >> 20) & 0x3f; + encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); + encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); + encodedByteGroup[2] = '='; + encodedByteGroup[3] = '='; + } + else { + // Just in case + assert(window.length == 2); + + group |= (window[1] << 16); + const(ubyte) a = (group >> 26) & 0x3f; + const(ubyte) b = (group >> 20) & 0x3f; + const(ubyte) c = (group >> 14) & 0x3f; + encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); + encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); + encodedByteGroup[2] = c.lookup_encoding(plusChar, slashChar); + encodedByteGroup[3] = '='; + } + + appender.put(encodedByteGroup[]); + } +} + +/// Test encoding of data which has a length that can be cleanly +/// encoded. +version(mir_test) +@safe pure unittest +{ + // 3 bytes + { + enum data = cast(immutable(ubyte)[])"ABC"; + assert(data.encodeBase64 == "QUJD"); + } + + // 6 bytes + { + enum data = cast(immutable(ubyte)[])"ABCDEF"; + assert(data.encodeBase64 == "QUJDREVG"); + } + + // 9 bytes + { + enum data = cast(immutable(ubyte)[])"ABCDEFGHI"; + assert(data.encodeBase64 == "QUJDREVGR0hJ"); + } + + // 12 bytes + { + enum data = cast(immutable(ubyte)[])"ABCDEFGHIJKL"; + assert(data.encodeBase64 == "QUJDREVGR0hJSktM"); + } +} + +/// Test encoding of data which has a length which CANNOT be cleanly encoded. +/// This typically means that there's padding. +version(mir_test) +@safe pure unittest +{ + // 1 byte + { + enum data = cast(immutable(ubyte)[])"A"; + assert(data.encodeBase64 == "QQ=="); + } + // 2 bytes + { + enum data = cast(immutable(ubyte)[])"AB"; + assert(data.encodeBase64 == "QUI="); + } + // 2 bytes + { + enum data = [0xFF, 0xFF]; + assert(data.encodeBase64 == "//8="); + } + // 4 bytes + { + enum data = [0xDE, 0xAD, 0xBA, 0xBE]; + assert(data.encodeBase64 == "3q26vg=="); + } + // 37 bytes + { + enum data = cast(immutable(ubyte)[])"A Very Very Very Very Large Test Blob"; + assert(data.encodeBase64 == "QSBWZXJ5IFZlcnkgVmVyeSBWZXJ5IExhcmdlIFRlc3QgQmxvYg=="); + } +} + +/// Test nogc encoding +version(mir_test) +@safe pure @nogc unittest +{ + import mir.appender : scopedBuffer; + + { + enum data = cast(immutable(ubyte)[])"A Very Very Very Very Large Test Blob"; + auto appender = scopedBuffer!char(); + data.encodeBase64(appender); + assert(appender.data == "QSBWZXJ5IFZlcnkgVmVyeSBWZXJ5IExhcmdlIFRlc3QgQmxvYg=="); + } + + { + enum data = cast(immutable(ubyte)[])"abc123!?$*&()'-=@~"; + auto appender = scopedBuffer!char(); + data.encodeBase64(appender); + assert(appender.data == "YWJjMTIzIT8kKiYoKSctPUB+"); + } +} + +/// Make sure we can decode what we encode. +version(mir_test) +@safe pure unittest +{ + // Test an example string + { + enum data = cast(immutable(ubyte)[])"abc123!?$*&()'-=@~"; + assert(data.encodeBase64.decodeBase64 == data); + } + // Test an example from Ion data + { + enum data = cast(immutable(ubyte)[])"a b c d e f g h i j k l m n o p q r s t u v w x y z"; + assert(data.encodeBase64.decodeBase64 == data); + } +} + diff --git a/source/mir/bignum/decimal.d b/source/mir/bignum/decimal.d new file mode 100644 index 00000000..b98fb63c --- /dev/null +++ b/source/mir/bignum/decimal.d @@ -0,0 +1,797 @@ +/++ +Stack-allocated decimal type. + +Note: + The module doesn't provide full arithmetic API for now. ++/ +module mir.bignum.decimal; + +import mir.serde: serdeProxy, serdeScoped; +import std.traits: isSomeChar; +/// +public import mir.parse: DecimalExponentKey; +import mir.bignum.low_level_view: ceilLog10Exp2; + +private enum expBufferLength = 2 + ceilLog10Exp2(ulong.sizeof * 8); +private static immutable C[9] zerosImpl(C) = "0.00000.0"; + +/++ +Stack-allocated decimal type. +Params: + size64 = count of 64bit words in coefficient ++/ +@serdeScoped @serdeProxy!(const(char)[]) +struct Decimal(uint size64) + if (size64 && size64 <= ushort.max) +{ + import mir.format: NumericSpec; + import mir.bignum.integer; + import mir.bignum.low_level_view; + import std.traits: isMutable, isFloatingPoint; + + /// + long exponent; + /// + BigInt!size64 coefficient; + + /// + void toString(C = char, W)(ref scope W w, NumericSpec spec = NumericSpec.init) const scope + if(isSomeChar!C && isMutable!C) + { + scope C[] _buffer; + if (false) w.put(_buffer); + () @trusted { + assert(spec.format == NumericSpec.Format.exponent || spec.format == NumericSpec.Format.human); + import mir.utility: _expect; + // handle special values + if (_expect(exponent == exponent.max, false)) + { + static immutable C[3] nan = "nan"; + static immutable C[4] ninf = "-inf"; + static immutable C[4] pinf = "+inf"; + w.put(coefficient.length == 0 ? coefficient.sign ? ninf[] : pinf[] : nan[]); + return; + } + + C[coefficientBufferLength + 16] buffer0 = void; + auto buffer = buffer0[0 .. $ - 16]; + + size_t coefficientLength; + static if (size_t.sizeof == 8) + { + if (__ctfe) + { + uint[coefficient.data.length * 2] data; + foreach (i; 0 .. coefficient.length) + { + auto l = cast(uint)coefficient.data[i]; + auto h = cast(uint)(coefficient.data[i] >> 32); + data[i * 2 + 0] = l; + data[i * 2 + 1] = h; + } + auto work = BigUIntView!uint(data); + work = work.topLeastSignificantPart(coefficient.length * 2).normalized; + coefficientLength = work.toStringImpl(buffer); + } + else + { + BigInt!size64 work = void; + work = coefficient; + coefficientLength = work.view.unsigned.toStringImpl(buffer); + } + } + else + { + BigInt!size64 work = void; + work = coefficient; + coefficientLength = work.view.unsigned.toStringImpl(buffer); + } + + C[1] sign = coefficient.sign ? "-" : "+"; + bool addSign = coefficient.sign || spec.plus; + long s = this.exponent + coefficientLength; + + alias zeros = zerosImpl!C; + + if (spec.format == NumericSpec.Format.human) + { + if (!spec.separatorCount) + spec.separatorCount = 3; + void putL(scope const(C)[] b) + { + assert(b.length); + + if (addSign) + w.put(sign[]); + + auto r = b.length % spec.separatorCount; + if (r == 0) + r = spec.separatorCount; + C[1] sep = spec.separatorChar; + goto LS; + do + { + w.put(sep[]); + LS: + w.put(b[0 .. r]); + b = b[r .. $]; + r = spec.separatorCount; + } + while(b.length); + } + + // try print decimal form without exponent + // up to 6 digits exluding leading 0. or final .0 + if (s <= 0) + { + //0.001.... + //0.0001 + //0.00001 + //0.000001 + //If separatorChar is defined lets be less greed for space. + if (this.exponent >= -6 || s >= -2 - (spec.separatorChar != 0) * 3) + { + if (addSign) + w.put(sign[]); + w.put(zeros[0 .. cast(sizediff_t)(-s + 2)]); + w.put(buffer[$ - coefficientLength .. $]); + return; + } + } + else + if (this.exponent >= 0) + { + ///dddddd.0 + if (!spec.separatorChar) + { + if (s <= 6) + { + buffer[$ - coefficientLength - 1] = sign[0]; + w.put(buffer[$ - coefficientLength - addSign .. $]); + w.put(zeros[($ - (cast(sizediff_t)this.exponent + 2)) .. $]); + return; + } + } + else + { + if (s <= 12) + { + buffer0[$ - 16 .. $] = '0'; + putL(buffer0[$ - coefficientLength - 16 .. $ - 16 + cast(sizediff_t)this.exponent]); + w.put(zeros[$ - 2 .. $]); + return; + } + } + } + else + { + ///dddddd.0 + if (!spec.separatorChar) + { + ///dddddd.d.... + if (s <= 6 || coefficientLength <= 6) + { + buffer[$ - coefficientLength - 1] = sign[0]; + w.put(buffer[$ - coefficientLength - addSign .. $ - coefficientLength + cast(sizediff_t)s]); + T2: + buffer[$ - coefficientLength + cast(sizediff_t)s - 1] = '.'; + w.put(buffer[$ - coefficientLength + cast(sizediff_t)s - 1 .. $]); + return; + } + } + else + { + if (s <= 12 || coefficientLength <= 12) + { + putL(buffer[$ - coefficientLength .. $ - coefficientLength + cast(sizediff_t)s]); + goto T2; + } + } + } + } + + assert(coefficientLength); + + long exponent = s - 1; + + if (coefficientLength > 1) + { + auto c = buffer[$ - coefficientLength]; + buffer[$ - coefficientLength] = '.'; + buffer[$ - ++coefficientLength] = c; + } + + buffer[$ - coefficientLength - 1] = sign[0]; + w.put(buffer[$ - coefficientLength - addSign .. $]); + + import mir.format_impl: printSignedToTail; + + static if (exponent.sizeof == 8) + enum N = 21; + else + enum N = 11; + + // prints e+/-exponent + auto expLength = printSignedToTail(exponent, buffer0[$ - N - 16 .. $ - 16], '+'); + buffer[$ - ++expLength] = spec.exponentChar; + w.put(buffer[$ - expLength .. $]); + } (); + } + +@safe: + + /// + DecimalView!size_t view() return scope + { + return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); + } + + /// ditto + DecimalView!(const size_t) view() const return scope + { + return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); + } + + /// + this(C)(scope const(C)[] str, int exponentShift = 0) @safe pure @nogc + if (isSomeChar!C) + { + DecimalExponentKey key; + if (fromStringImpl(str, key, exponentShift) || key == DecimalExponentKey.nan || key == DecimalExponentKey.infinity) + return; + static if (__traits(compiles, () @nogc { throw new Exception("Can't parse Decimal."); })) + { + import mir.exception: MirException; + throw new MirException("Can't parse Decimal!" ~ size64.stringof ~ " from string `", str , "`"); + } + else + { + static immutable exception = new Exception("Can't parse Decimal!" ~ size64.stringof ~ "."); + { import mir.exception : toMutable; throw exception.toMutable; } + } + } + + /++ + Constructs Decimal from the floating point number using the $(HTTPS github.com/ulfjack/ryu, Ryu algorithm). + + The number is the shortest decimal representation that being converted back would result the same floating-point number. + +/ + this(T)(const T x) + if (isFloatingPoint!T && size64 >= 1 + (T.mant_dig >= 64)) + { + import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; + this = genericBinaryToDecimal(x); + } + + /// + ref opAssign(uint rhsMaxSize64)(auto ref scope const Decimal!rhsMaxSize64 rhs) return + if (rhsMaxSize64 < size64) + { + this.exponent = rhs.exponent; + this.coefficient = rhs.coefficient; + return this; + } + + /++ + Handle thousand separators for non exponential numbers. + + Returns: false in case of overflow or incorrect string. + +/ + bool fromStringWithThousandsSeparatorImpl(C, + bool allowSpecialValues = true, + bool allowStartingPlus = true, + bool allowLeadingZeros = true, + )( + scope const(C)[] str, + const C thousandsSeparator, + const C fractionSeparator, + out DecimalExponentKey key, + int exponentShift = 0, + ) @trusted + if (isSomeChar!C) + { + import mir.algorithm.iteration: find; + import mir.format: stringBuf; + import mir.ndslice.chunks: chunks; + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: retro; + + auto buffer = stringBuf; + assert(thousandsSeparator != fractionSeparator); + if (str.length && (str[0] == '+' || str[0] == '-')) + { + buffer.put(cast(char)str[0]); + str = str[1 .. $]; + } + auto integer = str[0 .. $ - str.find!(a => a == fractionSeparator)]; + if (integer.length % 4 == 0) + return false; + foreach_reverse (chunk; integer.sliced.retro.chunks(4)) + { + auto s = chunk.retro.field; + if (s.length == 4) + { + if (s[0] != thousandsSeparator) + return false; + s = s[1 .. $]; + } + do + { + if (s[0] < '0' || s[0] > '9') + return false; + buffer.put(cast(char)s[0]); + s = s[1 .. $]; + } + while(s.length); + } + if (str.length > integer.length) + { + buffer.put('.'); + str = str[integer.length + 1 .. $]; + if (str.length == 0) + return false; + do + { + buffer.put(cast(char)str[0]); + str = str[1 .. $]; + } + while(str.length); + } + return fromStringImpl!(char, + allowSpecialValues, + false, // allowDotOnBounds + false, // allowDExponent + allowStartingPlus, + false, // allowUnderscores + allowLeadingZeros, // allowLeadingZeros + false, // allowExponent + false, // checkEmpty + )(buffer.data, key, exponentShift); + } + + /++ + Returns: false in case of overflow or incorrect string. + +/ + bool fromStringImpl(C, + bool allowSpecialValues = true, + bool allowDotOnBounds = true, + bool allowDExponent = true, + bool allowStartingPlus = true, + bool allowUnderscores = true, + bool allowLeadingZeros = true, + bool allowExponent = true, + bool checkEmpty = true, + ) + (scope const(C)[] str, out DecimalExponentKey key, int exponentShift = 0) + scope @trusted pure @nogc nothrow + if (isSomeChar!C) + { + import mir.bignum.low_level_view: DecimalView, BigUIntView, MaxWordPow10; + auto work = DecimalView!size_t(false, 0, BigUIntView!size_t(coefficient.data)); + auto ret = work.fromStringImpl!(C, + allowSpecialValues, + allowDotOnBounds, + allowDExponent, + allowStartingPlus, + allowUnderscores, + allowLeadingZeros, + allowExponent, + checkEmpty, + )(str, key, exponentShift); + coefficient.length = cast(uint) work.coefficient.coefficients.length; + coefficient.sign = work.sign; + exponent = work.exponent; + return ret; + } + + private enum coefficientBufferLength = 2 + ceilLog10Exp2(coefficient.data.length * (size_t.sizeof * 8)); // including dot and sign + private enum eDecimalLength = coefficientBufferLength + expBufferLength; + + /// + immutable(C)[] toString(C = char)(NumericSpec spec = NumericSpec.init) const scope @safe pure nothrow + if(isSomeChar!C && isMutable!C) + { + import mir.appender: UnsafeArrayBuffer; + C[eDecimalLength] data = void; + auto buffer = UnsafeArrayBuffer!C(data); + toString(buffer, spec); + return buffer.data.idup; + } + + /++ + Mir parsing supports up-to quadruple precision. The conversion error is 0 ULP for normal numbers. + Subnormal numbers with an exponent greater than or equal to -512 have upper error bound equal to 1 ULP. +/ + T opCast(T, bool wordNormalized = true)() scope const + if (isFloatingPoint!T && isMutable!T) + { + return view.opCast!(T, wordNormalized); + } + + /// + bool isNaN() scope const @property + { + return exponent == exponent.max && coefficient.length; + } + + /// + bool isInfinity() scope const @property + { + return exponent == exponent.max && coefficient.length == 0; + } + + /// + bool isSpecial() scope const @property + { + return exponent == exponent.max; + } + + /// + ref opOpAssign(string op, size_t rhsMaxSize64)(ref const Decimal!rhsMaxSize64 rhs) @trusted pure return + if (op == "+" || op == "-") + { + import mir.utility: max; + BigInt!(max(rhsMaxSize64, size64, 256u)) rhsCopy = void; + BigIntView!(const size_t) rhsView; + auto expDiff = cast(sizediff_t) (exponent - rhs.exponent); + if (expDiff >= 0) + { + exponent = rhs.exponent; + coefficient.mulPow5(expDiff); + coefficient.opOpAssign!"<<"(expDiff); + rhsView = rhs.coefficient.view; + } + else + { + rhsCopy.copyFrom(rhs.coefficient.coefficients, rhs.coefficient.sign); + rhsCopy.mulPow5(-expDiff); + rhsCopy.opOpAssign!"<<"(-expDiff); + rhsView = rhsCopy.view; + } + coefficient.opOpAssign!op(rhsView); + return this; + } +} + +/// +version(mir_bignum_test) +@trusted pure nothrow @nogc +unittest +{ + import mir.test: should; + import mir.conv: to; + Decimal!128 decimal = void; + DecimalExponentKey key; + + assert(decimal.fromStringImpl("3.141592653589793378e-10", key)); + decimal.opCast!double.should == 0x1.596bf8ce7631ep-32; + key.should == DecimalExponentKey.e; +} + +/// +version(mir_bignum_test) +@safe pure nothrow @nogc +unittest +{ + import mir.conv: to; + Decimal!3 decimal; + DecimalExponentKey key; + + assert(decimal.fromStringImpl("0", key)); + assert(key == DecimalExponentKey.none); + assert(decimal.exponent == 0); + assert(decimal.coefficient.length == 0); + assert(!decimal.coefficient.sign); + assert(cast(double) decimal.coefficient == 0); + + assert(decimal.fromStringImpl("-0.0", key)); + assert(key == DecimalExponentKey.dot); + assert(decimal.exponent == -1); + assert(decimal.coefficient.length == 0); + assert(decimal.coefficient.sign); + assert(cast(double) decimal.coefficient == 0); + + assert(decimal.fromStringImpl("0e0", key)); + assert(key == DecimalExponentKey.e); + assert(decimal.exponent == 0); + assert(decimal.coefficient.length == 0); + assert(!decimal.coefficient.sign); + assert(cast(double) decimal.coefficient == 0); +} + +/// +version(mir_bignum_test) @safe pure @nogc unittest +{ + auto a = Decimal!1("777.7"); + auto b = Decimal!1("777"); + import mir.format; + assert(stringBuf() << cast(double)a - cast(double)b << getData == "0.7000000000000455"); + a -= b; + assert(stringBuf() << a << getData == "0.7"); + + a = Decimal!1("-777.7"); + b = Decimal!1("777"); + a += b; + assert(stringBuf() << a << getData == "-0.7"); + + a = Decimal!1("777.7"); + b = Decimal!1("-777"); + a += b; + assert(stringBuf() << a << getData == "0.7"); + + a = Decimal!1("777"); + b = Decimal!1("777.7"); + a -= b; + assert(stringBuf() << a << getData == "-0.7"); +} + +/// Check @nogc toString impl +version(mir_bignum_test) @safe pure @nogc unittest +{ + import mir.format: stringBuf; + auto str = "5.28238923728e-876543210"; + auto decimal = Decimal!1(str); + auto buffer = stringBuf; + buffer << decimal; + assert(buffer.data == str); +} + +/// +version(mir_bignum_test) +@safe pure @nogc unittest +{ + Decimal!4 i = "-0"; + + assert(i.view.coefficient.coefficients.length == 0); + assert(i.coefficient.view.unsigned.coefficients.length == 0); + assert(i.coefficient.view == 0L); + assert(cast(long) i.coefficient == 0); + assert(i.coefficient.sign); +} + +/// +version(mir_bignum_test) @safe pure unittest +{ + auto str = "-3.4010447314490204552169750449563978034784726557588085989975288830070948234680e-13245"; + auto decimal = Decimal!4(str); + assert(decimal.toString == str, decimal.toString); + + decimal = Decimal!4.init; + assert(decimal.toString == "0.0"); +} + +/// +version(mir_bignum_test) +@safe pure nothrow @nogc +unittest +{ + import mir.test: should; + + import mir.conv: to; + Decimal!3 decimal; + DecimalExponentKey key; + + // Check precise percentate parsing + assert(decimal.fromStringImpl("71.7", key, -2)); + key.should == DecimalExponentKey.dot; + // The result is exact value instead of 0.7170000000000001 = 71.7 / 100 + (cast(double) decimal).should == 0.717; + + assert(decimal.fromStringImpl("+0.334e-5"w, key)); + key.should == DecimalExponentKey.e; + (cast(double) decimal).should == 0.334e-5; + + assert(decimal.fromStringImpl("100_000_000"w, key)); + key.should == DecimalExponentKey.none; + (cast(double) decimal).should == 1e8; + + assert(decimal.fromStringImpl("-334D-5"d, key)); + key.should == DecimalExponentKey.D; + (cast(double) decimal).should == -334e-5; + + assert(decimal.fromStringImpl("2482734692817364218734682973648217364981273648923423", key)); + key.should == DecimalExponentKey.none; + (cast(double) decimal).should == 2482734692817364218734682973648217364981273648923423.0; + + assert(decimal.fromStringImpl(".023", key)); + key.should == DecimalExponentKey.dot; + (cast(double) decimal).should == .023; + + assert(decimal.fromStringImpl("0E100", key)); + key.should == DecimalExponentKey.E; + (cast(double) decimal).should == 0; + + foreach (str; ["-nan", "-NaN", "-NAN"]) + { + assert(decimal.fromStringImpl(str, key)); + assert(decimal.coefficient.length > 0); + assert(decimal.exponent == decimal.exponent.max); + assert(decimal.coefficient.sign); + key.should == DecimalExponentKey.nan; + auto nan = cast(double) decimal; + (cast(double) decimal).should == double.nan; + } + + foreach (str; ["inf", "Inf", "INF"]) + { + assert(decimal.fromStringImpl(str, key)); + assert(decimal.coefficient.length == 0); + assert(decimal.exponent == decimal.exponent.max); + assert(key == DecimalExponentKey.infinity); + (cast(double) decimal).should == double.infinity; + } + + assert(decimal.fromStringImpl("-inf", key)); + assert(decimal.coefficient.length == 0); + assert(decimal.exponent == decimal.exponent.max); + assert(key == DecimalExponentKey.infinity); + should(cast(double) decimal) == -double.infinity; + + assert(!decimal.fromStringImpl("3.3.4", key)); + assert(!decimal.fromStringImpl("3.4.", key)); + assert(decimal.fromStringImpl("4.", key)); + assert(!decimal.fromStringImpl(".", key)); + assert(decimal.fromStringImpl("0.", key)); + assert(decimal.fromStringImpl("00", key)); + assert(!decimal.fromStringImpl("0d", key)); +} + +version(mir_bignum_test) +@safe pure nothrow @nogc +unittest +{ + import mir.conv: to; + Decimal!1 decimal; + DecimalExponentKey key; + + assert(decimal.fromStringImpl("1.334", key)); + assert(key == DecimalExponentKey.dot); + assert(cast(double) decimal == 1.334); + + assert(decimal.fromStringImpl("+0.334e-5"w, key)); + assert(key == DecimalExponentKey.e); + assert(cast(double) decimal == 0.334e-5); + + assert(decimal.fromStringImpl("-334D-5"d, key)); + assert(key == DecimalExponentKey.D); + assert(cast(double) decimal == -334e-5); + + assert(!decimal.fromStringImpl("2482734692817364218734682973648217364981273648923423", key)); + + assert(decimal.fromStringImpl(".023", key)); + assert(key == DecimalExponentKey.dot); + assert(cast(double) decimal == .023); + + assert(decimal.fromStringImpl("0E100", key)); + assert(key == DecimalExponentKey.E); + assert(cast(double) decimal == 0); + + /++ Test that Issue #365 is handled properly +/ + assert(decimal.fromStringImpl("123456.e0", key)); + assert(key == DecimalExponentKey.e); + assert(cast(double) decimal == 123_456.0); + + assert(decimal.fromStringImpl("123_456.e0", key)); + assert(key == DecimalExponentKey.e); + assert(cast(double) decimal == 123_456.0); + + assert(decimal.fromStringImpl("123456.E0", key)); + assert(key == DecimalExponentKey.E); + assert(cast(double) decimal == 123_456.0); + + assert(decimal.fromStringImpl("123_456.E0", key)); + assert(key == DecimalExponentKey.E); + assert(cast(double) decimal == 123_456.0); + + assert(decimal.fromStringImpl("123456.d0", key)); + assert(key == DecimalExponentKey.d); + assert(cast(double) decimal == 123_456.0); + + assert(decimal.fromStringImpl("123_456.d0", key)); + assert(key == DecimalExponentKey.d); + assert(cast(double) decimal == 123_456.0); + + assert(decimal.fromStringImpl("123456.D0", key)); + assert(key == DecimalExponentKey.D); + assert(cast(double) decimal == 123_456.0); + + assert(decimal.fromStringImpl("123_456.D0", key)); + assert(key == DecimalExponentKey.D); + assert(cast(double) decimal == 123_456.0); + + /++ Test invalid examples with the fix introduced for Issue #365 +/ + assert(!decimal.fromStringImpl("123_456_.D0", key)); + assert(!decimal.fromStringImpl("123_456.DD0", key)); + assert(!decimal.fromStringImpl("123_456_.E0", key)); + assert(!decimal.fromStringImpl("123_456.EE0", key)); + assert(!decimal.fromStringImpl("123456.ED0", key)); + assert(!decimal.fromStringImpl("123456E0D0", key)); + assert(!decimal.fromStringImpl("123456._D0", key)); + assert(!decimal.fromStringImpl("123456_.D0", key)); + assert(!decimal.fromStringImpl("123456.E0D0", key)); + assert(!decimal.fromStringImpl("123456.D0_", key)); + assert(!decimal.fromStringImpl("123456_", key)); + + foreach (str; ["-nan", "-NaN", "-NAN"]) + { + assert(decimal.fromStringImpl(str, key)); + assert(decimal.coefficient.length > 0); + assert(decimal.exponent == decimal.exponent.max); + assert(decimal.coefficient.sign); + assert(key == DecimalExponentKey.nan); + assert(cast(double) decimal != cast(double) decimal); + } + + foreach (str; ["inf", "Inf", "INF"]) + { + assert(decimal.fromStringImpl(str, key)); + assert(decimal.coefficient.length == 0); + assert(decimal.exponent == decimal.exponent.max); + assert(key == DecimalExponentKey.infinity); + assert(cast(double) decimal == double.infinity); + } + + assert(decimal.fromStringImpl("-inf", key)); + assert(decimal.coefficient.length == 0); + assert(decimal.exponent == decimal.exponent.max); + assert(key == DecimalExponentKey.infinity); + assert(cast(double) decimal == -double.infinity); + + assert(!decimal.fromStringImpl("3.3.4", key)); + assert(!decimal.fromStringImpl("3.4.", key)); + assert(decimal.fromStringImpl("4.", key)); + assert(!decimal.fromStringImpl(".", key)); + assert(decimal.fromStringImpl("0.", key)); + assert(decimal.fromStringImpl("00", key)); + assert(!decimal.fromStringImpl("0d", key)); +} + +/// +version(mir_bignum_test) +@safe pure @nogc unittest +{ + import mir.math.constant: PI; + Decimal!2 decimal = "3.141592653589793378e-40"; // constructor + assert(cast(double) decimal == double(PI) / 1e40); +} + + +/// +version(mir_bignum_test) +@safe pure nothrow @nogc +unittest +{ + // float and double can be used to construct Decimal of any length + auto decimal64 = Decimal!1(-1.235e-7); + assert(decimal64.exponent == -10); + assert(decimal64.coefficient == -1235); + + // real number may need Decimal at least length of 2 + auto decimal128 = Decimal!2(-1.235e-7L); + assert(decimal128.exponent == -10); + assert(decimal128.coefficient == -1235); + + decimal128 = Decimal!2(1234e3f); + assert(decimal128.exponent == 3); + assert(decimal128.coefficient == 1234); +} + +/// +version(mir_bignum_test) +@safe pure nothrow @nogc +unittest +{ + Decimal!3 decimal; + DecimalExponentKey key; + + assert(decimal.fromStringWithThousandsSeparatorImpl("12,345.678", ',', '.', key)); + assert(cast(double) decimal == 12345.678); + assert(key == DecimalExponentKey.dot); + + assert(decimal.fromStringWithThousandsSeparatorImpl("12,345,678", ',', '.', key, -3)); + assert(cast(double) decimal == 12345.678); + assert(key == DecimalExponentKey.none); + + assert(decimal.fromStringWithThousandsSeparatorImpl("021 345,678", ' ', ',', key)); + assert(cast(double) decimal == 21345.678); + assert(key == DecimalExponentKey.dot); +} diff --git a/source/mir/bignum/fixed.d b/source/mir/bignum/fixed.d new file mode 100644 index 00000000..54920761 --- /dev/null +++ b/source/mir/bignum/fixed.d @@ -0,0 +1,1065 @@ +/++ +Note: + The module doesn't provide full arithmetic API for now. ++/ +module mir.bignum.fixed; + +import std.traits; +import mir.bitop; +import mir.utility; + +/++ +Fixed-length unsigned integer. + +Params: + size = size in bits ++/ +struct UInt(size_t size) + if (size % (size_t.sizeof * 8) == 0 && size >= size_t.sizeof * 8) +{ + import mir.bignum.fixed: UInt; + /++ + Payload. The data is located in the target endianness. + +/ + size_t[size / (size_t.sizeof * 8)] data; + + /// + this(size_t N)(auto ref const size_t[N] data) + if (N && N <= this.data.length) + { + version(LittleEndian) + this.data[0 .. N] = data; + else + this.data[$ - N .. $] = data; + } + + /// + this(size_t argSize)(auto ref const UInt!argSize arg) + if (argSize <= size) + { + this(arg.data); + } + + static if (size_t.sizeof == uint.sizeof && data.length % 2 == 0) + /// + this()(auto ref const ulong[data.length / 2] data) + { + if (!__ctfe) + { + this.data = cast(typeof(this.data)) data; + } + else + { + version(LittleEndian) + { + static foreach (i; 0 .. data.length) + { + this.data[i * 2 + 0] = cast(uint) data[i]; + this.data[i * 2 + 1] = cast(uint) (data[i] >> 32); + } + } + else + { + static foreach (i; 0 .. data.length) + { + this.data[i * 2 + 1] = cast(uint) data[i]; + this.data[i * 2 + 0] = cast(uint) (data[i] >> 32); + } + } + } + } + + static if (size >= 64) + /// + this(ulong data) + { + static if (size_t.sizeof == ulong.sizeof) + { + this.data[0] = data; + } + else + { + this.data[0] = cast(uint) data; + this.data[1] = cast(uint) (data >> 32); + } + } + + static if (size < 64) + /// + this(uint data) + { + this.data[0] = data; + } + + /// + this(C)(scope const(C)[] str) @safe pure @nogc + if (isSomeChar!C) + { + if (fromStringImpl(str)) + return; + static if (__traits(compiles, () @nogc { throw new Exception("Can't parse UInt."); })) + { + import mir.exception: MirException; + throw new MirException("Can't parse UInt!" ~ size.stringof ~ " from string `", str , "`."); + } + else + { + static immutable exception = new Exception("Can't parse UInt!" ~ size.stringof ~ "."); + { import mir.exception : toMutable; throw exception.toMutable; } + } + } + + static if (size == 128) + /// + version(mir_bignum_test) @safe pure @nogc unittest + { + import mir.math.constant: PI; + UInt!256 integer = "34010447314490204552169750449563978034784726557588085989975288830070948234680"; // constructor + assert(integer == UInt!256.fromHexString("4b313b23aa560e1b0985f89cbe6df5460860e39a64ba92b4abdd3ee77e4e05b8")); + } + + /++ + Returns: false in case of overflow or incorrect string. + Precondition: non-empty coefficients. + +/ + bool fromStringImpl(C)(scope const(C)[] str) + scope @trusted pure @nogc nothrow + if (isSomeChar!C) + { + import mir.bignum.low_level_view: BigUIntView; + return BigUIntView!size_t(data[]).fromStringImpl(str); + } + + /// + immutable(C)[] toString(C = char)() scope const @safe pure nothrow + if(isSomeChar!C && isMutable!C) + { + UInt!size copy = this; + auto work = copy.view.normalized; + import mir.bignum.low_level_view: ceilLog10Exp2; + C[ceilLog10Exp2(data.length * (size_t.sizeof * 8))] buffer = void; + return buffer[$ - work.toStringImpl(buffer) .. $].idup; + } + + static if (size == 128) + /// + version(mir_bignum_test) @safe pure unittest + { + auto str = "34010447314490204552169750449563978034784726557588085989975288830070948234680"; + auto integer = UInt!256(str); + assert(integer.toString == str); + + integer = UInt!256.init; + assert(integer.toString == "0"); + } + + /// + void toString(C = char, W)(ref scope W w) scope const + if(isSomeChar!C && isMutable!C) + { + UInt!size copy = this; + auto work = copy.view.normalized; + import mir.bignum.low_level_view: ceilLog10Exp2; + C[ceilLog10Exp2(data.length * (size_t.sizeof * 8))] buffer = void; + w.put(buffer[$ - work.toStringImpl(buffer) .. $]); + } + + static if (size == 128) + /// Check @nogc toString impl + version(mir_bignum_test) @safe pure @nogc unittest + { + import mir.format: stringBuf; + auto str = "34010447314490204552169750449563978034784726557588085989975288830070948234680"; + auto integer = UInt!256(str); + auto buffer = stringBuf; + buffer << integer; + assert(buffer.data == str); + } + + /// + enum UInt!size max = ((){UInt!size ret; ret.data = size_t.max; return ret;})(); + + /// + enum UInt!size min = UInt!size.init; + + import mir.bignum.low_level_view: BigUIntView; + + /// + BigUIntView!size_t view() @property pure nothrow @nogc scope return @safe + { + return BigUIntView!size_t(data); + } + + /// + BigUIntView!(const size_t) view() const @property pure nothrow @nogc scope return @safe + { + return BigUIntView!(const size_t)(data); + } + + /// + static UInt!size fromHexString(bool allowUnderscores = false)(scope const(char)[] str) + { + typeof(return) ret; + if (ret.fromHexStringImpl!(char, allowUnderscores)(str)) + return ret; + version(D_Exceptions) + { + import mir.bignum.low_level_view: hexStringException; + { import mir.exception : toMutable; throw hexStringException.toMutable; } + } + else + { + import mir.bignum.low_level_view: hexStringErrorMsg; + assert(0, hexStringErrorMsg); + } + } + + /++ + +/ + bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) + @safe pure @nogc nothrow + if (isSomeChar!C) + { + return view.fromHexStringImpl!(C, allowUnderscores)(str); + } + + /// + static UInt!size fromBinaryString(bool allowUnderscores = false)(scope const(char)[] str) + { + typeof(return) ret; + if (ret.fromBinaryStringImpl!(char, allowUnderscores)(str)) + return ret; + version(D_Exceptions) + { + import mir.bignum.low_level_view: binaryStringException; + { import mir.exception : toMutable; throw binaryStringException.toMutable; } + } + else + { + import mir.bignum.low_level_view: binaryStringErrorMsg; + assert(0, binaryStringErrorMsg); + } + } + + /++ + +/ + bool fromBinaryStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) + @safe pure @nogc nothrow + if (isSomeChar!C) + { + return view.fromBinaryStringImpl!(C, allowUnderscores)(str); + } + + /++ + +/ + auto opEquals(size_t rhsSize)(auto ref const UInt!rhsSize rhs) const + { + static if (rhsSize == size) + return this.data == rhs.data; + else + static if (rhsSize > size) + return this.toSize!rhsSize.data == rhs.data; + else + return this.data == rhs.toSize!size.data; + } + + static if (size >= 64) + /// ditto + auto opEquals(ulong rhs) const + { + return opEquals(UInt!size(rhs)); + } + else + auto opEquals(uint rhs) const + { + return opEquals(UInt!size(rhs)); + } + + /++ + +/ + auto opCmp(UInt!size rhs) const + { + foreach_reverse(i; 0 .. data.length) + { + if (this.data[i] < rhs.data[i]) + return -1; + if (this.data[i] > rhs.data[i]) + return +1; + } + return 0; + } + + static if (size >= 64) + /// ditto + auto opCmp(ulong rhs) const scope + { + return opCmp(UInt!size(rhs)); + } + else + auto opCmp(uint rhs) const scope + { + return opCmp(UInt!size(rhs)); + } + + static if (size >= 64) + /++ + +/ + ref UInt!size opAssign(ulong rhs) scope return + @safe pure nothrow @nogc + { + this.data = UInt!size(rhs).data; + return this; + } + else + /// + ref UInt!size opAssign(uint rhs) scope return + @safe pure nothrow @nogc + { + this.data = UInt!size(rhs).data; + return this; + } + + /++ + +/ + ref UInt!size opAssign(uint rhsSize)(UInt!rhsSize rhs) scope return + @safe pure nothrow @nogc + { + this.data = UInt!size(rhs).data; + return this; + } + + /++ + `bool overflow = a += b ` and `bool overflow = a -= b` operations. + +/ + bool opOpAssign(string op)(UInt!size rhs, bool overflow = false) + @safe pure nothrow @nogc scope + if (op == "+" || op == "-") + { + return view.opOpAssign!op(rhs.view, overflow); + } + + /// ditto + bool opOpAssign(string op)(size_t rhs) + @safe pure nothrow @nogc scope + if (op == "+" || op == "-") + { + return view.opOpAssign!op(rhs); + } + + static if (size_t.sizeof < ulong.sizeof) + /// ditto + bool opOpAssign(string op)(ulong rhs) + @safe pure nothrow @nogc scope + if (op == "+" || op == "-") + { + return opOpAssign!op(UInt!size(rhs)); + } + + /// ditto + bool opOpAssign(string op, uint rsize)(UInt!rsize rhs, bool overflow = false) + @safe pure nothrow @nogc scope + if ((op == "+" || op == "-") && rsize < size) + { + return opOpAssign!op(rhs.toSize!size, overflow); + } + + /++ + Returns: overflow value of multiplication + +/ + size_t opOpAssign(string op : "*")(size_t rhs, size_t carry = 0) + @safe pure nothrow @nogc scope + { + return view.opOpAssign!op(rhs, carry); + } + + static if (size_t.sizeof == 4) + /// ditto + auto opOpAssign(string op : "*")(ulong rhs) + @safe pure nothrow @nogc scope + { + return opOpAssign!op(UInt!64(rhs)); + } + + + /++ + Returns: overflow value of multiplication + +/ + void opOpAssign(string op : "*", size_t rhsSize)(UInt!rhsSize rhs) + @safe pure nothrow @nogc scope + if (rhsSize <= size) + { + this = extendedMul(this, rhs).toSize!size; + } + + /++ + Performs `uint remainder = (overflow$big) /= scalar` operatrion, where `$` denotes big-endian concatenation. + Precondition: `overflow < rhs` + Params: + rhs = unsigned value to devide by + overflow = initial unsigned overflow + Returns: + unsigned remainder value (evaluated overflow) + +/ + uint opOpAssign(string op : "/")(uint rhs, uint overflow = 0) + @safe pure nothrow @nogc scope + { + assert(overflow < rhs); + auto work = view.normalized; + if (worl.coefficients.length) + return work.opOpAssign!op(rhs, overflow); + return overflow; + } + + /++ + Performs division & extracts the remainder. + Params: + rhs = unsigned value to divide by + Returns: quotient, sets `rhs` to remainder + +/ + ref divMod(size_t rhsSize)(scope ref UInt!rhsSize rhs) + @safe pure nothrow @nogc scope return + { + import mir.bignum.internal.kernel: divMod, divisionRequiredBuffSize; + + UInt!size quotient; + + auto dividendV = this.view; + auto divisorV = rhs.view; + divisorV = divisorV.normalized; + dividendV = dividendV.normalized; + + import mir.utility: min; + enum vlen = min(rhs.data.length, data.length); + size_t[divisionRequiredBuffSize(data.length, vlen)] buffer = void; + + divMod( + quotient.data, + divisorV.coefficients, + dividendV.coefficients, + divisorV.coefficients, + buffer); + this = quotient; + return this; + } + + /++ + Performs `big /= rhs` operation. + Params: + rhs = unsigned value to divide by + Returns: + quotient from division + +/ + ref opOpAssign(string op : "/", size_t rhsSize)(UInt!rhsSize rhs) + @safe pure nothrow @nogc scope return + { + return this.divMod(rhs); + } + + /// ditto + ref opOpAssign(string op : "/")(ulong rhs) + @safe pure nothrow @nogc scope return + { + return opOpAssign!(op, ulong.sizeof * 8)(UInt!(ulong.sizeof * 8)(rhs)); + } + + /++ + Performs `big %= rhs` operation. + Params: + rhs = unsigned value to divide by + Returns: + remainder from division + +/ + ref opOpAssign(string op : "%", size_t rhsSize)(UInt!rhsSize rhs) + @safe pure nothrow @nogc scope return + { + this.divMod(rhs); + this = cast(UInt!size)rhs; + } + + /// ditto + ref opOpAssign(string op : "%")(ulong rhs) + @safe pure nothrow @nogc scope + { + return opOpAssign!(op, ulong.sizeof * 8)(UInt!(ulong.sizeof * 8)(rhs)); + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("e3251bacb112c88b71ad3f85a970a314"); + auto b = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); + assert(a / b == UInt!128.fromHexString("1")); + assert(a % b == UInt!128.fromHexString("36920c8e407c9645d0b611586c0a077")); + } + + /// + ref UInt!size opOpAssign(string op)(UInt!size rhs) nothrow return + if (op == "^" || op == "|" || op == "&") + { + static foreach (i; 0 .. data.length) + mixin(`data[i] ` ~ op ~ `= rhs.data[i];`); + return this; + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); + auto b = UInt!128.fromHexString("e3251bacb112c88b71ad3f85a970a314"); + assert((a.opBinary!"|"(b)) == UInt!128.fromHexString("ffbffbeffd1affaf75adfff5abf0a39d")); + } + + /// + ref UInt!size opOpAssign(string op)(size_t rhs) nothrow return scope + if (op == "^" || op == "|" || op == "&") + { + mixin(`view.coefficients[0] ` ~ op ~ `= rhs;`); + return this; + } + + static if (size_t.sizeof < ulong.sizeof) + /// ditto + ref opOpAssign(string op)(ulong rhs) return + @safe pure nothrow @nogc scope + if (op == "^" || op == "|" || op == "&") + { + return opOpAssign!op(UInt!size(rhs)); + } + + /// + ref UInt!size opOpAssign(string op)(size_t shift) + @safe pure nothrow @nogc return + if (op == "<<" || op == ">>") + { + auto d = view.coefficients; + assert(shift < size); + auto index = shift / (size_t.sizeof * 8); + auto bs = shift % (size_t.sizeof * 8); + auto ss = size_t.sizeof * 8 - bs; + static if (op == ">>") + { + if (bs) + { + foreach (j; 0 .. data.length - (index + 1)) + { + d[j] = (d[j + index] >>> bs) | (d[j + (index + 1)] << ss); + } + } + else + { + foreach (j; 0 .. data.length - (index + 1)) + { + d[j] = d[j + index]; + } + } + d[$ - (index + 1)] = d[$ - 1] >>> bs; + foreach (j; data.length - index .. data.length) + { + d[j] = 0; + } + } + else + { + if (bs) + { + foreach_reverse (j; index + 1 .. data.length) + { + d[j] = (d[j - index] << bs) | (d[j - (index + 1)] >> ss); + } + } + else + { + foreach_reverse (j; index + 1 .. data.length) + { + d[j] = d[j - index]; + } + } + d[index] = d[0] << bs; + foreach_reverse (j; 0 .. index) + { + d[j] = 0; + } + } + return this; + } + + /++ + `auto c = a << b` operation. + +/ + UInt!size opBinary(string op)(size_t rhs) + const @safe pure nothrow @nogc + if (op == "<<" || op == ">>>" || op == ">>") + { + UInt!size ret = this; + ret.opOpAssign!op(rhs); + return ret; + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(a << 0 == a); + assert(a << 4 == UInt!128.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); + assert(a << 68 == UInt!128.fromHexString("4a1de7022b0029d00000000000000000")); + assert(a << 127 == UInt!128.fromHexString("80000000000000000000000000000000")); + assert(a >> 0 == a); + assert(a >> 4 == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029")); + assert(a >> 68 == UInt!128.fromHexString("afbbfae3cd0aff2")); + assert(a >> 127 == UInt!128(1)); + } + + /++ + Binary operations + +/ + template opBinary(string op) + if (op == "^" || op == "|" || op == "&" || op == "+" || op == "-" || op == "*" || op == "/" || op == "%") + { + /// + UInt!size opBinary(size_t rsize)(UInt!rsize rhs) + const @safe pure nothrow @nogc + if (rsize <= size) + { + UInt!size ret = this; + ret.opOpAssign!op(rhs); + return ret; + } + + /// ditto + UInt!size opBinary(ulong rhs) + const @safe pure nothrow @nogc + { + UInt!size ret = this; + ret.opOpAssign!op(rhs); + return ret; + } + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(a / UInt!128.fromHexString("5") == UInt!128.fromHexString("23259893f5ceffd49db9f949a0899a1f")); + assert(a == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); + assert(a % UInt!128.fromHexString("5") == UInt!128.fromHexString("2")); + assert(a == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); + + assert(a / 5 == UInt!128.fromHexString("23259893f5ceffd49db9f949a0899a1f")); + assert(a % 5 == UInt!64.fromHexString("2")); + assert(a % 5 == 2); + } + + /// ditto + template opBinaryRight(string op) + if (op == "^" || op == "|" || op == "&" || op == "+" || op == "*") + { + /// + UInt!size opBinaryRight(size_t lsize)(UInt!lsize lhs) + const @safe pure nothrow @nogc + if (lsize < size) + { + UInt!size ret = this; + ret.opOpAssign!op(lhs); + return ret; + } + + /// ditto + UInt!size opBinaryRight(ulong lhs) + const @safe pure nothrow @nogc + { + UInt!size ret = this; + ret.opOpAssign!op(lhs); + return ret; + } + } + + /++ + Shifts left using at most `size_t.sizeof * 8 - 1` bits + +/ + UInt!size smallLeftShift(uint shift) const + { + assert(shift < size_t.sizeof * 8); + UInt!size ret = this; + if (shift) + { + auto csh = size_t.sizeof * 8 - shift; + static foreach_reverse (i; 1 .. data.length) + { + ret.data[i] = (ret.data[i] << shift) | (ret.data[i - 1] >>> csh); + } + ret.data[0] = ret.data[0] << shift; + } + return ret; + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(a.smallLeftShift(4) == UInt!128.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); + } + + /++ + Shifts right using at most `size_t.sizeof * 8 - 1` bits + +/ + UInt!size smallRightShift(uint shift) const + { + assert(shift < size_t.sizeof * 8); + UInt!size ret = this; + if (shift) + { + auto csh = size_t.sizeof * 8 - shift; + static foreach (i; 0 .. data.length - 1) + { + ret.data[i] = (ret.data[i] >>> shift) | (ret.data[i + 1] << csh); + } + ret.data[$ - 1] = ret.data[$ - 1] >>> shift; + } + return ret; + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(a.smallRightShift(4) == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029")); + } + + /++ + +/ + T opCast(T)() const + if (is(Unqual!T == bool)) + { + static foreach (i; 0 .. data.length) + { + if (data[i]) + return true; + } + return false; + } + + /++ + +/ + T opCast(T)() const + if (is(Unqual!T == ulong)) + { + static if (size_t.sizeof == ulong.sizeof) + { + return data[0]; + } + else + { + return data[0] | (ulong(data[1]) << 32); + } + } + + /++ + +/ + T opCast(T)() const @safe pure nothrow @nogc + if (is(T == UInt!newSize, uint newSize)) + { + enum newLength = typeof(return).data.length; + static if (newLength <= data.length) + { + return typeof(return)(data[0 .. newLength]); + } + else + { + typeof(return) ret; + ret.data[0 .. data.length] = data; + return ret; + } + } + + /++ + +/ + T opCast(T)() const + if (is(Unqual!T == uint)) + { + return cast(uint) data[0]; + } + + /++ + Returns: + the number with shrinked or extended size. + +/ + UInt!newSize toSize(size_t newSize, bool lowerBits = true)() + const @safe pure @nogc nothrow + { + typeof(return) ret; + import mir.utility: min; + enum N = min(ret.data.length, data.length); + static if (lowerBits) + { + ret.data[0 .. N] = data[0 .. N]; + } + else + { + ret.data[0 .. N] = data[$ - N .. $]; + } + return ret; + } + + /// + UInt!(size + additionalRightBits) rightExtend(size_t additionalRightBits)() + const @safe pure @nogc nothrow + { + static if (additionalRightBits) + { + typeof(return) ret; + version (BigEndian) + ret.data[0 .. data.length] = data; + else + ret.data[$ - data.length .. $] = data; + return ret; + } + else + { + return this; + } + } + + /++ + +/ + bool bt(size_t position) const + @safe pure nothrow @nogc + { + assert(position < data.sizeof * 8); + return view.bt(position); + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(a.bt(127) == 1); + assert(a.bt(126) == 0); + assert(a.bt(125) == 1); + assert(a.bt(124) == 0); + assert(a.bt(0) == 1); + assert(a.bt(1) == 0); + assert(a.bt(2) == 1); + assert(a.bt(3) == 1); + } + + /++ + +/ + size_t ctlz() const scope @property + @safe pure nothrow @nogc + { + return view.ctlz; + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); + assert (a.ctlz == 0); + a = UInt!128.init; + assert (a.ctlz == 128); + a = UInt!128.fromHexString("3"); + assert (a.ctlz == 126); + } + + /++ + +/ + size_t cttz() const @property + @safe pure nothrow @nogc + { + return view.cttz; + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + auto a = UInt!128.fromHexString("d"); + assert (a.cttz == 0); + a = UInt!128.init; + assert (a.cttz == 128); + a = UInt!128.fromHexString("300000000000000000"); + assert (a.cttz == 68); + } + + /++ + +/ + bool signBit() const @property + { + return data[$ - 1] >> (size_t.sizeof * 8 - 1); + } + + /// ditto + void signBit(bool value) @property + { + enum signMask = ptrdiff_t.max; + data[$ - 1] = (data[$ - 1] & ptrdiff_t.max) | (size_t(value) << (size_t.sizeof * 8 - 1)); + } + + static if (size == 128) + /// + version(mir_bignum_test) + unittest + { + auto a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); + assert(a.signBit); + a.signBit = false; + assert(a == UInt!128.fromHexString("5fbbfae3cd0aff2714a1de7022b0029d")); + assert(!a.signBit); + a.signBit = true; + assert(a == UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d")); + } +} + +/++ ++/ +UInt!sizeB extendedMulHigh(size_t sizeA, size_t sizeB)(UInt!sizeA a, UInt!sizeB b) + @safe pure nothrow @nogc +{ + return (extendedMul(a, b) >> sizeA).toSize!sizeB; +} + +/++ ++/ +UInt!(sizeA + sizeB) extendedMul(size_t sizeA, size_t sizeB)(UInt!sizeA a, UInt!sizeB b) + @safe pure nothrow @nogc +{ + UInt!(sizeA + sizeB) ret; + enum al = a.data.length; + enum alp1 = a.data.length + 1; + ret.data[0 .. alp1] = extendedMul(a, b.data[0]).data; + static foreach ( i; 1 .. b.data.length) + ret.data[i .. i + alp1] = extendedMulAdd(a, b.data[i], UInt!sizeA(ret.data[i .. i + al])).data; + return ret; +} + +/// ditto +UInt!(size + size_t.sizeof * 8) + extendedMul(size_t size)(UInt!size a, size_t b) + @safe pure nothrow @nogc +{ + size_t overflow = a.view *= b; + auto ret = a.toSize!(size + size_t.sizeof * 8); + ret.data[$ - 1] = overflow; + return ret; +} + +/// ditto +auto extendedMul()(ulong a, ulong b) + @safe pure nothrow @nogc +{ + static if (size_t.sizeof == ulong.sizeof) + { + import mir.utility: extMul; + auto e = extMul(a, b); + return UInt!128([e.low, e.high]); + } + else + { + return extendedMul(UInt!64(a), UInt!64(b)); + } +} + +/// ditto +auto extendedMul()(uint a, uint b) + @safe pure nothrow @nogc +{ + static if (size_t.sizeof == uint.sizeof) + { + import mir.utility: extMul; + auto e = extMul(a, b); + version(LittleEndian) + return UInt!64([e.low, e.high]); + else + return UInt!64([e.high, e.low]); + } + else + { + return UInt!64([ulong(a) * b]); + } +} + +/// +version(mir_bignum_test) +@safe pure @nogc +unittest +{ + auto a = UInt!128.max; + auto b = UInt!256.max; + auto c = UInt!384.max; + assert(extendedMul(a, a) == UInt!256.max - UInt!128.max - UInt!128.max); + assert(extendedMul(a, b) == UInt!384.max - UInt!128.max - UInt!256.max); + assert(extendedMul(b, a) == UInt!384.max - UInt!128.max - UInt!256.max); + + a = UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d"); + b = UInt!256.fromHexString("3fe48f2dc8aad570d037bc9b323fc0cfa312fcc2f63cb521bd8a4ca6157ef619"); + c = UInt!384.fromHexString("37d7034b86e8d58a9fc564463fcedef9e2ad1126dd2c0f803e61c72852a9917ef74fa749e7936a9e4e224aeeaff91f55"); + assert(extendedMul(a, b) == c); + assert(extendedMul(b, a) == c); + + a = UInt!128.fromHexString("23edf5ff44ee3a4feafc652607aa1eb9"); + b = UInt!256.fromHexString("d3d79144b8941fb50c9102e3251bacb112c88b71ad3f85a970a31458ce24297b"); + c = UInt!384.fromHexString("1dbb62fe6ca5fed101068eda7222d6a9857633ecdfed37a2d156ff6309065ecc633f31465727677a93a7acbd1dac63e3"); + assert(extendedMul(a, b) == c); + assert(extendedMul(b, a) == c); +} + +/// ulong +version(mir_bignum_test) +@safe pure @nogc +unittest +{ + ulong a = 0xdfbbfae3cd0aff27; + ulong b = 0x14a1de7022b0029d; + auto c = UInt!128.fromHexString("120827399968ea2a2db185d16e8cc8eb"); + assert(extendedMul(a, b) == c); + assert(extendedMul(b, a) == c); +} + +/// uint +version(mir_bignum_test) +@safe pure @nogc +unittest +{ + uint a = 0xdfbbfae3; + uint b = 0xcd0aff27; + auto c = UInt!64.fromHexString("b333243de8695595"); + assert(extendedMul(a, b) == c); + assert(extendedMul(b, a) == c); +} + +/++ ++/ +UInt!(size + size_t.sizeof * 8) + extendedMulAdd(size_t size)(UInt!size a, size_t b, UInt!size c) +{ + auto ret = extendedMul(a, b); + auto view = ret.view; + view.coefficients[$ - 1] += view.topLeastSignificantPart(a.data.length) += c.view; + return ret; +} diff --git a/source/mir/bignum/fp.d b/source/mir/bignum/fp.d new file mode 100644 index 00000000..d56109de --- /dev/null +++ b/source/mir/bignum/fp.d @@ -0,0 +1,751 @@ +/++ +Note: + The module doesn't provide full arithmetic API for now. ++/ +module mir.bignum.fp; + +import mir.bitop; +import mir.utility; +import std.traits; + +package enum half(uint hs) = (){ + import mir.bignum.fixed: UInt; + UInt!hs ret; ret.signBit = true; return ret; +}(); + +/++ +Software floating point number. + +Params: + size = coefficient size in bits ++/ +struct Fp(uint size) + if (size % (uint.sizeof * 8) == 0 && size >= (uint.sizeof * 8)) +{ + import mir.bignum.fixed: UInt; + + bool sign; + long exponent; + UInt!size coefficient; + + /++ + +/ + nothrow + this(bool sign, long exponent, UInt!size normalizedCoefficient) + { + this.coefficient = normalizedCoefficient; + this.exponent = exponent; + this.sign = sign; + } + + /++ + Constructs $(LREF Fp) from hardaware floating point number. + Params: + value = Hardware floating point number. Special values `nan` and `inf` aren't allowed. + normalize = flag to indicate if the normalization should be performed. + +/ + this(T)(const T value, bool normalize = true) + @safe pure nothrow @nogc + if (isFloatingPoint!T && T.mant_dig <= size) + { + import mir.math.common : fabs; + import mir.math.ieee : frexp, signbit, ldexp; + this.sign = value.signbit != 0; + if (value == 0) + return; + T x = value.fabs; + if (_expect(!(x < T.infinity), false)) + { + this.exponent = this.exponent.max; + this.coefficient = x != T.infinity; + return; + } + int exp; + { + enum scale = T(2) ^^ T.mant_dig; + x = frexp(x, exp) * scale; + } + + static if (T.mant_dig < 64) + { + auto xx = cast(ulong)cast(long)x; + if (normalize) + { + auto shift = ctlz(xx); + exp -= shift + T.mant_dig + size - 64; + xx <<= shift; + this.coefficient = UInt!64(xx).rightExtend!(size - 64); + } + else + { + this.coefficient = xx; + } + } + else + static if (T.mant_dig == 64) + { + auto xx = cast(ulong)x; + if (normalize) + { + auto shift = ctlz(xx); + exp -= shift + T.mant_dig + size - 64; + xx <<= shift; + this.coefficient = UInt!64(xx).rightExtend!(size - 64); + } + else + { + this.coefficient = xx; + } + } + else + { + enum scale = T(2) ^^ 64; + enum scaleInv = 1 / scale; + x *= scaleInv; + long high = cast(long) x; + if (high > x) + --high; + x -= high; + x *= scale; + auto most = ulong(high); + auto least = cast(ulong)x; + version(LittleEndian) + ulong[2] pair = [least, most]; + else + ulong[2] pair = [most, least]; + + if (normalize) + { + this.coefficient = UInt!128(pair).rightExtend!(size - 128); + auto shift = most ? ctlz(most) : ctlz(least) + 64; + exp -= shift + T.mant_dig + size - 64 * (1 + (T.mant_dig > 64)); + this.coefficient <<= shift; + } + else + { + this.coefficient = pair; + } + } + if (!normalize) + { + exp -= T.mant_dig; + int shift = T.min_exp - T.mant_dig - exp; + if (shift > 0) + { + this.coefficient >>= shift; + exp = T.min_exp - T.mant_dig; + } + } + this.exponent = exp; + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc nothrow + unittest + { + enum h = -33.0 * 2.0 ^^ -10; + auto f = Fp!64(h); + assert(f.sign); + assert(f.exponent == -10 - (64 - 6)); + assert(f.coefficient == 33UL << (64 - 6)); + assert(cast(double) f == h); + + // CTFE + static assert(cast(double) Fp!64(h) == h); + + f = Fp!64(-0.0); + assert(f.sign); + assert(f.exponent == 0); + assert(f.coefficient == 0); + + // subnormals + static assert(cast(float) Fp!64(float.min_normal / 2) == float.min_normal / 2); + static assert(cast(float) Fp!64(float.min_normal * float.epsilon) == float.min_normal * float.epsilon); + // subnormals + static assert(cast(double) Fp!64(double.min_normal / 2) == double.min_normal / 2); + static assert(cast(double) Fp!64(double.min_normal * double.epsilon) == double.min_normal * double.epsilon); + // subnormals + static if (real.mant_dig <= 64) + { + static assert(cast(real) Fp!128(real.min_normal / 2) == real.min_normal / 2); + static assert(cast(real) Fp!128(real.min_normal * real.epsilon) == real.min_normal * real.epsilon); + } + + enum d = cast(float) Fp!64(float.min_normal / 2, false); + + // subnormals + static assert(cast(float) Fp!64(float.min_normal / 2, false) == float.min_normal / 2, d.stringof); + static assert(cast(float) Fp!64(float.min_normal * float.epsilon, false) == float.min_normal * float.epsilon); + // subnormals + static assert(cast(double) Fp!64(double.min_normal / 2, false) == double.min_normal / 2); + static assert(cast(double) Fp!64(double.min_normal * double.epsilon, false) == double.min_normal * double.epsilon); + // subnormals + static if (real.mant_dig <= 64) + { + static assert(cast(real) Fp!64(real.min_normal / 2, false) == real.min_normal / 2); + static assert(cast(real) Fp!64(real.min_normal * real.epsilon, false) == real.min_normal * real.epsilon); + } + + import mir.bignum.fixed: UInt; + + assert(cast(double)Fp!128(+double.infinity) == +double.infinity); + assert(cast(double)Fp!128(-double.infinity) == -double.infinity); + + import mir.math.ieee : signbit; + auto r = cast(double)Fp!128(-double.nan); + assert(r != r && r.signbit); + } + + // static if (size == 128) + // /// Without normalization + // version(mir_bignum_test) + // @safe pure @nogc nothrow + // unittest + // { + // auto f = Fp!64(-33.0 * 2.0 ^^ -10, false); + // assert(f.sign); + // assert(f.exponent == -10 - (double.mant_dig - 6)); + // assert(f.coefficient == 33UL << (double.mant_dig - 6)); + // } + + /++ + +/ + this(uint isize)(UInt!isize integer, bool normalizedInteger = false) + nothrow + { + import mir.bignum.fixed: UInt; + static if (isize < size) + { + if (normalizedInteger) + { + this(false, long(isize) - size, integer.rightExtend!(size - isize)); + } + else + { + this(integer.toSize!size, false); + } + } + else + { + if (!integer) + return; + this.exponent = isize - size; + if (!normalizedInteger) + { + auto c = integer.ctlz; + integer <<= c; + this.exponent -= c; + } + static if (isize == size) + { + coefficient = integer; + } + else + { + enum N = coefficient.data.length; + version (LittleEndian) + coefficient.data = integer.data[$ - N .. $]; + else + coefficient.data = integer.data[0 .. N]; + enum tailSize = isize - size; + auto cr = integer.toSize!tailSize.opCmp(half!tailSize); + if (cr > 0 || cr == 0 && coefficient.bt(0)) + { + if (auto overflow = coefficient += 1) + { + coefficient = half!size; + exponent++; + } + } + } + } + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + import mir.bignum.fixed: UInt; + + auto fp = Fp!128(UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); + assert(fp.exponent == 0); + assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); + + fp = Fp!128(UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"), true); + assert(fp.exponent == 0); + assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); + + fp = Fp!128(UInt!128.fromHexString("ae3cd0aff2714a1de7022b0029d")); + assert(fp.exponent == -20); + assert(fp.coefficient == UInt!128.fromHexString("ae3cd0aff2714a1de7022b0029d00000")); + + fp = Fp!128(UInt!128.fromHexString("e7022b0029d")); + assert(fp.exponent == -84); + assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); + + fp = Fp!128(UInt!64.fromHexString("e7022b0029d")); + assert(fp.exponent == -84); + assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); + + fp = Fp!128(UInt!64.fromHexString("e7022b0029dd0aff"), true); + assert(fp.exponent == -64); + assert(fp.coefficient == UInt!128.fromHexString("e7022b0029dd0aff0000000000000000")); + + fp = Fp!128(UInt!64.fromHexString("e7022b0029d")); + assert(fp.exponent == -84); + assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); + + fp = Fp!128(UInt!192.fromHexString("ffffffffffffffffffffffffffffffff1000000000000000")); + assert(fp.exponent == 64); + assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); + + fp = Fp!128(UInt!192.fromHexString("ffffffffffffffffffffffffffffffff8000000000000000")); + assert(fp.exponent == 65); + assert(fp.coefficient == UInt!128.fromHexString("80000000000000000000000000000000")); + + fp = Fp!128(UInt!192.fromHexString("fffffffffffffffffffffffffffffffe8000000000000000")); + assert(fp.exponent == 64); + assert(fp.coefficient == UInt!128.fromHexString("fffffffffffffffffffffffffffffffe")); + + fp = Fp!128(UInt!192.fromHexString("fffffffffffffffffffffffffffffffe8000000000000001")); + assert(fp.exponent == 64); + assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); + } + + /// ditto + this(ulong value) + { + this(UInt!64(value)); + } + + /// + this(long value) + { + this(ulong(value >= 0 ? value : -value)); + this.sign = !(value >= 0); + } + + /// + this(int value) + { + this(long(value)); + } + + /// + this(uint value) + { + this(ulong(value)); + } + + /// + bool isNaN() scope const @property + { + return this.exponent == this.exponent.max && this.coefficient != this.coefficient.init; + } + + /// + bool isInfinity() scope const @property + { + return this.exponent == this.exponent.max && this.coefficient == coefficient.init; + } + + /// + bool isSpecial() scope const @property + { + return this.exponent == this.exponent.max; + } + + /// + bool opEquals(const Fp rhs) scope const + { + if (this.exponent != rhs.exponent) + return false; + if (this.coefficient != rhs.coefficient) + return false; + if (this.coefficient == 0) + return !this.isSpecial || this.sign == rhs.sign; + if (this.sign != rhs.sign) + return false; + return !this.isSpecial; + } + + /// + ref Fp opOpAssign(string op)(Fp rhs) nothrow scope return + if (op == "*" || op == "/") + { + this = this.opBinary!op(rhs); + return this; + } + + /// + Fp!(max(size, rhsSize)) opBinary(string op : "*", uint rhsSize)(Fp!rhsSize rhs) nothrow const + { + return cast(Fp) .extendedMul(cast()this, rhs); + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + import mir.bignum.fixed: UInt; + + auto a = Fp!128(0, -13, UInt!128.fromHexString("dfbbfae3cd0aff2714a1de7022b0029d")); + auto b = Fp!128(1, 100, UInt!128.fromHexString("e3251bacb112c88b71ad3f85a970a314")); + auto fp = a.opBinary!"*"(b); + assert(fp.sign); + assert(fp.exponent == 100 - 13 + 128); + assert(fp.coefficient == UInt!128.fromHexString("c6841dd302415d785373ab6d93712988")); + } + + /// Uses approximate division for now + /// TODO: use full precision division for void when Fp division is ready + Fp!(max(size, rhsSize)) opBinary(string op : "/", uint rhsSize)(Fp!rhsSize rhs) nothrow const + { + Fp a = this; + alias b = rhs; + auto exponent = a.exponent - b.exponent; + a.exponent = b.exponent = -long(size); + auto ret = typeof(return)(cast(real) a / cast(real) b); + ret.exponent += exponent; + return ret; + } + + /// + T opCast(T)() nothrow const + if (is(Unqual!T == bool)) + { + return exponent || coefficient; + } + + /// + T opCast(T, bool noSpecial = false, bool noHalf = false)() nothrow const + if (is(T == float) || is(T == double) || is(T == real)) + { + import mir.math.ieee: ldexp; + static if (!noSpecial) + { + if (_expect(this.isSpecial, false)) + { + T ret = this.coefficient ? T.nan : T.infinity; + if (this.sign) + ret = -ret; + return ret; + } + } + auto exp = cast()this.exponent; + static if (size == 32) + { + T c = cast(uint) coefficient; + } + else + static if (size == 64) + { + T c = cast(ulong) coefficient; + } + else + { + enum shift = size - T.mant_dig; + enum rMask = (UInt!size(1) << shift) - UInt!size(1); + enum rHalf = UInt!size(1) << (shift - 1); + enum rInc = UInt!size(1) << shift; + UInt!size adC = this.coefficient; + static if (!noHalf) + { + auto cr = (this.coefficient & rMask).opCmp(rHalf); + if ((cr > 0) | (cr == 0) & this.coefficient.bt(shift)) + { + if (auto overflow = adC += rInc) + { + adC = half!size; + exp++; + } + } + } + adC >>= shift; + exp += shift; + T c = cast(ulong) adC; + static if (T.mant_dig > 64) // + { + static assert (T.mant_dig <= 128); + c += ldexp(cast(T) cast(ulong) (adC >> 64), 64); + } + } + if (this.sign) + c = -c; + static if (exp.sizeof > int.sizeof) + { + import mir.utility: min, max; + exp = exp.max(int.min).min(int.max); + } + return ldexp(c, cast(int)exp); + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + import mir.bignum.fixed: UInt; + auto fp = Fp!128(1, 100, UInt!128.fromHexString("e3251bacb112cb8b71ad3f85a970a314")); + assert(cast(double)fp == -0xE3251BACB112C8p+172); + + fp = Fp!128(1, long.max, UInt!128.init); + assert(cast(double)fp == -double.infinity); + + import mir.math.ieee : signbit; + fp = Fp!128(1, long.max, UInt!128(123)); + auto r = cast(double)fp; + assert(r != r && r.signbit); + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + import mir.bignum.fixed: UInt; + auto fp = Fp!128(1, 100, UInt!128.fromHexString("e3251bacb112cb8b71ad3f85a970a314")); + static if (real.mant_dig == 64) + assert(cast(real)fp == -0xe3251bacb112cb8bp+164L); + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + import mir.bignum.fixed: UInt; + auto fp = Fp!64(1, 100, UInt!64(0xe3251bacb112cb8b)); + version (DigitalMars) + { + // https://issues.dlang.org/show_bug.cgi?id=20963 + assert(cast(double)fp == -0xE3251BACB112C8p+108 + || cast(double)fp == -0xE3251BACB112D0p+108); + } + else + { + assert(cast(double)fp == -0xE3251BACB112C8p+108); + } + } +// -0x1.c64a375962259p+163 = +// -0xe.3251bacb112cb8bp+160 = +// -0x1.c64a37596225ap+163 = +// -0xe.3251bacb112cb8bp+160 = + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + import mir.bignum.fixed: UInt; + auto fp = Fp!64(1, 100, UInt!64(0xe3251bacb112cb8b)); + static if (real.mant_dig == 64) + assert(cast(real)fp == -0xe3251bacb112cb8bp+100L); + } + + /// + T opCast(T : Fp!newSize, bool noSpecial = false, size_t newSize)() nothrow const + if (newSize != size) + { + Fp!newSize ret; + ret.sign = this.sign; + + static if (!noSpecial) + { + if (_expect(this.isSpecial, false)) + { + ret.exponent = ret.exponent.max; + ret.coefficient = !!this.coefficient; + return ret; + } + if (!this) + { + return ret; + } + } + + UInt!size coefficient = this.coefficient; + int shift; + // subnormal + + static if (!noSpecial) + { + if (this.exponent == this.exponent.min) + { + shift = cast(int)coefficient.ctlz; + coefficient <<= shift; + } + } + + ret = Fp!newSize(coefficient, true); + ret.exponent -= shift; + ret.sign = this.sign; + + import mir.checkedint: adds; + /// overflow + + static if (!noSpecial) + { + bool overflow; + ret.exponent = adds(ret.exponent, this.exponent, overflow); + if (_expect(overflow, false)) + { + // overflow + if (this.exponent > 0) + { + ret.exponent = ret.exponent.max; + ret.coefficient = 0u; + } + // underflow + else + { + ret.coefficient >>= cast(uint)(ret.exponent - exponent.min); + ret.exponent = ret.coefficient ? ret.exponent.min : 0; + } + } + } + else + { + ret.exponent += this.exponent; + } + return ret; + } + + static if (size == 128) + /// + version(mir_bignum_test) + @safe pure @nogc + unittest + { + import mir.bignum.fixed: UInt; + auto fp = cast(Fp!64) Fp!128(UInt!128.fromHexString("afbbfae3cd0aff2784a1de7022b0029d")); + assert(fp.exponent == 64); + assert(fp.coefficient == UInt!64.fromHexString("afbbfae3cd0aff28")); + + assert(Fp!128(-double.infinity) * Fp!128(1) == Fp!128(-double.infinity)); + } +} + +/// +Fp!(coefficientizeA + coefficientizeB) extendedMul(bool noSpecial = false, uint coefficientizeA, uint coefficientizeB)(Fp!coefficientizeA a, Fp!coefficientizeB b) + @trusted pure nothrow @nogc +{ + import mir.bignum.fixed: extendedMul; + import mir.checkedint: adds; + + typeof(return) ret = void; + ret.coefficient = extendedMul(a.coefficient, b.coefficient); + static if (noSpecial) + { + ret.exponent = a.exponent + b.exponent; + if (!ret.coefficient.signBit) + { + ret.exponent -= 1; // check overflow + ret.coefficient = ret.coefficient.smallLeftShift(1); + } + } + else + { + // nan * any -> nan + // inf * fin -> inf + if (_expect(a.isSpecial | b.isSpecial, false)) + { // set nan + ret.exponent = ret.exponent.max; + // nan inf case + if (a.isSpecial & b.isSpecial) + ret.coefficient = a.coefficient | b.coefficient; + } + else + { + bool overflow; + ret.exponent = adds(a.exponent, b.exponent, overflow); + // exponent underflow -> 0 or subnormal + // overflow -> inf + if (_expect(overflow, false)) + { + // overflow + if (a.exponent > 0) // && b.exponent > 0 is always true + { + ret.exponent = ret.exponent.max; + ret.coefficient = 0; + } + // underflow + else // a.exponent < 0 and b.exponent < 0 + { + // TODO: subnormal + ret.exponent = 0; + ret.coefficient = 0; + } + } + else + if (!ret.coefficient.signBit) + { + auto normal = ret.exponent != ret.exponent.min; + ret.exponent -= normal; // check overflow + ret.coefficient = ret.coefficient.smallLeftShift(normal); + } + } + } + ret.sign = a.sign ^ b.sign; + return ret; +} + +/// +template fp_log2(T) + if (is(T == float) || is(T == double) || is(T == real)) +{ + /// + T fp_log2(uint size)(Fp!size x) + { + import mir.math.common: log2; + auto exponent = x.exponent + size; + if (!x.isSpecial) + x.exponent = -long(size); + return log2(cast(T)x) + exponent; + } +} + +/// +version(mir_bignum_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: log2, approxEqual; + import mir.bignum.fp: fp_log2; + + double x = 123456789.0e+123; + assert(approxEqual(x.Fp!128.fp_log2!double, x.log2)); +} + +/// +template fp_log(T) + if (is(T == float) || is(T == double) || is(T == real)) +{ + /// + T fp_log(uint size)(Fp!size x) + { + import mir.math.constant: LN2; + return T(LN2) * fp_log2!T(x); + } +} + +/// +version(mir_bignum_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: log, approxEqual; + import mir.bignum.fp: fp_log; + + double x = 123456789.0e+123; + assert(approxEqual(x.Fp!128.fp_log!double, x.log)); +} diff --git a/source/mir/bignum/integer.d b/source/mir/bignum/integer.d new file mode 100644 index 00000000..0991c151 --- /dev/null +++ b/source/mir/bignum/integer.d @@ -0,0 +1,1193 @@ +/++ +Note: + The module doesn't provide full arithmetic API for now. ++/ +module mir.bignum.integer; + +import mir.bitop; +import mir.serde: serdeProxy, serdeScoped; +import mir.utility; +import std.traits; + +/++ +Stack-allocated big signed integer. +Params: + size64 = count of 64bit words in coefficient ++/ +@serdeScoped @serdeProxy!(const(char)[]) +struct BigInt(uint size64) + if (size64 && size64 <= ushort.max) +{ + import mir.bignum.low_level_view; + import mir.bignum.fixed; + + /// + bool sign; + /// + uint length; + /// + size_t[ulong.sizeof / size_t.sizeof * size64] data;// = void; + +@safe: + + /// + this(uint size)(UInt!size fixedInt) + { + this(fixedInt.data); + } + + /// + this(uint N)(size_t[N] data) + if (N <= this.data.length) + { + static if (data.length == 0) + { + sign = false; + length = 0; + } + static if (data.length == 1) + { + this(data[0]); + } + else + static if (data.length == 2) + { + sign = false; + this.data[0] = data[0]; + this.data[1] = data[1]; + this.length = data[1] ? 2 : data[0] != 0; + } + else + { + sign = false; + this.data[0 .. N] = data; + length = data.length; + normalize; + } + } + + /// + this(ulong data) + { + sign = false; + static if (size_t.sizeof == ulong.sizeof) + { + length = data != 0; + this.data[0] = data; + } + else + { + this.length = !!data + !!(data >> 32); + this.data[0] = cast(uint) data; + this.data[1] = cast(uint) (data >> 32); + } + } + + /// + this(long data) + { + this(ulong(data < 0 ? -data : data)); + this.sign = data < 0; + } + + /// + this(int data) + { + this(long(data)); + } + + /// + this(uint data) + { + this(ulong(data)); + } + + /// + this()(scope const(char)[] str) @safe pure @nogc + { + if (fromStringImpl(str)) + return; + static if (__traits(compiles, () @nogc { throw new Exception("Can't parse BigInt."); })) + { + import mir.exception: MirException; + throw new MirException("Can't parse BigInt!" ~ size64.stringof ~ " from string `", str , "`."); + } + else + { + static immutable exception = new Exception("Can't parse BigInt!" ~ size64.stringof ~ "."); + { import mir.exception : toMutable; throw exception.toMutable; } + } + } + + /// + inout(size_t)[] coefficients()() inout @property scope return + { + return data[0 .. length]; + } + + /// + ref opAssign(ulong data) return scope + { + __ctor(data); + return this; + } + + /// + ref opAssign(long data) return scope + { + __ctor(data); + return this; + } + + /// + ref opAssign(uint data) return scope + { + __ctor(data); + return this; + } + + /// + ref opAssign(int data) return scope + { + __ctor(data); + return this; + } + + /// + ref opAssign(uint rhsSize)(UInt!rhsSize data) return scope + { + __ctor(data); + return this; + } + + static if (size64 == 3) + /// + version(mir_bignum_test) @safe pure @nogc unittest + { + import mir.math.constant: PI; + BigInt!4 integer = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; // constructor + assert(integer.sign); + integer.sign = false; + assert(integer == BigInt!4.fromHexString("4b313b23aa560e1b0985f89cbe6df5460860e39a64ba92b4abdd3ee77e4e05b8")); + } + + /// + ref opAssign(uint rhsSize64)(auto ref scope const BigInt!rhsSize64 rhs) return + @trusted pure nothrow @nogc + in (rhs.length <= this.data.length) + { + static if (size64 == rhsSize64) + { + if (&this is &rhs) + return this; + } + this.sign = rhs.sign; + this.length = rhs.length; + this.data[0 .. length] = rhs.data[0 .. length]; + return this; + } + + /// + static BigInt fromBigEndian()(scope const(ubyte)[] data, bool sign = false) + @trusted pure @nogc + { + static immutable bigIntOverflowException = new Exception("BigInt!" ~ size64.stringof ~ ".fromBigEndian: data overflow"); + BigInt ret = void; + if (!ret.copyFromBigEndian(data, sign)) + { import mir.exception : toMutable; throw bigIntOverflowException.toMutable; } + return ret; + } + + /// + bool copyFromBigEndian()(scope const(ubyte)[] data, bool sign = false) + @trusted pure @nogc + { + while(data.length && data[0] == 0) + data = data[1 .. $]; + if (data.length == 0) + { + this.length = 0; + this.sign = false; + } + else + { + if (data.length > this.data.sizeof) + return false; + this.sign = sign; + this.length = cast(uint) (data.length / size_t.sizeof); + auto tail = data[0 .. data.length % size_t.sizeof]; + data = data[data.length % size_t.sizeof .. $]; + foreach_reverse (ref c; this.coefficients) + { + size_t value; + foreach (j; 0 .. size_t.sizeof) + { + value <<= 8; + value |= data[0]; + data = data[1 .. $]; + } + c = value; + } + assert(data.length == 0); + if (tail.length) + { + this.length++; + size_t value; + foreach (b; tail) + { + value <<= 8; + value |= b; + } + this.data[length - 1] = value; + } + } + return true; + } + + /++ + Returns: false in case of overflow or incorrect string. + Precondition: non-empty coefficients. + +/ + bool fromStringImpl(C)(scope const(C)[] str) + scope @trusted pure @nogc nothrow + if (isSomeChar!C) + { + auto work = data[].BigIntView!size_t; + if (work.fromStringImpl(str)) + { + length = cast(uint) work.coefficients.length; + sign = work.sign; + return true; + } + return false; + } + + /// + BigInt copy() @property @trusted + { + BigInt ret = void; + ret.sign = sign; + ret.length = length; + ret.data[0 .. length] = data[0 .. length]; + return ret; + } + + /// + bool opEquals()(auto ref const BigInt rhs) + const @safe pure nothrow @nogc + { + return view == rhs.view; + } + + /// + bool opEquals(ulong rhs, bool rhsSign = false) + const @safe pure nothrow @nogc + { + if (rhs == 0 && this.length == 0 || this.length == 1 && this.sign == rhsSign && this.data[0] == rhs) + return true; + static if (is(size_t == ulong) || size64 == 1) + return false; + else + return this.length == 2 && this.data[0] == cast(uint) rhs && this.data[1] == cast(uint) (rhs >> 32); + } + + /// + bool opEquals(long rhs) + const @safe pure nothrow @nogc + { + auto sign = rhs < 0; + return this.opEquals(sign ? ulong(-rhs) : ulong(rhs), sign); + } + + /// + bool opEquals(uint rhs) + const @safe pure nothrow @nogc + { + return opEquals(ulong(rhs), false); + } + + /// + bool opEquals(int rhs) + const @safe pure nothrow @nogc + { + return opEquals(long(rhs)); + } + + /++ + +/ + auto opCmp()(auto ref const BigInt rhs) + const @safe pure nothrow @nogc + { + return view.opCmp(rhs.view); + } + + /// + BigIntView!size_t view()() scope return @property + { + return typeof(return)(this.data[0 .. this.length], this.sign); + } + + /// + BigIntView!(const size_t) view()() const scope return @property + { + return typeof(return)(this.data[0 .. this.length], this.sign); + } + + /// + void normalize()() + { + pragma(inline, false); + auto norm = view.normalized; + this.length = cast(uint) norm.unsigned.coefficients.length; + this.sign = norm.sign; + } + + /++ + +/ + void putCoefficient(size_t value) + { + assert(length < data.length); + data[length++] = value; + } + + /++ + Performs `size_t overflow = (big += overflow) *= scalar` operatrion. + Params: + rhs = unsigned value to multiply by + overflow = initial overflow value + Returns: + unsigned overflow value + +/ + size_t opOpAssign(string op : "*")(size_t rhs, size_t overflow = 0u) + @safe pure nothrow @nogc + { + if (length == 0) + goto L; + overflow = view.unsigned.opOpAssign!op(rhs, overflow); + if (overflow && length < data.length) + { + L: + putCoefficient(overflow); + overflow = 0; + } + return overflow; + } + + /++ + Performs `uint remainder = (overflow$big) /= scalar` operatrion, where `$` denotes big-endian concatenation. + Precondition: `overflow < rhs` + Params: + rhs = unsigned value to devide by + overflow = initial unsigned overflow + Returns: + unsigned remainder value (evaluated overflow) + +/ + uint opOpAssign(string op : "/")(uint rhs, uint overflow = 0) + @safe pure nothrow @nogc + { + assert(overflow < rhs); + if (length) + return view.unsigned.opOpAssign!op(rhs, overflow); + return overflow; + } + + /++ + Performs `size_t overflow = (big += overflow) *= fixed` operatrion. + Params: + rhs = unsigned value to multiply by + overflow = initial overflow value + Returns: + unsigned overflow value + +/ + UInt!size opOpAssign(string op : "*", size_t size)(UInt!size rhs, UInt!size overflow = 0) + @safe pure nothrow @nogc + { + if (length == 0) + goto L; + overflow = view.unsigned.opOpAssign!op(rhs, overflow); + if (overflow && length < data.length) + { + L: + static if (size <= 64) + { + auto o = cast(ulong)overflow; + static if (size_t.sizeof == ulong.sizeof) + { + putCoefficient(o); + overflow = UInt!size.init; + } + else + { + putCoefficient(cast(uint)o); + o >>= 32; + if (length < data.length) + { + putCoefficient(cast(uint)o); + o = 0; + } + overflow = UInt!size(o); + } + } + else + { + do + { + putCoefficient(cast(size_t)overflow); + overflow >>= size_t.sizeof * 8; + } + while(overflow && length < data.length); + } + } + return overflow; + } + + /// + ref opOpAssign(string op, size_t size)(UInt!size rhs) + @safe pure nothrow @nogc scope return + if (op == "/" || op == "%") + { + BigInt!(size / 64) bigRhs = rhs; + return this.opOpAssign!op(bigRhs); + } + + /// ditto + ref opOpAssign(string op)(ulong rhs) + @safe pure nothrow @nogc scope return + if (op == "/" || op == "%") + { + BigInt!1 bigRhs = rhs; + return this.opOpAssign!op(bigRhs); + } + + /// ditto + ref opOpAssign(string op)(long rhs) + @safe pure nothrow @nogc scope return + if (op == "/" || op == "%") + { + BigInt!1 bigRhs = rhs; + return this.opOpAssign!op(bigRhs); + } + + /++ + +/ + ref powMod(uint expSize)(scope ref const BigInt!expSize exponent, scope ref const BigInt modulus) + @safe pure nothrow @nogc return scope + in(!exponent.sign) + { + return this.powMod(exponent.view.unsigned, modulus); + } + + ///ditto + ref powMod()(scope BigUIntView!(const size_t) exponent, scope ref const BigInt modulus) + @trusted pure nothrow @nogc return scope + { + pragma(inline, false); + + import mir.ndslice.topology: bitwise; + + if (modulus == 1 || modulus == -1) + { + this.sign = 0; + this.length = 0; + return this; + } + + BigInt!(size64 * 2) bas = void; + bas = this; + BigInt!(size64 * 2) res = void; + res = 1u; + + foreach (b; exponent.coefficients.bitwise[0 .. $ - exponent.ctlz]) + { + bas %= modulus; + if (b) + { + res *= bas; + res %= modulus; + } + bas *= bas; + } + + this = res; + return this; + } + + /// + static if (size64 == 3) + version (mir_bignum_test) + unittest + { + BigInt!3 x = 2; + BigInt!3 e = 10; + BigInt!3 m = 100; + + x.powMod(e, m); + assert(x == 24); + } + + /// + static if (size64 == 3) + version (mir_bignum_test) + unittest + { + BigInt!3 x = 564321; + BigInt!3 e = "13763753091226345046315979581580902400000310"; + BigInt!3 m = "13763753091226345046315979581580902400000311"; + + x.powMod(e, m); + assert(x == 1); + } + + /++ + +/ + ref multiply(uint aSize64, uint bSize64) + ( + scope ref const BigInt!aSize64 a, + scope ref const BigInt!bSize64 b, + ) + @safe pure nothrow @nogc scope return + if (size64 >= aSize64 + bSize64) + { + import mir.utility: max; + import mir.bignum.internal.kernel : multiply, karatsubaRequiredBuffSize; + enum sizeM = ulong.sizeof / size_t.sizeof; + size_t[max(aSize64 * sizeM, bSize64 * sizeM).karatsubaRequiredBuffSize] buffer = void; + this.length = cast(uint) multiply(data, a.coefficients, b.coefficients, buffer); + this.sign = (this.length != 0) & (a.sign ^ b.sign); + return this; + } + + /// + ref divMod(uint divisorSize64, uint remainderSize = size64) + ( + scope ref const BigInt!divisorSize64 divisor, + scope ref BigInt!size64 quotient, + scope ref BigInt!remainderSize remainder, + ) + const @trusted pure nothrow @nogc scope return + if (remainderSize >= divisorSize64) + { + return this.divMod(divisor, quotient, &remainder); + } + + private ref divMod(uint divisorSize64, uint remainderSize = size64) + ( + scope ref const BigInt!divisorSize64 divisor, + scope ref BigInt!size64 quotient, + scope BigInt!remainderSize* remainder = null, + ) + const @trusted pure nothrow @nogc scope return + if (remainderSize >= divisorSize64) + { + import mir.bignum.internal.kernel : divMod, divisionRequiredBuffSize; + + pragma(inline, false); + + if (divisor.length == 0) + assert(0, "Zero BigInt divizor"); + if (divisor.coefficients[$ - 1] == 0) + assert(0, "Denormalized BigInt divizor"); + + if (this.length < divisor.length) + { + if (remainder !is null) + { + if (&this !is remainder) + *remainder = this; + remainder.sign = 0; + } + + static if (size64 == remainderSize) + if ("ient is remainder) + return this; + + quotient.sign = 0; + quotient.length = 0; + + return this; + } + + enum sizeM = ulong.sizeof / size_t.sizeof; + enum vlen = min(divisorSize64, size64); + size_t[divisionRequiredBuffSize(size64 * sizeM, vlen * sizeM)] buffer = void; + + quotient.length = cast(uint) divMod( + quotient.data, + remainder !is null ? remainder.data[] : null, + this.coefficients, + divisor.coefficients, + buffer, + ); + + quotient.sign = (this.sign ^ divisor.sign) && quotient.length; + + if (remainder !is null) + { + remainder.sign = 0; + remainder.length = divisor.length; + remainder.normalize; + } + + return this; + } + + /++ + Performs `this %= rhs` and `this /= rhs` operations. + Params: + rhs = value to divide by + Returns: + remainder or quotient from the truncated division + +/ + ref opOpAssign(string op, size_t rhsSize64)(scope const ref BigInt!rhsSize64 rhs) + @safe pure nothrow @nogc return + if (op == "/" || op == "%") + { + enum isRem = op == "%"; + return this.divMod(rhs, this, isRem ? &this : null); + } + + /++ + Performs `this %= rhs` and `this /= rhs` operations. + Params: + rhs = value to divide by + Returns: + remainder or quotient from the truncated division + +/ + ref opOpAssign(string op : "*", size_t rhsSize64)(scope const ref BigInt!rhsSize64 rhs) + @trusted pure nothrow @nogc return + { + BigInt!(size64 + rhsSize64) c = void; + c.multiply(this, rhs); + this = c; + return this; + } + + /// + static if (size64 == 3) + version (mir_bignum_test) + unittest + { + BigInt!32 x = "236089459999051800787306800176765276560685597708945239133346035689205694959466543423391020917332149321603397284295007899190053323478336179162578944"; + BigInt!32 y = "19095614279677503764429420557677401943131308638097701560446870251856566051181587499424174939645900335127490246389509326965738171086235365599977209919032320327138167362675030414072140005376"; + BigInt!32 z = "4508273263639244391466225874448166762388283627989411942887789415132291146444880491003321910228134369483394456858712486391978856464207606191606690798518090459546799016472580324664149788791167494389789813815605288815981925073283892089331019170542792502117265455020551819803771537458327634120582677504637693661973404860326560198184402944"; + x *= y; + assert(x == z); + } + + /++ + Performs `size_t overflow = big *= fixed` operatrion. + Params: + rhs = unsigned value to multiply by + Returns: + overflow + +/ + bool opOpAssign(string op, size_t rhsSize64)(ref const BigInt!rhsSize64 rhs) + @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + return opOpAssign!op(rhs.view); + } + + /// ditto + bool opOpAssign(string op)(BigIntView!(const size_t) rhs) + @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + sizediff_t diff = length - rhs.coefficients.length; + if (diff < 0) + { + auto oldLength = length; + length = cast(int)rhs.coefficients.length; + coefficients[oldLength .. $] = 0; + } + else + if (rhs.coefficients.length == 0) + return false; + auto thisView = view; + auto overflow = thisView.opOpAssign!op(rhs); + this.sign = thisView.sign; + if (overflow) + { + if (length < data.length) + { + putCoefficient(overflow); + overflow = false; + } + } + else + { + normalize; + } + return overflow; + } + + /// ditto + bool opOpAssign(string op)(ulong rhs) + @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + import mir.ndslice.bignum.fixed: UInt; + return this.opOpAssign!op(rhs.UInt!64.view.signed); + } + + /// ditto + bool opOpAssign(string op)(uint rhs) + @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + return this.opOpAssign!op(ulong(rhs)); + } + + /// ditto + bool opOpAssign(string op)(long rhs) + @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + import mir.bignum.fixed: UInt; + auto sign = rhs < 0; + rhs = sign ? -rhs : rhs; + return this.opOpAssign!op(rhs.UInt!64.view.normalized.signed(sign)); + } + + /// ditto + bool opOpAssign(string op)(int rhs) + @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + return this.opOpAssign!op(long(rhs)); + } + + /++ + +/ + static BigInt fromHexString(bool allowUnderscores = false)(scope const(char)[] str) + @trusted pure + { + BigInt ret = void; + if (ret.fromHexStringImpl!(char, allowUnderscores)(str)) + return ret; + version(D_Exceptions) + { import mir.exception : toMutable; throw hexStringException.toMutable; } + else + assert(0, hexStringErrorMsg); + } + + /++ + +/ + bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) + @safe pure @nogc nothrow + if (isSomeChar!C) + { + auto work = BigIntView!size_t(data); + auto ret = work.fromHexStringImpl!(C, allowUnderscores)(str); + length = cast(uint)work.unsigned.coefficients.length; + sign = work.sign; + return ret; + } + + /++ + +/ + static BigInt fromBinaryString(bool allowUnderscores = false)(scope const(char)[] str) + @trusted pure + { + BigInt ret = void; + if (ret.fromBinaryStringImpl!(char, allowUnderscores)(str)) + return ret; + version(D_Exceptions) + { import mir.exception : toMutable; throw binaryStringException.toMutable; } + else + assert(0, binaryStringErrorMsg); + } + + static if (size64 == 3) + /// + version(mir_bignum_test) @safe pure @nogc unittest + { + BigInt!4 integer = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; // constructor + assert(integer == BigInt!4.fromBinaryString("-100101100110001001110110010001110101010010101100000111000011011000010011000010111111000100111001011111001101101111101010100011000001000011000001110001110011010011001001011101010010010101101001010101111011101001111101110011101111110010011100000010110111000")); + } + + /++ + +/ + bool fromBinaryStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) + @safe pure @nogc nothrow + if (isSomeChar!C) + { + auto work = BigIntView!size_t(data); + auto ret = work.fromBinaryStringImpl!(C, allowUnderscores)(str); + length = cast(uint)work.unsigned.coefficients.length; + sign = work.sign; + return ret; + } + + /// + ref pow()(ulong degree) + { + BigInt!size64 bas = void; + bas = this; + this = 1u; + + while (degree) + { + if (degree & 1) + this *= bas; + bas *= bas; + degree >>= 1; + } + return this; + } + + /// + bool mulPow5()(ulong degree) + { + import mir.bignum.internal.dec2float: MaxWordPow5; + // assert(approxCanMulPow5(degree)); + if (length == 0) + return false; + enum n = MaxWordPow5!size_t; + enum wordInit = size_t(5) ^^ n; + size_t word = wordInit; + size_t overflow; + + while(degree) + { + if (degree >= n) + { + degree -= n; + } + else + { + word = 1; + do word *= 5; + while(--degree); + } + overflow |= this *= word; + } + return overflow != 0; + } + + /// + ref BigInt opOpAssign(string op)(size_t shift) + @safe pure nothrow @nogc return + if (op == "<<" || op == ">>") + { + auto index = shift / (size_t.sizeof * 8); + auto bs = shift % (size_t.sizeof * 8); + auto ss = size_t.sizeof * 8 - bs; + static if (op == ">>") + { + if (index >= length) + { + length = 0; + return this; + } + auto d = view.coefficients; + if (bs) + { + foreach (j; 0 .. d.length - (index + 1)) + { + d[j] = (d[j + index] >>> bs) | (d[j + (index + 1)] << ss); + } + } + else + { + foreach (j; 0 .. d.length - (index + 1)) + { + d[j] = d[j + index]; + } + } + auto most = d[$ - (index + 1)] = d[$ - 1] >>> bs; + length -= index + (most == 0); + } + else + { + if (index >= data.length || length == 0) + { + length = 0; + return this; + } + + if (bs) + { + auto most = coefficients[$ - 1] >> ss; + length += index; + if (length < data.length) + { + if (most) + { + length++; + coefficients[$ - 1] = most; + length--; + } + } + else + { + length = data.length; + } + + auto d = view.coefficients; + foreach_reverse (j; index + 1 .. length) + { + d[j] = (d[j - index] << bs) | (d[j - (index + 1)] >> ss); + } + d[index] = d[0] << bs; + if (length < data.length) + length += most != 0; + } + else + { + length = cast(uint) min(length + index, cast(uint)data.length); + auto d = view.coefficients; + foreach_reverse (j; index .. length) + { + d[j] = d[j - index]; + } + } + view.coefficients[0 .. index] = 0; + } + return this; + } + + /// + T opCast(T)() const + if (isFloatingPoint!T && isMutable!T) + { + return view.opCast!T; + } + + /// + T opCast(T, bool nonZero = false)() const + if (is(T == long) || is(T == int)) + { + return this.view.opCast!(T, nonZero); + } + + /++ + Returns: overflow flag + +/ + bool copyFrom(W)(scope const(W)[] coefficients, bool sign = false) + if (__traits(isUnsigned, W)) + { + static if (W.sizeof > size_t.sizeof) + { + return this.copyFrom(cast(BigIntView!(const size_t))view, sign); + } + else + { + this.sign = sign; + auto dest = cast(W[])data; + auto overflow = dest.length < coefficients.length; + auto n = overflow ? dest.length : coefficients.length; + this.length = cast(uint)(n / (size_t.sizeof / W.sizeof)); + dest[0 .. n] = coefficients[0 .. n]; + static if (size_t.sizeof / W.sizeof > 1) + { + if (auto tail = n % (size_t.sizeof / W.sizeof)) + { + this.length++; + auto shift = ((size_t.sizeof / W.sizeof) - tail) * (W.sizeof * 8); + auto value = this.coefficients[$ - 1]; + value <<= shift; + value >>= shift; + this.coefficients[$ - 1] = value; + } + } + return overflow; + } + } + + /// + immutable(C)[] toString(C = char)() scope const @safe pure nothrow + if(isSomeChar!C && isMutable!C) + { + C[ceilLog10Exp2(data.length * (size_t.sizeof * 8)) + 1] buffer = void; + BigInt copy = this; + auto len = copy.view.unsigned.toStringImpl(buffer); + if (sign) + buffer[$ - ++len] = '-'; + return buffer[$ - len .. $].idup; + } + + static if (size64 == 3) + /// + version(mir_bignum_test) @safe pure unittest + { + auto str = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; + auto integer = BigInt!4(str); + assert(integer.toString == str); + + integer = BigInt!4.init; + assert(integer.toString == "0"); + } + + /// + void toString(C = char, W)(ref scope W w) scope const + if(isSomeChar!C && isMutable!C) + { + C[ceilLog10Exp2(data.length * (size_t.sizeof * 8)) + 1] buffer = void; + BigInt copy = this; + auto len = copy.view.unsigned.toStringImpl(buffer); + if (sign) + buffer[$ - ++len] = '-'; + w.put(buffer[$ - len .. $]); + } + + /// + size_t bitLength()() const @property + { + return length == 0 ? 0 : length * size_t.sizeof * 8 - data[length - 1].ctlz; + } + + /// + size_t ctlz()() const @property + { + return data.sizeof * 8 - bitLength; + } +} + +/// Check @nogc toString impl +version(mir_bignum_test) @safe pure @nogc unittest +{ + import mir.format; + auto str = "-34010447314490204552169750449563978034784726557588085989975288830070948234680"; + auto integer = BigInt!4(str); + auto buffer = stringBuf; + buffer << integer; + assert(buffer.data == str); +} + +/// +version(mir_bignum_test) +unittest +{ + import mir.test; + import mir.bignum.fixed; + import mir.bignum.low_level_view; + + { + auto a = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); + auto b = UInt!128.fromHexString("f79a222050aaeaaa417fa25a2ac93291"); + + // ca3d7e25aebe687b 168dcef32d0bb2f0 + auto c = BigInt!4.fromHexString("bf4c87424431d21563f23b1fc00d75ac"); + a %= b; + a.should == c; + a = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); + a /= b; + assert(a == BigInt!4.fromHexString("ca3d7e25aebe687b7cc1b250b44690fb")); + } + + { + auto a = BigInt!4.fromHexString("7fff000080000000000000000000"); + auto b = UInt!128.fromHexString("80000000000000000001"); + + auto c = BigInt!4.fromHexString("fffe0000"); + a /= b; + a.should == c; + + a = BigInt!4.fromHexString("7fff000080000000000000000000"); + assert((a %= b) == BigInt!4.fromHexString("7fffffffffff00020000")); + } + + { + auto a = BigInt!16.fromHexString("76d053cdcc87ec8c9455c375d6a08c799fad73cf07415e70af5dfacaff4bd306647a7cceb98839cce89ae65900938821564fd2af3c9d881c172264bb17e3530ce79b938d5eb7ffec558be43ab5b684978417c5053fb8df63fc65c9efd8b2e86469c53259509eb597f81647930f24ef05a79bfecf04e5ec52414c6a3f7481d533"); + auto b = UInt!128.fromHexString("9c5c1aa6ad7ad18065a3a74598e27bee"); + + assert((a /= b) == BigInt!16.fromHexString("c2871f2b07522bda1e63de12850d2208bb242c716b5739d6744ee1d9c937b8d765d3742e18785d08c2405e5c83f3c875d5726d09dfaee29e813675a4f91bfee01e8cbbbca9588325d54cf2a625faffde4d8709e0517f786f609d8ce6997e0e71d2f976ae169b0c4be7a7dba3135af96c")); + a = BigInt!16.fromHexString("76d053cdcc87ec8c9455c375d6a08c799fad73cf07415e70af5dfacaff4bd306647a7cceb98839cce89ae65900938821564fd2af3c9d881c172264bb17e3530ce79b938d5eb7ffec558be43ab5b684978417c5053fb8df63fc65c9efd8b2e86469c53259509eb597f81647930f24ef05a79bfecf04e5ec52414c6a3f7481d533"); + assert((a %= b) == BigInt!16.fromHexString("85d81587a8b62af1874315d26ebf0ecb")); + } + + { + auto a = BigInt!4.fromHexString("DEADBEEF"); + auto b = UInt!256.fromHexString("18aeff9fa4aace484a9f8f9002cdf38fa6e53fc0f6c035051dc86931c1c08316"); + + assert((a /= b) == 0); + a = BigInt!4.fromHexString("DEADBEEF"); + assert((a %= b) == 0xDEADBEEF); + } + + void test(const long av, const long bv) + { + auto a = BigInt!4(av); + const b = BigInt!4(bv); + a /= b; + assert(a == av / bv); + a = BigInt!4(av); + a %= b; + assert(a == av % bv); + } + + { + auto av = 0xDEADBEEF; + auto bv = 0xDEAD; + test(+av, +bv); + // test(+av, -bv); + // test(-av, +bv); + // test(+av, +bv); + } +} + +/// +version(mir_bignum_test) +unittest +{ + import mir.bignum.fixed; + import mir.bignum.low_level_view; + + auto a = BigInt!4.fromHexString("4b313b23aa560e1b0985f89cbe6df5460860e39a64ba92b4abdd3ee77e4e05b8"); + auto b = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); + auto c = BigInt!4.fromHexString("7869dd864619cace5953a09910327b3971413e6aa5f417fa25a2ac93291b941f"); + c.sign = true; + assert(a != b); + assert(a < b); + a -= b; + assert(a.sign); + assert(a == c); + a -= a; + assert(!a.sign); + assert(!a.length); + + auto d = BigInt!4.fromHexString("0de1a911c6dc8f90a7169a148e65d22cf34f6a8254ae26362b064f26ac44218a"); + assert((b *= 0x7869dd86) == 0x5c019770); + assert(b == d); + + d = BigInt!4.fromHexString("856eeb23e68cc73f2a517448862cdc97e83f9dfa23768296724bf00fda7df32a"); + auto o = b *= UInt!128.fromHexString("f79a222050aaeaaa417fa25a2ac93291"); + assert(o == UInt!128.fromHexString("d6d15b99499b73e68c3331eb0f7bf16")); + assert(b == d); + + d = BigInt!4.fromHexString("d"); // initial value + d.mulPow5(60); + c = BigInt!4.fromHexString("81704fcef32d3bd8117effd5c4389285b05d"); + assert(d == c); + + d >>= 80; + c = BigInt!4.fromHexString("81704fcef32d3bd8"); + assert(d == c); + + c = BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"); + d = BigInt!4.fromHexString("9935cea0707f79a222050aaeaaaed17feb7aa76999d700000000000000000000"); + c <<= 80; + assert(d == c); + c >>= 80; + c <<= 84; + d <<= 4; + assert(d == c); + assert(c != b); + b.sign = true; + assert(!c.copyFrom(b.coefficients, b.sign)); + assert(c == b); + b >>= 18; + auto bView = cast(BigIntView!ushort)b.view; + assert(!c.copyFrom(bView.coefficients[0 .. $ - 1], bView.sign)); + assert(c == b); +} + +version(mir_bignum_test) +@safe pure @nogc unittest +{ + BigInt!4 i = "-0"; + assert(i.coefficients.length == 0); + assert(!i.sign); + assert(cast(long) i == 0); +} diff --git a/source/mir/bignum/internal/dec2float.d b/source/mir/bignum/internal/dec2float.d new file mode 100644 index 00000000..9772e77e --- /dev/null +++ b/source/mir/bignum/internal/dec2float.d @@ -0,0 +1,585 @@ +module mir.bignum.internal.dec2float; + +version (LDC) import ldc.attributes: optStrategy; +else struct optStrategy { string opt; } + +template MaxWordPow5(T) +{ + static if (is(T == ubyte)) + enum MaxWordPow5 = 3; + else + static if (is(T == ushort)) + enum MaxWordPow5 = 6; + else + static if (is(T == uint)) + enum MaxWordPow5 = 13; + else + static if (is(T == ulong)) + enum MaxWordPow5 = 27; + else + static assert(0); +} + +template MaxFpPow5(T) +{ + static if (T.mant_dig == 24) + enum MaxFpPow5 = 6; + else + static if (T.mant_dig == 53) + enum MaxFpPow5 = 10; + else + static if (T.mant_dig == 64) + enum MaxFpPow5 = 27; + else + static if (T.mant_dig == 113) + enum MaxFpPow5 = 48; + else + static assert(0, "floating point format isn't supported"); +} + +@safe pure nothrow @nogc: + +alias decimalTo(T : float) = decimalToFloat32; +alias decimalTo(T : double) = decimalToFloat64; +alias decimalTo(T : real) = decimalToReal; + +alias binaryTo(T : float) = binaryToFloat32; +alias binaryTo(T : double) = binaryToFloat64; +alias binaryTo(T : real) = binaryToReal; + +private float binaryToFloat32(scope const size_t[] coefficients, long exponent = 0) +{ + pragma(inline, false); + return binaryToFloatImpl!float(coefficients, exponent); +} + +private double binaryToFloat64(scope const size_t[] coefficients, long exponent = 0) +{ + pragma(inline, false); + return binaryToFloatImpl!double(coefficients, exponent); +} + +private real binaryToReal(scope const size_t[] coefficients, long exponent = 0) +{ + pragma(inline, real.mant_dig == double.mant_dig); + static if (real.mant_dig == double.mant_dig) + return binaryToFloat64(coefficients, exponent); + else + return binaryToFloatImpl!real(coefficients, exponent); +} + +private float decimalToFloat32(scope const ulong coefficient, long exponent) +{ + pragma(inline, false); + return decimalToFloatImpl!float(coefficient, exponent); +} + +private double decimalToFloat64(scope const ulong coefficient, long exponent) +{ + pragma(inline, false); + return decimalToFloatImpl!double(coefficient, exponent); +} + +private real decimalToReal(scope const ulong coefficient, long exponent) +{ + pragma(inline, real.mant_dig == double.mant_dig); + static if (real.mant_dig == double.mant_dig) + return decimalToFloat64(coefficient, exponent); + else + return decimalToFloatImpl!real(coefficient, exponent); +} + +private float decimalToFloat32(scope const size_t[] coefficients, long exponent) +{ + pragma(inline, false); + return decimalToFloatImpl!float(coefficients, exponent); +} + +private double decimalToFloat64(scope const size_t[] coefficients, long exponent) +{ + pragma(inline, false); + return decimalToFloatImpl!double(coefficients, exponent); +} + +private real decimalToReal(scope const size_t[] coefficients, long exponent) +{ + pragma(inline, real.mant_dig == double.mant_dig); + static if (real.mant_dig == double.mant_dig) + return decimalToFloat64(coefficients, exponent); + else + return decimalToFloatImpl!real(coefficients, exponent); +} + +T decimalToFloatImpl(T)(ulong coefficient, long exponent) + if (is(T == float) || is(T == double) || is(T == real)) +{ + version (LDC) + pragma(inline, true); + + import mir.bignum.fixed: UInt; + import mir.bignum.fp: Fp, extendedMul; + import mir.utility: _expect; + + enum wordBits = T.mant_dig < 64 ? 64 : 128; + enum ulong half = (1UL << (wordBits - T.mant_dig - 1)); + enum bigHalf = UInt!128([0UL, half]); + static if (T.mant_dig < 64) + enum bigMask = (UInt!128(1UL) << (64 - T.mant_dig)) - 1; + + static if (T.mant_dig > 64) + enum ulong mask = (1UL << (128 - T.mant_dig)) - 1; + else + static if (T.mant_dig == 64) + enum ulong mask = ulong.max; + else + enum ulong mask = (1UL << (64 - T.mant_dig)) - 1; + + if (coefficient == 0) + return 0; + + version (TeslAlgoM) {} else + if (_expect(-ExponentM <= exponent && exponent <= ExponentM, true)) + { + auto c = coefficient.Fp!64; + + T approx = void; + + if (exponent < 0) + { + version (all) + {{ + auto z = c.extendedMul!true(_load!wordBits(exponent)); + approx = z.opCast!(T, true); + long bitsDiff = (cast(ulong) z.opCast!(Fp!wordBits).coefficient & mask) - half; + uint slop = 3; + if (_expect(approx > T.min_normal && (bitsDiff < 0 ? -bitsDiff : bitsDiff) > slop, true)) + return approx; + }} + + if (-exponent <= MaxFpPow5!T) + { + auto e = _load!wordBits(-exponent); + return coefficient / e.opCast!(T, true); + } + + static if (T.mant_dig < 64) + {{ + auto z = c.extendedMul!true(_load!128(exponent)); + approx = z.opCast!(T, true); + auto bitsDiff = (z.opCast!(Fp!128).coefficient & bigMask) - bigHalf; + if (bitsDiff.signBit) + bitsDiff = UInt!128.init - bitsDiff; + uint slop = 3; + if (_expect(approx > T.min_normal && bitsDiff > slop, true)) + return approx; + }} + + } + else + { + version (all) + {{ + auto z = c.extendedMul!true(_load!wordBits(exponent)); + approx = z.opCast!(T, true); + + if (exponent <= 27) // exact exponent + { + return approx; + } + + long bitsDiff = (cast(ulong) z.opCast!(Fp!wordBits).coefficient & mask) - half; + uint slop; + if (_expect(approx > T.min_normal && (bitsDiff < 0 ? -bitsDiff : bitsDiff) > slop, true)) + return approx; + }} + + static if (T.mant_dig < 64) + {{ + auto z = c.extendedMul!true(_load!128(exponent)); + approx = z.opCast!(T, true); + + if (exponent <= 55) // exact exponent + { + return approx; + } + + auto bitsDiff = (z.opCast!(Fp!128).coefficient & bigMask) - bigHalf; + if (bitsDiff.signBit) + bitsDiff = UInt!128.init - bitsDiff; + uint slop; + if (_expect(approx > T.min_normal && bitsDiff > slop, true)) + return approx; + }} + + } + } + size_t[ulong.sizeof / size_t.sizeof] coefficients; + coefficients[0] = cast(size_t) coefficient; + static if (coefficients.length == 2) + coefficients[1] = cast(size_t) (coefficient >> 32); + static if (coefficients.length == 1) + return algorithmM!T(coefficients, exponent); + else + return algorithmM!T(coefficients[0 .. 1 + (coefficient > uint.max)], exponent); +} + +private T decimalToFloatImpl(T)(scope const size_t[] coefficients, long exponent) + @safe + if ((is(T == float) || is(T == double) || is(T == real))) + in (coefficients.length == 0 || coefficients[$ - 1]) +{ + version (LDC) + pragma(inline, true); + + import mir.bignum.fixed: UInt; + import mir.bignum.fp: Fp, extendedMul; + import mir.utility: _expect; + + enum wordBits = T.mant_dig < 64 ? 64 : 128; + enum ulong half = (1UL << (wordBits - T.mant_dig - 1)); + static if (T.mant_dig > 64) + enum ulong mask = (1UL << (128 - T.mant_dig)) - 1; + else + static if (T.mant_dig == 64) + enum ulong mask = ulong.max; + else + enum ulong mask = (1UL << (64 - T.mant_dig)) - 1; + + if (coefficients.length < 1) + return 0; + + if (coefficients.length == 1) + return decimalTo!T(coefficients[0], exponent); + + static if (size_t.sizeof == uint.sizeof) + { + if (coefficients.length == 2) + return decimalTo!T(coefficients[0] | (ulong(coefficients[1]) << 32), exponent); + } + + version (TeslAlgoM) {} else + if (_expect(-ExponentM <= exponent && exponent <= ExponentM, true)) + { + auto c = coefficients.binaryToFp!wordBits; + auto z = c.extendedMul!true(_load!wordBits(exponent)); + auto approx = z.opCast!(T, true); + auto slop = 1 + 3 * (exponent < 0); + long bitsDiff = (cast(ulong) z.opCast!(Fp!wordBits).coefficient & mask) - half; + + if (_expect(approx > T.min_normal && (bitsDiff < 0 ? -bitsDiff : bitsDiff) > slop, true)) + return approx; + } + return algorithmM!T(coefficients, exponent); +} + +private enum LOG2_10 = 0x3.5269e12f346e2bf924afdbfd36bf6p0; + +private template bigSize(T) + if ((is(T == float) || is(T == double) || is(T == real))) +{ + static if (T.mant_dig < 64) + { + enum size_t bigSize = 128; + } + else + { + enum size_t bits = T.max_exp - T.min_exp + T.mant_dig; + enum size_t bigSize = bits / 64 + bits % 64 + 1; + } +} + +@optStrategy("minsize") @trusted +private T algorithmM(T)(scope const size_t[] coefficients, long exponent) + if ((is(T == float) || is(T == double) || is(T == real))) + in (coefficients.length) +{ + pragma(inline, false); + + import mir.bitop: ctlz; + import mir.bignum.fp: Fp; + import mir.bignum.integer: BigInt; + import mir.math.common: log2, ceil; + import mir.math.ieee: ldexp, nextUp; + + BigInt!(bigSize!T) u = void; + BigInt!(bigSize!T) v = void; + BigInt!(bigSize!T) q = void; + BigInt!(bigSize!T) r = void; + + if (coefficients.length == 0) + return 0; + + // if no overflow + if (exponent >= 0) + { + if (3 * exponent + coefficients.length * size_t.sizeof * 8 - ctlz(coefficients[$ - 1]) - 1 > T.max_exp) + return T.infinity; + if (exponent == 0) + return coefficients.binaryTo!T; + u.copyFrom(coefficients); + u.mulPow5(exponent); + return u.coefficients.binaryTo!T(exponent); + } + + auto log2_u = coefficients.binaryTo!T.log2; + auto log2_v = cast(T)(-LOG2_10) * exponent; + sizediff_t k = cast(sizediff_t) ceil(log2_u - log2_v); + + k -= T.mant_dig; + + if (k < T.min_exp - T.mant_dig) + { + if (k + T.mant_dig + 1 < T.min_exp - T.mant_dig) + return 0; + k = T.min_exp - T.mant_dig; + } + else + if (k > T.max_exp) + { + if (k - 2 > T.max_exp) + return T.infinity; + k = T.max_exp; + } + + if(u.copyFrom(coefficients)) + return T.nan; + if (k < 0) + { + if (u.ctlz < -k) + return T.nan; + u <<= -k; + } + + if (log2_v >= bigSize!T * 64) + return T.nan; + + v = 1; + v.mulPow5(-exponent); + v <<= cast(int)-exponent + (k > 0 ? k : 0); + + sizediff_t s; + for(;;) + { + u.divMod(v, q, r); + + s = cast(int) q.bitLength - T.mant_dig; + assert(k >= T.min_exp - T.mant_dig); + if (s == 0) + break; + + if (s < 0) + { + if (k == T.min_exp - T.mant_dig) + break; + k--; + } + else + { + if (k == T.max_exp) + return T.infinity; + k++; + } + + if ((s < 0 ? u : v).ctlz == 0) + return T.nan; + (s < 0 ? u : v) <<= 1; + } + + sizediff_t cmp; + if (s <= 0) + { + u = v; + u -= r; + cmp = r.opCmp(u); + } + else + { + cmp = s - 1 - q.view.unsigned.cttz; + if (cmp == 0) // half + cmp += r != 0; + q >>= s; + k += s; + } + auto z = q.coefficients.binaryTo!T.ldexp(cast(int)k); + return cmp < 0 || cmp == 0 && !q.view.unsigned.bt(0) ? z : nextUp(z); +} + +private T binaryToFloatImpl(T)(scope const size_t[] coefficients, long exponent) + @safe + if ((is(T == float) || is(T == double) || is(T == real))) + in (coefficients.length == 0 || coefficients[$ - 1]) +{ + version (LDC) + pragma(inline, true); + + enum md = T.mant_dig; + enum b = size_t.sizeof * 8; + enum n = md / b + (md % b != 0); + enum s = n * b; + + if (coefficients.length == 0) + return 0; + + if (exponent > T.max_exp) + return T.infinity; + + auto fp = coefficients.binaryToFp!(s, s - md); + fp.exponent += exponent; + return fp.opCast!(T, true, true); +} + + +package(mir.bignum) auto binaryToFp(uint coefficientSize, uint internalRoundLastBits = 0) + (scope const(size_t)[] coefficients) + if (internalRoundLastBits < size_t.sizeof * 8) + in (coefficients.length) + in (coefficients[$ - 1]) +{ + import mir.bignum.fixed: UInt; + import mir.bignum.fp: Fp; + import mir.bitop: ctlz; + import mir.utility: _expect; + + version (LDC) + pragma(inline, true); + + Fp!coefficientSize ret; + + enum N = ret.coefficient.data.length; + sizediff_t size = coefficients.length * (size_t.sizeof * 8); + sizediff_t expShift = size - coefficientSize; + ret.exponent = expShift; + if (_expect(expShift <= 0, true)) + { + static if (N == 1) + { + ret.coefficient.data[0] = coefficients[$ - 1]; + } + else + { + ret.coefficient.data[$ - coefficients.length .. $] = coefficients; + } + auto c = cast(uint) ctlz(ret.coefficient.view.coefficients[$ - 1]); + ret.exponent -= c; + ret.coefficient = ret.coefficient.smallLeftShift(c); + } + else + { + UInt!(coefficientSize + size_t.sizeof * 8) holder; + + static if (N == 1) + { + holder.data[0] = coefficients[$ - 2]; + holder.data[1] = coefficients[$ - 1]; + } + else + { + import mir.utility: min; + auto minLength = min(coefficients.length, holder.data.length); + holder.data[$ - minLength .. $] = coefficients[$ - minLength .. $]; + } + + auto c = cast(uint) ctlz(holder.data[$ - 1]); + ret.exponent -= c; + holder = holder.smallLeftShift(c); + ret.coefficient = holder.toSize!(coefficientSize, false); + auto tail = holder.data[0]; + + bool nonZeroTail() + { + while(_expect(coefficients[0] == 0, false)) + { + coefficients = coefficients[1 .. $]; + assert(coefficients.length); + } + return coefficients.length > N + 1; + } + + static if (internalRoundLastBits) + { + enum half = size_t(1) << (internalRoundLastBits - 1); + enum mask0 = (size_t(1) << internalRoundLastBits) - 1; + auto tail0 = ret.coefficient.data[0] & mask0; + ret.coefficient.data[0] &= ~mask0; + auto condInc = tail0 >= half + && ( tail0 > half + || tail + || (ret.coefficient.data[0] & 1) + || nonZeroTail); + } + else + { + enum half = cast(size_t)sizediff_t.min; + auto condInc = tail >= half + && ( tail > half + || (ret.coefficient.data[0] & 1) + || nonZeroTail); + } + + if (condInc) + { + enum inc = size_t(1) << internalRoundLastBits; + if (auto overflow = ret.coefficient += inc) + { + import mir.bignum.fp: half; + ret.coefficient = half!coefficientSize; + ret.exponent++; + } + } + } + return ret; +} + +private enum ExponentM = 512; + +private auto _load(uint size : 64)(long e) @trusted + in (-ExponentM < e && e < ExponentM) +{ + version (LDC) + pragma(inline, true); + + import mir.bignum.fixed: UInt; + import mir.bignum.fp: Fp; + import mir.bignum.internal.dec2float_table; + + auto idx = cast(sizediff_t)e - min_p10_e; + auto p10coeff = p10_coefficients_h[idx]; + auto p10exp = p10_exponents[idx]; + return Fp!64(false, p10exp, UInt!64(p10coeff)); +} + +private auto _load(uint size : 128)(long e) @trusted + in (-ExponentM < e && e < ExponentM) +{ + version (LDC) + pragma(inline, true); + + import mir.bignum.fixed: UInt; + import mir.bignum.fp: Fp; + import mir.bignum.internal.dec2float_table; + + static assert(min_p10_e <= -ExponentM); + static assert(max_p10_e >= ExponentM); + auto idx = cast(sizediff_t)e - min_p10_e; + ulong h = p10_coefficients_h[idx]; + ulong l = p10_coefficients_l[idx]; + if (l >= cast(ulong)long.min) + h--; + auto p10coeff = UInt!128(cast(ulong[2])[l, h]); + auto p10exp = p10_exponents[idx] - 64; + return Fp!128(false, p10exp, p10coeff); +} + +version(mir_test) +unittest +{ + import mir.bignum.fp; + import mir.bignum.fixed; + import mir.test; + ulong[2] data = [ulong.max - 2, 1]; + auto coefficients = cast(size_t[])data[]; + if (coefficients[$ - 1] == 0) + coefficients = coefficients[0 .. $ - 1]; + coefficients.binaryToFp!64.should == Fp!64(false, 1, UInt!64(0xFFFFFFFFFFFFFFFE)); + coefficients.binaryToFp!128.should == Fp!128(false, -63, UInt!128([0x8000000000000000, 0xFFFFFFFFFFFFFFFE])); +} diff --git a/source/mir/bignum/internal/dec2float_table.d b/source/mir/bignum/internal/dec2float_table.d new file mode 100644 index 00000000..dc7a6187 --- /dev/null +++ b/source/mir/bignum/internal/dec2float_table.d @@ -0,0 +1,3092 @@ +/++ +Tables of approximations of powers of ten. +DO NOT MODIFY: Generated by `etc/dec2float_table.py` ++/ +module mir.bignum.internal.dec2float_table; + +enum min_p10_e = -512; +enum max_p10_e = 512; + +static immutable short[1025] p10_exponents = [ + -1764, + -1761, + -1758, + -1754, + -1751, + -1748, + -1744, + -1741, + -1738, + -1734, + -1731, + -1728, + -1724, + -1721, + -1718, + -1714, + -1711, + -1708, + -1705, + -1701, + -1698, + -1695, + -1691, + -1688, + -1685, + -1681, + -1678, + -1675, + -1671, + -1668, + -1665, + -1661, + -1658, + -1655, + -1651, + -1648, + -1645, + -1641, + -1638, + -1635, + -1631, + -1628, + -1625, + -1621, + -1618, + -1615, + -1612, + -1608, + -1605, + -1602, + -1598, + -1595, + -1592, + -1588, + -1585, + -1582, + -1578, + -1575, + -1572, + -1568, + -1565, + -1562, + -1558, + -1555, + -1552, + -1548, + -1545, + -1542, + -1538, + -1535, + -1532, + -1528, + -1525, + -1522, + -1519, + -1515, + -1512, + -1509, + -1505, + -1502, + -1499, + -1495, + -1492, + -1489, + -1485, + -1482, + -1479, + -1475, + -1472, + -1469, + -1465, + -1462, + -1459, + -1455, + -1452, + -1449, + -1445, + -1442, + -1439, + -1435, + -1432, + -1429, + -1425, + -1422, + -1419, + -1416, + -1412, + -1409, + -1406, + -1402, + -1399, + -1396, + -1392, + -1389, + -1386, + -1382, + -1379, + -1376, + -1372, + -1369, + -1366, + -1362, + -1359, + -1356, + -1352, + -1349, + -1346, + -1342, + -1339, + -1336, + -1332, + -1329, + -1326, + -1323, + -1319, + -1316, + -1313, + -1309, + -1306, + -1303, + -1299, + -1296, + -1293, + -1289, + -1286, + -1283, + -1279, + -1276, + -1273, + -1269, + -1266, + -1263, + -1259, + -1256, + -1253, + -1249, + -1246, + -1243, + -1239, + -1236, + -1233, + -1229, + -1226, + -1223, + -1220, + -1216, + -1213, + -1210, + -1206, + -1203, + -1200, + -1196, + -1193, + -1190, + -1186, + -1183, + -1180, + -1176, + -1173, + -1170, + -1166, + -1163, + -1160, + -1156, + -1153, + -1150, + -1146, + -1143, + -1140, + -1136, + -1133, + -1130, + -1127, + -1123, + -1120, + -1117, + -1113, + -1110, + -1107, + -1103, + -1100, + -1097, + -1093, + -1090, + -1087, + -1083, + -1080, + -1077, + -1073, + -1070, + -1067, + -1063, + -1060, + -1057, + -1053, + -1050, + -1047, + -1043, + -1040, + -1037, + -1034, + -1030, + -1027, + -1024, + -1020, + -1017, + -1014, + -1010, + -1007, + -1004, + -1000, + -997, + -994, + -990, + -987, + -984, + -980, + -977, + -974, + -970, + -967, + -964, + -960, + -957, + -954, + -950, + -947, + -944, + -940, + -937, + -934, + -931, + -927, + -924, + -921, + -917, + -914, + -911, + -907, + -904, + -901, + -897, + -894, + -891, + -887, + -884, + -881, + -877, + -874, + -871, + -867, + -864, + -861, + -857, + -854, + -851, + -847, + -844, + -841, + -838, + -834, + -831, + -828, + -824, + -821, + -818, + -814, + -811, + -808, + -804, + -801, + -798, + -794, + -791, + -788, + -784, + -781, + -778, + -774, + -771, + -768, + -764, + -761, + -758, + -754, + -751, + -748, + -744, + -741, + -738, + -735, + -731, + -728, + -725, + -721, + -718, + -715, + -711, + -708, + -705, + -701, + -698, + -695, + -691, + -688, + -685, + -681, + -678, + -675, + -671, + -668, + -665, + -661, + -658, + -655, + -651, + -648, + -645, + -642, + -638, + -635, + -632, + -628, + -625, + -622, + -618, + -615, + -612, + -608, + -605, + -602, + -598, + -595, + -592, + -588, + -585, + -582, + -578, + -575, + -572, + -568, + -565, + -562, + -558, + -555, + -552, + -549, + -545, + -542, + -539, + -535, + -532, + -529, + -525, + -522, + -519, + -515, + -512, + -509, + -505, + -502, + -499, + -495, + -492, + -489, + -485, + -482, + -479, + -475, + -472, + -469, + -465, + -462, + -459, + -455, + -452, + -449, + -446, + -442, + -439, + -436, + -432, + -429, + -426, + -422, + -419, + -416, + -412, + -409, + -406, + -402, + -399, + -396, + -392, + -389, + -386, + -382, + -379, + -376, + -372, + -369, + -366, + -362, + -359, + -356, + -353, + -349, + -346, + -343, + -339, + -336, + -333, + -329, + -326, + -323, + -319, + -316, + -313, + -309, + -306, + -303, + -299, + -296, + -293, + -289, + -286, + -283, + -279, + -276, + -273, + -269, + -266, + -263, + -259, + -256, + -253, + -250, + -246, + -243, + -240, + -236, + -233, + -230, + -226, + -223, + -220, + -216, + -213, + -210, + -206, + -203, + -200, + -196, + -193, + -190, + -186, + -183, + -180, + -176, + -173, + -170, + -166, + -163, + -160, + -157, + -153, + -150, + -147, + -143, + -140, + -137, + -133, + -130, + -127, + -123, + -120, + -117, + -113, + -110, + -107, + -103, + -100, + -97, + -93, + -90, + -87, + -83, + -80, + -77, + -73, + -70, + -67, + -63, + -60, + -57, + -54, + -50, + -47, + -44, + -40, + -37, + -34, + -30, + -27, + -24, + -20, + -17, + -14, + -10, + -7, + -4, + 0, + 3, + 6, + 10, + 13, + 16, + 20, + 23, + 26, + 30, + 33, + 36, + 39, + 43, + 46, + 49, + 53, + 56, + 59, + 63, + 66, + 69, + 73, + 76, + 79, + 83, + 86, + 89, + 93, + 96, + 99, + 103, + 106, + 109, + 113, + 116, + 119, + 123, + 126, + 129, + 132, + 136, + 139, + 142, + 146, + 149, + 152, + 156, + 159, + 162, + 166, + 169, + 172, + 176, + 179, + 182, + 186, + 189, + 192, + 196, + 199, + 202, + 206, + 209, + 212, + 216, + 219, + 222, + 226, + 229, + 232, + 235, + 239, + 242, + 245, + 249, + 252, + 255, + 259, + 262, + 265, + 269, + 272, + 275, + 279, + 282, + 285, + 289, + 292, + 295, + 299, + 302, + 305, + 309, + 312, + 315, + 319, + 322, + 325, + 328, + 332, + 335, + 338, + 342, + 345, + 348, + 352, + 355, + 358, + 362, + 365, + 368, + 372, + 375, + 378, + 382, + 385, + 388, + 392, + 395, + 398, + 402, + 405, + 408, + 412, + 415, + 418, + 422, + 425, + 428, + 431, + 435, + 438, + 441, + 445, + 448, + 451, + 455, + 458, + 461, + 465, + 468, + 471, + 475, + 478, + 481, + 485, + 488, + 491, + 495, + 498, + 501, + 505, + 508, + 511, + 515, + 518, + 521, + 524, + 528, + 531, + 534, + 538, + 541, + 544, + 548, + 551, + 554, + 558, + 561, + 564, + 568, + 571, + 574, + 578, + 581, + 584, + 588, + 591, + 594, + 598, + 601, + 604, + 608, + 611, + 614, + 617, + 621, + 624, + 627, + 631, + 634, + 637, + 641, + 644, + 647, + 651, + 654, + 657, + 661, + 664, + 667, + 671, + 674, + 677, + 681, + 684, + 687, + 691, + 694, + 697, + 701, + 704, + 707, + 711, + 714, + 717, + 720, + 724, + 727, + 730, + 734, + 737, + 740, + 744, + 747, + 750, + 754, + 757, + 760, + 764, + 767, + 770, + 774, + 777, + 780, + 784, + 787, + 790, + 794, + 797, + 800, + 804, + 807, + 810, + 813, + 817, + 820, + 823, + 827, + 830, + 833, + 837, + 840, + 843, + 847, + 850, + 853, + 857, + 860, + 863, + 867, + 870, + 873, + 877, + 880, + 883, + 887, + 890, + 893, + 897, + 900, + 903, + 907, + 910, + 913, + 916, + 920, + 923, + 926, + 930, + 933, + 936, + 940, + 943, + 946, + 950, + 953, + 956, + 960, + 963, + 966, + 970, + 973, + 976, + 980, + 983, + 986, + 990, + 993, + 996, + 1000, + 1003, + 1006, + 1009, + 1013, + 1016, + 1019, + 1023, + 1026, + 1029, + 1033, + 1036, + 1039, + 1043, + 1046, + 1049, + 1053, + 1056, + 1059, + 1063, + 1066, + 1069, + 1073, + 1076, + 1079, + 1083, + 1086, + 1089, + 1093, + 1096, + 1099, + 1102, + 1106, + 1109, + 1112, + 1116, + 1119, + 1122, + 1126, + 1129, + 1132, + 1136, + 1139, + 1142, + 1146, + 1149, + 1152, + 1156, + 1159, + 1162, + 1166, + 1169, + 1172, + 1176, + 1179, + 1182, + 1186, + 1189, + 1192, + 1196, + 1199, + 1202, + 1205, + 1209, + 1212, + 1215, + 1219, + 1222, + 1225, + 1229, + 1232, + 1235, + 1239, + 1242, + 1245, + 1249, + 1252, + 1255, + 1259, + 1262, + 1265, + 1269, + 1272, + 1275, + 1279, + 1282, + 1285, + 1289, + 1292, + 1295, + 1298, + 1302, + 1305, + 1308, + 1312, + 1315, + 1318, + 1322, + 1325, + 1328, + 1332, + 1335, + 1338, + 1342, + 1345, + 1348, + 1352, + 1355, + 1358, + 1362, + 1365, + 1368, + 1372, + 1375, + 1378, + 1382, + 1385, + 1388, + 1392, + 1395, + 1398, + 1401, + 1405, + 1408, + 1411, + 1415, + 1418, + 1421, + 1425, + 1428, + 1431, + 1435, + 1438, + 1441, + 1445, + 1448, + 1451, + 1455, + 1458, + 1461, + 1465, + 1468, + 1471, + 1475, + 1478, + 1481, + 1485, + 1488, + 1491, + 1494, + 1498, + 1501, + 1504, + 1508, + 1511, + 1514, + 1518, + 1521, + 1524, + 1528, + 1531, + 1534, + 1538, + 1541, + 1544, + 1548, + 1551, + 1554, + 1558, + 1561, + 1564, + 1568, + 1571, + 1574, + 1578, + 1581, + 1584, + 1587, + 1591, + 1594, + 1597, + 1601, + 1604, + 1607, + 1611, + 1614, + 1617, + 1621, + 1624, + 1627, + 1631, + 1634, + 1637, +]; + +static immutable align(16) ulong[1025] p10_coefficients_h = [ + 0x9049EE32DB23D21C, + 0xB45C69BF91ECC6A4, + 0xE173842F7667F84C, + 0x8CE8329DAA00FB30, + 0xB0223F45148139FC, + 0xDC2ACF1659A1887B, + 0x899AC16DF804F54D, + 0xAC0171C9760632A0, + 0xD701CE3BD387BF48, + 0x866120E56434D78D, + 0xA7F9691EBD420D70, + 0xD1F7C3666C9290CC, + 0x833ADA2003DB9A80, + 0xA40990A804D2811F, + 0xCD0BF4D206072167, + 0x8027790343C474E1, + 0xA031574414B59219, + 0xC83DAD1519E2F69F, + 0xFA4D185A605BB447, + 0x9C702F387C3950AC, + 0xC38C3B069B47A4D7, + 0xF46F49C842198E0D, + 0x98C58E1D294FF8C8, + 0xBEF6F1A473A3F6FA, + 0xEEB4AE0D908CF4B9, + 0x9530ECC87A5818F3, + 0xBA7D27FA98EE1F30, + 0xE91C71F93F29A6FC, + 0x91B1C73BC77A085E, + 0xB61E390AB9588A75, + 0xE3A5C74D67AEAD12, + 0x8E479C9060CD2C2C, + 0xB1D983B479007736, + 0xDE4FE4A197409504, + 0x8AF1EEE4FE885D22, + 0xADAE6A9E3E2A746B, + 0xD91A0545CDB51186, + 0x87B0434BA0912AF4, + 0xA99C541E88B575B1, + 0xD40369262AE2D31D, + 0x848221B7DACDC3F2, + 0xA5A2AA25D18134EE, + 0xCF0B54AF45E1822A, + 0x816714ED8BACF15A, + 0xA1C0DA28EE982DB1, + 0xCA3110B32A3E391D, + 0xFCBD54DFF4CDC764, + 0x9DF6550BF9009C9F, + 0xC573EA4EF740C3C6, + 0xF6D0E4E2B510F4B8, + 0x9A428F0DB12A98F3, + 0xC0D332D11D753F30, + 0xF107FF8564D28EFC, + 0x96A4FFB35F03995D, + 0xBC4E3FA036C47FB5, + 0xEB61CF8844759FA2, + 0x931D21B52AC983C5, + 0xB7E46A22757BE4B6, + 0xE5DD84AB12DADDE4, + 0x8FAA72EAEBC8CAAF, + 0xB3950FA5A6BAFD5A, + 0xE07A538F1069BCB1, + 0x8C4C74396A4215EE, + 0xAF5F9147C4D29B6A, + 0xDB377599B6074245, + 0x8902A98011C4896B, + 0xAB4353E01635ABC6, + 0xD61428D81BC316B7, + 0x85CC99871159EE32, + 0xA73FBFE8D5B069BF, + 0xD10FAFE30B1C842F, + 0x82A9CDEDE6F1D29D, + 0xA354416960AE4744, + 0xCC2951C3B8D9D916, + 0xFF33A634A7104F5B, + 0x9F8047E0E86A3199, + 0xC76059D92284BDFF, + 0xF938704F6B25ED7F, + 0x9BC34631A2F7B46F, + 0xC2B417BE0BB5A18B, + 0xF3611DAD8EA309EE, + 0x981CB28C7925E635, + 0xBE23DF2F976F5FC2, + 0xEDACD6FB7D4B37B2, + 0x948C065D2E4F02CF, + 0xB9AF07F479E2C383, + 0xE81AC9F1985B7464, + 0x9110BE36FF3928BF, + 0xB554EDC4BF0772EE, + 0xE2AA2935EEC94FAA, + 0x8DAA59C1B53DD1CA, + 0xB114F032228D463D, + 0xDD5A2C3EAB3097CC, + 0x8A585BA72AFE5EDF, + 0xACEE7290F5BDF697, + 0xD82A0F35332D743D, + 0x871A49813FFC68A6, + 0xA8E0DBE18FFB82D0, + 0xD31912D9F3FA6384, + 0x83EFABC8387C7E32, + 0xA4EB96BA469B9DBF, + 0xCE267C68D842852E, + 0x80D80DC18729933D, + 0xA10E1131E8F3F80C, + 0xC951957E6330F60F, + 0xFBA5FADDFBFD3393, + 0x9D47BCCABD7E403C, + 0xC499ABFD6CDDD04B, + 0xF5C016FCC815445E, + 0x99980E5DFD0D4ABB, + 0xBFFE11F57C509D69, + 0xEFFD9672DB64C4C4, + 0x95FE7E07C91EFAFA, + 0xBB7E1D89BB66B9B9, + 0xEA5DA4EC2A406827, + 0x927A87139A684118, + 0xB71928D88102515E, + 0xE4DF730EA142E5B6, + 0x8F0BA7E924C9CF92, + 0xB2CE91E36DFC4376, + 0xDF82365C497B5454, + 0x8BB161F9ADED14B4, + 0xAE9DBA78196859E1, + 0xDA4529161FC2705A, + 0x886B39ADD3D98638, + 0xAA86081948CFE7C6, + 0xD5278A1F9B03E1B8, + 0x8538B653C0E26D13, + 0xA686E3E8B11B0858, + 0xD0289CE2DD61CA6D, + 0x8219620DCA5D1E84, + 0xA29FBA913CF46625, + 0xCB47A9358C317FAF, + 0xFE199382EF3DDF9B, + 0x9ECFFC31D586ABC1, + 0xC683FB3E4AE856B1, + 0xF824FA0DDDA26C5D, + 0x9B171C48AA8583BA, + 0xC1DCE35AD526E4A9, + 0xF2541C318A709DD3, + 0x9774919EF68662A4, + 0xBD51B606B427FB4D, + 0xECA623886131FA20, + 0x93E7D6353CBF3C54, + 0xB8E1CBC28BEF0B69, + 0xE71A3EB32EEACE43, + 0x9070672FFD52C0EA, + 0xB48C80FBFCA77124, + 0xE1AFA13AFBD14D6E, + 0x8D0DC4C4DD62D064, + 0xB05135F614BB847E, + 0xDC65837399EA659D, + 0x89BF722840327F82, + 0xAC2F4EB2503F1F63, + 0xD73B225EE44EE73B, + 0x8684F57B4EB15085, + 0xA82632DA225DA4A6, + 0xD22FBF90AAF50DD0, + 0x835DD7BA6AD928A2, + 0xA4354DA9058F72CA, + 0xCD42A11346F34F7D, + 0x8049A4AC0C5811AE, + 0xA05C0DD70F6E161A, + 0xC873114CD3499BA0, + 0xFA8FD5A0081C0288, + 0x9C99E58405118195, + 0xC3C05EE50655E1FA, + 0xF4B0769E47EB5A79, + 0x98EE4A22ECF3188C, + 0xBF29DCABA82FDEAE, + 0xEEF453D6923BD65A, + 0x9558B4661B6565F8, + 0xBAAEE17FA23EBF76, + 0xE95A99DF8ACE6F54, + 0x91D8A02BB6C10594, + 0xB64EC836A47146FA, + 0xE3E27A444D8D98B8, + 0x8E6D8C6AB0787F73, + 0xB208EF855C969F50, + 0xDE8B2B66B3BC4724, + 0x8B16FB203055AC76, + 0xADDCB9E83C6B1794, + 0xD953E8624B85DD79, + 0x87D4713D6F33AA6C, + 0xA9C98D8CCB009506, + 0xD43BF0EFFDC0BA48, + 0x84A57695FE98746D, + 0xA5CED43B7E3E9188, + 0xCF42894A5DCE35EA, + 0x818995CE7AA0E1B2, + 0xA1EBFB4219491A1F, + 0xCA66FA129F9B60A7, + 0xFD00B897478238D1, + 0x9E20735E8CB16382, + 0xC5A890362FDDBC63, + 0xF712B443BBD52B7C, + 0x9A6BB0AA55653B2D, + 0xC1069CD4EABE89F9, + 0xF148440A256E2C77, + 0x96CD2A865764DBCA, + 0xBC807527ED3E12BD, + 0xEBA09271E88D976C, + 0x93445B8731587EA3, + 0xB8157268FDAE9E4C, + 0xE61ACF033D1A45DF, + 0x8FD0C16206306BAC, + 0xB3C4F1BA87BC8697, + 0xE0B62E2929ABA83C, + 0x8C71DCD9BA0B4926, + 0xAF8E5410288E1B6F, + 0xDB71E91432B1A24B, + 0x892731AC9FAF056F, + 0xAB70FE17C79AC6CA, + 0xD64D3D9DB981787D, + 0x85F0468293F0EB4E, + 0xA76C582338ED2622, + 0xD1476E2C07286FAA, + 0x82CCA4DB847945CA, + 0xA37FCE126597973D, + 0xCC5FC196FEFD7D0C, + 0xFF77B1FCBEBCDC4F, + 0x9FAACF3DF73609B1, + 0xC795830D75038C1E, + 0xF97AE3D0D2446F25, + 0x9BECCE62836AC577, + 0xC2E801FB244576D5, + 0xF3A20279ED56D48A, + 0x9845418C345644D7, + 0xBE5691EF416BD60C, + 0xEDEC366B11C6CB8F, + 0x94B3A202EB1C3F39, + 0xB9E08A83A5E34F08, + 0xE858AD248F5C22CA, + 0x91376C36D99995BE, + 0xB58547448FFFFB2E, + 0xE2E69915B3FFF9F9, + 0x8DD01FAD907FFC3C, + 0xB1442798F49FFB4B, + 0xDD95317F31C7FA1D, + 0x8A7D3EEF7F1CFC52, + 0xAD1C8EAB5EE43B67, + 0xD863B256369D4A41, + 0x873E4F75E2224E68, + 0xA90DE3535AAAE202, + 0xD3515C2831559A83, + 0x8412D9991ED58092, + 0xA5178FFF668AE0B6, + 0xCE5D73FF402D98E4, + 0x80FA687F881C7F8E, + 0xA139029F6A239F72, + 0xC987434744AC874F, + 0xFBE9141915D7A922, + 0x9D71AC8FADA6C9B5, + 0xC4CE17B399107C23, + 0xF6019DA07F549B2B, + 0x99C102844F94E0FB, + 0xC0314325637A193A, + 0xF03D93EEBC589F88, + 0x96267C7535B763B5, + 0xBBB01B9283253CA3, + 0xEA9C227723EE8BCB, + 0x92A1958A7675175F, + 0xB749FAED14125D37, + 0xE51C79A85916F485, + 0x8F31CC0937AE58D3, + 0xB2FE3F0B8599EF08, + 0xDFBDCECE67006AC9, + 0x8BD6A141006042BE, + 0xAECC49914078536D, + 0xDA7F5BF590966849, + 0x888F99797A5E012D, + 0xAAB37FD7D8F58179, + 0xD5605FCDCF32E1D7, + 0x855C3BE0A17FCD26, + 0xA6B34AD8C9DFC070, + 0xD0601D8EFC57B08C, + 0x823C12795DB6CE57, + 0xA2CB1717B52481ED, + 0xCB7DDCDDA26DA269, + 0xFE5D54150B090B03, + 0x9EFA548D26E5A6E2, + 0xC6B8E9B0709F109A, + 0xF867241C8CC6D4C1, + 0x9B407691D7FC44F8, + 0xC21094364DFB5637, + 0xF294B943E17A2BC4, + 0x979CF3CA6CEC5B5B, + 0xBD8430BD08277231, + 0xECE53CEC4A314EBE, + 0x940F4613AE5ED137, + 0xB913179899F68584, + 0xE757DD7EC07426E5, + 0x9096EA6F3848984F, + 0xB4BCA50B065ABE63, + 0xE1EBCE4DC7F16DFC, + 0x8D3360F09CF6E4BD, + 0xB080392CC4349DED, + 0xDCA04777F541C568, + 0x89E42CAAF9491B61, + 0xAC5D37D5B79B6239, + 0xD77485CB25823AC7, + 0x86A8D39EF77164BD, + 0xA8530886B54DBDEC, + 0xD267CAA862A12D67, + 0x8380DEA93DA4BC60, + 0xA46116538D0DEB78, + 0xCD795BE870516656, + 0x806BD9714632DFF6, + 0xA086CFCD97BF97F4, + 0xC8A883C0FDAF7DF0, + 0xFAD2A4B13D1B5D6C, + 0x9CC3A6EEC6311A64, + 0xC3F490AA77BD60FD, + 0xF4F1B4D515ACB93C, + 0x991711052D8BF3C5, + 0xBF5CD54678EEF0B7, + 0xEF340A98172AACE5, + 0x9580869F0E7AAC0F, + 0xBAE0A846D2195713, + 0xE998D258869FACD7, + 0x91FF83775423CC06, + 0xB67F6455292CBF08, + 0xE41F3D6A7377EECA, + 0x8E938662882AF53E, + 0xB23867FB2A35B28E, + 0xDEC681F9F4C31F31, + 0x8B3C113C38F9F37F, + 0xAE0B158B4738705F, + 0xD98DDAEE19068C76, + 0x87F8A8D4CFA417CA, + 0xA9F6D30A038D1DBC, + 0xD47487CC8470652B, + 0x84C8D4DFD2C63F3B, + 0xA5FB0A17C777CF0A, + 0xCF79CC9DB955C2CC, + 0x81AC1FE293D599C0, + 0xA21727DB38CB0030, + 0xCA9CF1D206FDC03C, + 0xFD442E4688BD304B, + 0x9E4A9CEC15763E2F, + 0xC5DD44271AD3CDBA, + 0xF7549530E188C129, + 0x9A94DD3E8CF578BA, + 0xC13A148E3032D6E8, + 0xF18899B1BC3F8CA2, + 0x96F5600F15A7B7E5, + 0xBCB2B812DB11A5DE, + 0xEBDF661791D60F56, + 0x936B9FCEBB25C996, + 0xB84687C269EF3BFB, + 0xE65829B3046B0AFA, + 0x8FF71A0FE2C2E6DC, + 0xB3F4E093DB73A093, + 0xE0F218B8D25088B8, + 0x8C974F7383725573, + 0xAFBD2350644EEAD0, + 0xDBAC6C247D62A584, + 0x894BC396CE5DA772, + 0xAB9EB47C81F5114F, + 0xD686619BA27255A3, + 0x8613FD0145877586, + 0xA798FC4196E952E7, + 0xD17F3B51FCA3A7A1, + 0x82EF85133DE648C5, + 0xA3AB66580D5FDAF6, + 0xCC963FEE10B7D1B3, + 0xFFBBCFE994E5C620, + 0x9FD561F1FD0F9BD4, + 0xC7CABA6E7C5382C9, + 0xF9BD690A1B68637B, + 0x9C1661A651213E2D, + 0xC31BFA0FE5698DB8, + 0xF3E2F893DEC3F126, + 0x986DDB5C6B3A76B8, + 0xBE89523386091466, + 0xEE2BA6C0678B597F, + 0x94DB483840B717F0, + 0xBA121A4650E4DDEC, + 0xE896A0D7E51E1566, + 0x915E2486EF32CD60, + 0xB5B5ADA8AAFF80B8, + 0xE3231912D5BF60E6, + 0x8DF5EFABC5979C90, + 0xB1736B96B6FD83B4, + 0xDDD0467C64BCE4A1, + 0x8AA22C0DBEF60EE4, + 0xAD4AB7112EB3929E, + 0xD89D64D57A607745, + 0x87625F056C7C4A8B, + 0xA93AF6C6C79B5D2E, + 0xD389B47879823479, + 0x843610CB4BF160CC, + 0xA54394FE1EEDB8FF, + 0xCE947A3DA6A9273E, + 0x811CCC668829B887, + 0xA163FF802A3426A9, + 0xC9BCFF6034C13053, + 0xFC2C3F3841F17C68, + 0x9D9BA7832936EDC1, + 0xC5029163F384A931, + 0xF64335BCF065D37D, + 0x99EA0196163FA42E, + 0xC06481FB9BCF8D3A, + 0xF07DA27A82C37088, + 0x964E858C91BA2655, + 0xBBE226EFB628AFEB, + 0xEADAB0ABA3B2DBE5, + 0x92C8AE6B464FC96F, + 0xB77ADA0617E3BBCB, + 0xE55990879DDCAABE, + 0x8F57FA54C2A9EAB7, + 0xB32DF8E9F3546564, + 0xDFF9772470297EBD, + 0x8BFBEA76C619EF36, + 0xAEFAE51477A06B04, + 0xDAB99E59958885C5, + 0x88B402F7FD75539B, + 0xAAE103B5FCD2A882, + 0xD59944A37C0752A2, + 0x857FCAE62D8493A5, + 0xA6DFBD9FB8E5B88F, + 0xD097AD07A71F26B2, + 0x825ECC24C8737830, + 0xA2F67F2DFA90563B, + 0xCBB41EF979346BCA, + 0xFEA126B7D78186BD, + 0x9F24B832E6B0F436, + 0xC6EDE63FA05D3144, + 0xF8A95FCF88747D94, + 0x9B69DBE1B548CE7D, + 0xC24452DA229B021C, + 0xF2D56790AB41C2A3, + 0x97C560BA6B0919A6, + 0xBDB6B8E905CB600F, + 0xED246723473E3813, + 0x9436C0760C86E30C, + 0xB94470938FA89BCF, + 0xE7958CB87392C2C3, + 0x90BD77F3483BB9BA, + 0xB4ECD5F01A4AA828, + 0xE2280B6C20DD5232, + 0x8D590723948A535F, + 0xB0AF48EC79ACE837, + 0xDCDB1B2798182245, + 0x8A08F0F8BF0F156B, + 0xAC8B2D36EED2DAC6, + 0xD7ADF884AA879177, + 0x86CCBB52EA94BAEB, + 0xA87FEA27A539E9A5, + 0xD29FE4B18E88640F, + 0x83A3EEEEF9153E89, + 0xA48CEAAAB75A8E2B, + 0xCDB02555653131B6, + 0x808E17555F3EBF12, + 0xA0B19D2AB70E6ED6, + 0xC8DE047564D20A8C, + 0xFB158592BE068D2F, + 0x9CED737BB6C4183D, + 0xC428D05AA4751E4D, + 0xF53304714D9265E0, + 0x993FE2C6D07B7FAC, + 0xBF8FDB78849A5F97, + 0xEF73D256A5C0F77D, + 0x95A8637627989AAE, + 0xBB127C53B17EC159, + 0xE9D71B689DDE71B0, + 0x9226712162AB070E, + 0xB6B00D69BB55C8D1, + 0xE45C10C42A2B3B06, + 0x8EB98A7A9A5B04E3, + 0xB267ED1940F1C61C, + 0xDF01E85F912E37A3, + 0x8B61313BBABCE2C6, + 0xAE397D8AA96C1B78, + 0xD9C7DCED53C72256, + 0x881CEA14545C7575, + 0xAA242499697392D3, + 0xD4AD2DBFC3D07788, + 0x84EC3C97DA624AB5, + 0xA6274BBDD0FADD62, + 0xCFB11EAD453994BA, + 0x81CEB32C4B43FCF5, + 0xA2425FF75E14FC32, + 0xCAD2F7F5359A3B3E, + 0xFD87B5F28300CA0E, + 0x9E74D1B791E07E48, + 0xC612062576589DDB, + 0xF79687AED3EEC551, + 0x9ABE14CD44753B53, + 0xC16D9A0095928A27, + 0xF1C90080BAF72CB1, + 0x971DA05074DA7BEF, + 0xBCE5086492111AEB, + 0xEC1E4A7DB69561A5, + 0x9392EE8E921D5D07, + 0xB877AA3236A4B449, + 0xE69594BEC44DE15B, + 0x901D7CF73AB0ACD9, + 0xB424DC35095CD80F, + 0xE12E13424BB40E13, + 0x8CBCCC096F5088CC, + 0xAFEBFF0BCB24AAFF, + 0xDBE6FECEBDEDD5BF, + 0x89705F4136B4A597, + 0xABCC77118461CEFD, + 0xD6BF94D5E57A42BC, + 0x8637BD05AF6C69B6, + 0xA7C5AC471B478423, + 0xD1B71758E219652C, + 0x83126E978D4FDF3B, + 0xA3D70A3D70A3D70A, + 0xCCCCCCCCCCCCCCCD, + 0x8000000000000000, + 0xA000000000000000, + 0xC800000000000000, + 0xFA00000000000000, + 0x9C40000000000000, + 0xC350000000000000, + 0xF424000000000000, + 0x9896800000000000, + 0xBEBC200000000000, + 0xEE6B280000000000, + 0x9502F90000000000, + 0xBA43B74000000000, + 0xE8D4A51000000000, + 0x9184E72A00000000, + 0xB5E620F480000000, + 0xE35FA931A0000000, + 0x8E1BC9BF04000000, + 0xB1A2BC2EC5000000, + 0xDE0B6B3A76400000, + 0x8AC7230489E80000, + 0xAD78EBC5AC620000, + 0xD8D726B7177A8000, + 0x878678326EAC9000, + 0xA968163F0A57B400, + 0xD3C21BCECCEDA100, + 0x84595161401484A0, + 0xA56FA5B99019A5C8, + 0xCECB8F27F4200F3A, + 0x813F3978F8940984, + 0xA18F07D736B90BE5, + 0xC9F2C9CD04674EDF, + 0xFC6F7C4045812296, + 0x9DC5ADA82B70B59E, + 0xC5371912364CE305, + 0xF684DF56C3E01BC7, + 0x9A130B963A6C115C, + 0xC097CE7BC90715B3, + 0xF0BDC21ABB48DB20, + 0x96769950B50D88F4, + 0xBC143FA4E250EB31, + 0xEB194F8E1AE525FD, + 0x92EFD1B8D0CF37BE, + 0xB7ABC627050305AE, + 0xE596B7B0C643C719, + 0x8F7E32CE7BEA5C70, + 0xB35DBF821AE4F38C, + 0xE0352F62A19E306F, + 0x8C213D9DA502DE45, + 0xAF298D050E4395D7, + 0xDAF3F04651D47B4C, + 0x88D8762BF324CD10, + 0xAB0E93B6EFEE0054, + 0xD5D238A4ABE98068, + 0x85A36366EB71F041, + 0xA70C3C40A64E6C52, + 0xD0CF4B50CFE20766, + 0x82818F1281ED44A0, + 0xA321F2D7226895C8, + 0xCBEA6F8CEB02BB3A, + 0xFEE50B7025C36A08, + 0x9F4F2726179A2245, + 0xC722F0EF9D80AAD6, + 0xF8EBAD2B84E0D58C, + 0x9B934C3B330C8577, + 0xC2781F49FFCFA6D5, + 0xF316271C7FC3908B, + 0x97EDD871CFDA3A57, + 0xBDE94E8E43D0C8EC, + 0xED63A231D4C4FB27, + 0x945E455F24FB1CF9, + 0xB975D6B6EE39E437, + 0xE7D34C64A9C85D44, + 0x90E40FBEEA1D3A4B, + 0xB51D13AEA4A488DD, + 0xE264589A4DCDAB15, + 0x8D7EB76070A08AED, + 0xB0DE65388CC8ADA8, + 0xDD15FE86AFFAD912, + 0x8A2DBF142DFCC7AB, + 0xACB92ED9397BF996, + 0xD7E77A8F87DAF7FC, + 0x86F0AC99B4E8DAFD, + 0xA8ACD7C0222311BD, + 0xD2D80DB02AABD62C, + 0x83C7088E1AAB65DB, + 0xA4B8CAB1A1563F52, + 0xCDE6FD5E09ABCF27, + 0x80B05E5AC60B6178, + 0xA0DC75F1778E39D6, + 0xC913936DD571C84C, + 0xFB5878494ACE3A5F, + 0x9D174B2DCEC0E47B, + 0xC45D1DF942711D9A, + 0xF5746577930D6501, + 0x9968BF6ABBE85F20, + 0xBFC2EF456AE276E9, + 0xEFB3AB16C59B14A3, + 0x95D04AEE3B80ECE6, + 0xBB445DA9CA61281F, + 0xEA1575143CF97227, + 0x924D692CA61BE758, + 0xB6E0C377CFA2E12E, + 0xE498F455C38B997A, + 0x8EDF98B59A373FEC, + 0xB2977EE300C50FE7, + 0xDF3D5E9BC0F653E1, + 0x8B865B215899F46D, + 0xAE67F1E9AEC07188, + 0xDA01EE641A708DEA, + 0x884134FE908658B2, + 0xAA51823E34A7EEDF, + 0xD4E5E2CDC1D1EA96, + 0x850FADC09923329E, + 0xA6539930BF6BFF46, + 0xCFE87F7CEF46FF17, + 0x81F14FAE158C5F6E, + 0xA26DA3999AEF774A, + 0xCB090C8001AB551C, + 0xFDCB4FA002162A63, + 0x9E9F11C4014DDA7E, + 0xC646D63501A1511E, + 0xF7D88BC24209A565, + 0x9AE757596946075F, + 0xC1A12D2FC3978937, + 0xF209787BB47D6B85, + 0x9745EB4D50CE6333, + 0xBD176620A501FC00, + 0xEC5D3FA8CE427B00, + 0x93BA47C980E98CE0, + 0xB8A8D9BBE123F018, + 0xE6D3102AD96CEC1E, + 0x9043EA1AC7E41393, + 0xB454E4A179DD1877, + 0xE16A1DC9D8545E95, + 0x8CE2529E2734BB1D, + 0xB01AE745B101E9E4, + 0xDC21A1171D42645D, + 0x899504AE72497EBA, + 0xABFA45DA0EDBDE69, + 0xD6F8D7509292D603, + 0x865B86925B9BC5C2, + 0xA7F26836F282B733, + 0xD1EF0244AF2364FF, + 0x8335616AED761F1F, + 0xA402B9C5A8D3A6E7, + 0xCD036837130890A1, + 0x802221226BE55A65, + 0xA02AA96B06DEB0FE, + 0xC83553C5C8965D3D, + 0xFA42A8B73ABBF48D, + 0x9C69A97284B578D8, + 0xC38413CF25E2D70E, + 0xF46518C2EF5B8CD1, + 0x98BF2F79D5993803, + 0xBEEEFB584AFF8604, + 0xEEAABA2E5DBF6785, + 0x952AB45CFA97A0B3, + 0xBA756174393D88E0, + 0xE912B9D1478CEB17, + 0x91ABB422CCB812EF, + 0xB616A12B7FE617AA, + 0xE39C49765FDF9D95, + 0x8E41ADE9FBEBC27D, + 0xB1D219647AE6B31C, + 0xDE469FBD99A05FE3, + 0x8AEC23D680043BEE, + 0xADA72CCC20054AEA, + 0xD910F7FF28069DA4, + 0x87AA9AFF79042287, + 0xA99541BF57452B28, + 0xD3FA922F2D1675F2, + 0x847C9B5D7C2E09B7, + 0xA59BC234DB398C25, + 0xCF02B2C21207EF2F, + 0x8161AFB94B44F57D, + 0xA1BA1BA79E1632DC, + 0xCA28A291859BBF93, + 0xFCB2CB35E702AF78, + 0x9DEFBF01B061ADAB, + 0xC56BAEC21C7A1916, + 0xF6C69A72A3989F5C, + 0x9A3C2087A63F6399, + 0xC0CB28A98FCF3C80, + 0xF0FDF2D3F3C30B9F, + 0x969EB7C47859E744, + 0xBC4665B596706115, + 0xEB57FF22FC0C795A, + 0x9316FF75DD87CBD8, + 0xB7DCBF5354E9BECE, + 0xE5D3EF282A242E82, + 0x8FA475791A569D11, + 0xB38D92D760EC4455, + 0xE070F78D3927556B, + 0x8C469AB843B89563, + 0xAF58416654A6BABB, + 0xDB2E51BFE9D0696A, + 0x88FCF317F22241E2, + 0xAB3C2FDDEEAAD25B, + 0xD60B3BD56A5586F2, + 0x85C7056562757457, + 0xA738C6BEBB12D16D, + 0xD106F86E69D785C8, + 0x82A45B450226B39D, + 0xA34D721642B06084, + 0xCC20CE9BD35C78A5, + 0xFF290242C83396CE, + 0x9F79A169BD203E41, + 0xC75809C42C684DD1, + 0xF92E0C3537826146, + 0x9BBCC7A142B17CCC, + 0xC2ABF989935DDBFE, + 0xF356F7EBF83552FE, + 0x98165AF37B2153DF, + 0xBE1BF1B059E9A8D6, + 0xEDA2EE1C7064130C, + 0x9485D4D1C63E8BE8, + 0xB9A74A0637CE2EE1, + 0xE8111C87C5C1BA9A, + 0x910AB1D4DB9914A0, + 0xB54D5E4A127F59C8, + 0xE2A0B5DC971F303A, + 0x8DA471A9DE737E24, + 0xB10D8E1456105DAD, + 0xDD50F1996B947519, + 0x8A5296FFE33CC930, + 0xACE73CBFDC0BFB7B, + 0xD8210BEFD30EFA5A, + 0x8714A775E3E95C78, + 0xA8D9D1535CE3B396, + 0xD31045A8341CA07C, + 0x83EA2B892091E44E, + 0xA4E4B66B68B65D61, + 0xCE1DE40642E3F4B9, + 0x80D2AE83E9CE78F4, + 0xA1075A24E4421731, + 0xC94930AE1D529CFD, + 0xFB9B7CD9A4A7443C, + 0x9D412E0806E88AA6, + 0xC491798A08A2AD4F, + 0xF5B5D7EC8ACB58A3, + 0x9991A6F3D6BF1766, + 0xBFF610B0CC6EDD3F, + 0xEFF394DCFF8A948F, + 0x95F83D0A1FB69CD9, + 0xBB764C4CA7A44410, + 0xEA53DF5FD18D5514, + 0x92746B9BE2F8552C, + 0xB7118682DBB66A77, + 0xE4D5E82392A40515, + 0x8F05B1163BA6832D, + 0xB2C71D5BCA9023F8, + 0xDF78E4B2BD342CF7, + 0x8BAB8EEFB6409C1A, + 0xAE9672ABA3D0C321, + 0xDA3C0F568CC4F3E9, + 0x8865899617FB1871, + 0xAA7EEBFB9DF9DE8E, + 0xD51EA6FA85785631, + 0x8533285C936B35DF, + 0xA67FF273B8460357, + 0xD01FEF10A657842C, + 0x8213F56A67F6B29C, + 0xA298F2C501F45F43, + 0xCB3F2F7642717713, + 0xFE0EFB53D30DD4D8, + 0x9EC95D1463E8A507, + 0xC67BB4597CE2CE49, + 0xF81AA16FDC1B81DB, + 0x9B10A4E5E9913129, + 0xC1D4CE1F63F57D73, + 0xF24A01A73CF2DCD0, + 0x976E41088617CA02, + 0xBD49D14AA79DBC82, + 0xEC9C459D51852BA3, + 0x93E1AB8252F33B46, + 0xB8DA1662E7B00A17, + 0xE7109BFBA19C0C9D, + 0x906A617D450187E2, + 0xB484F9DC9641E9DB, + 0xE1A63853BBD26451, + 0x8D07E33455637EB3, + 0xB049DC016ABC5E60, + 0xDC5C5301C56B75F7, + 0x89B9B3E11B6329BB, + 0xAC2820D9623BF429, + 0xD732290FBACAF134, + 0x867F59A9D4BED6C0, + 0xA81F301449EE8C70, + 0xD226FC195C6A2F8C, + 0x83585D8FD9C25DB8, + 0xA42E74F3D032F526, + 0xCD3A1230C43FB26F, + 0x80444B5E7AA7CF85, + 0xA0555E361951C367, + 0xC86AB5C39FA63441, + 0xFA856334878FC151, + 0x9C935E00D4B9D8D2, + 0xC3B8358109E84F07, + 0xF4A642E14C6262C9, + 0x98E7E9CCCFBD7DBE, + 0xBF21E44003ACDD2D, + 0xEEEA5D5004981478, + 0x95527A5202DF0CCB, + 0xBAA718E68396CFFE, + 0xE950DF20247C83FD, + 0x91D28B7416CDD27E, + 0xB6472E511C81471E, + 0xE3D8F9E563A198E5, + 0x8E679C2F5E44FF8F, + 0xB201833B35D63F73, + 0xDE81E40A034BCF50, + 0x8B112E86420F6192, + 0xADD57A27D29339F6, + 0xD94AD8B1C7380874, + 0x87CEC76F1C830549, + 0xA9C2794AE3A3C69B, + 0xD433179D9C8CB841, + 0x849FEEC281D7F329, + 0xA5C7EA73224DEFF3, + 0xCF39E50FEAE16BF0, + 0x81842F29F2CCE376, + 0xA1E53AF46F801C53, + 0xCA5E89B18B602368, + 0xFCF62C1DEE382C42, + 0x9E19DB92B4E31BA9, + 0xC5A05277621BE294, + 0xF70867153AA2DB39, + 0x9A65406D44A5C903, + 0xC0FE908895CF3B44, + 0xF13E34AABB430A15, + 0x96C6E0EAB509E64D, + 0xBC789925624C5FE1, + 0xEB96BF6EBADF77D9, + 0x933E37A534CBAAE8, + 0xB80DC58E81FE95A1, + 0xE61136F2227E3B0A, + 0x8FCAC257558EE4E6, + 0xB3BD72ED2AF29E20, + 0xE0ACCFA875AF45A8, + 0x8C6C01C9498D8B89, + 0xAF87023B9BF0EE6B, + 0xDB68C2CA82ED2A06, + 0x892179BE91D43A44, + 0xAB69D82E364948D4, + 0xD6444E39C3DB9B0A, + 0x85EAB0E41A6940E6, + 0xA7655D1D2103911F, + 0xD13EB46469447567, + 0x82C730BEC1CAC961, + 0xA378FCEE723D7BB9, + 0xCC573C2A0ECCDAA7, + 0xFF6D0B3492801151, + 0x9FA42700DB900AD2, + 0xC78D30C112740D87, + 0xF9707CF1571110E9, + 0x9BE64E16D66AAA91, + 0xC2DFE19C8C055536, + 0xF397DA03AF06AA83, + 0x983EE8424D642A92, + 0xBE4EA252E0BD3537, + 0xEDE24AE798EC8284, + 0x94AD6ED0BF93D193, + 0xB9D8CA84EF78C5F7, + 0xE84EFD262B56F775, + 0x91315E37DB165AA9, + 0xB57DB5C5D1DBF153, + 0xE2DD23374652EDA8, + 0x8DCA36028BF3D489, + 0xB13CC3832EF0C9AC, + 0xDD8BF463FAACFC16, + 0x8A7778BE7CAC1D8E, + 0xAD1556EE1BD724F1, + 0xD85AACA9A2CCEE2E, + 0x8738ABEA05C014DD, + 0xA906D6E487301A14, + 0xD3488C9DA8FC2099, + 0x840D57E2899D945F, + 0xA510ADDB2C04F977, + 0xCE54D951F70637D5, + 0x80F507D33A63E2E5, + 0xA13249C808FCDB9F, + 0xC97EDC3A0B3C1286, + 0xFBDE93488E0B1728, + 0x9D6B1C0D58C6EE79, + 0xC4C5E310AEF8AA17, + 0xF5F75BD4DAB6D49D, + 0x99BA996508B244E2, + 0xC0293FBE4ADED61B, + 0xF0338FADDD968BA1, + 0x962039CCAA7E1745, + 0xBBA8483FD51D9D16, + 0xEA925A4FCA65045B, + 0x929B7871DE7F22B9, + 0xB742568E561EEB67, + 0xE512EC31EBA6A641, + 0x8F2BD39F334827E9, + 0xB2F6C887001A31E3, + 0xDFB47AA8C020BE5C, + 0x8BD0CCA9781476F9, + 0xAEC4FFD3D61994B8, + 0xDA763FC8CB9FF9E6, + 0x8889E7DD7F43FC2F, + 0xAAAC61D4DF14FB3B, + 0xD5577A4A16DA3A0A, + 0x8556AC6E4E486446, + 0xA6AC5789E1DA7D58, + 0xD0576D6C5A511CAE, + 0x8236A463B872B1ED, + 0xA2C44D7CA68F5E68, + 0xCB7560DBD0333602, + 0xFE52B912C4400382, + 0x9EF3B3ABBAA80231, + 0xC6B0A096A95202BE, + 0xF85CC8BC53A6836D, + 0x9B39FD75B4481224, + 0xC2087CD3215A16AD, + 0xF28A9C07E9B09C59, + 0x9796A184F20E61B7, + 0xBD7C49E62E91FA25, + 0xECDB5C5FBA3678AF, + 0x940919BBD4620B6D, + 0xB90B602AC97A8E48, + 0xE74E38357BD931DB, + 0x9090E3216D67BF29, + 0xB4B51BE9C8C1AEF3, + 0xE1E262E43AF21AAF, + 0x8D2D7DCEA4D750AE, + 0xB078DD424E0D24D9, + 0xDC971492E1906E0F, + 0x89DE6CDBCCFA44CA, + 0xAC560812C038D5FC, + 0xD76B8A1770470B7B, + 0x86A3364EA62C672D, + 0xA84C03E24FB780F8, + 0xD25F04DAE3A56136, + 0x837B6308CE475CC2, + 0xA45A3BCB01D933F2, + 0xCD70CABDC24F80EF, + 0x80667EB69971B095, + 0xA0801E643FCE1CBB, + 0xC8A025FD4FC1A3E9, + 0xFAC82F7CA3B20CE4, + 0x9CBD1DADE64F480E, + 0xC3EC65195FE31A12, + 0xF4E77E5FB7DBE096, + 0x9910AEFBD2E96C5E, + 0xBF54DABAC7A3C775, + 0xEF2A1169798CB953, + 0x957A4AE1EBF7F3D4, + 0xBAD8DD9A66F5F0C9, + 0xE98F150100B36CFB, + 0x91F96D20A070241D, + 0xB677C868C88C2D24, + 0xE415BA82FAAF386D, + 0x8E8D9491DCAD8344, + 0xB230F9B653D8E415, + 0xDEBD3823E8CF1D1A, + 0x8B36431671817230, + 0xAE03D3DC0DE1CEBD, + 0xD984C8D3115A426C, + 0x87F2FD83EAD86983, + 0xA9EFBCE4E58E83E4, + 0xD46BAC1E1EF224DD, + 0x84C34B92D357570A, + 0xA5F41E77882D2CCD, + 0xCF7126156A387800, + 0x81A6B7CD62634B00, + 0xA21065C0BAFC1DC0, + 0xCA947F30E9BB2530, + 0xFD399EFD2429EE7C, + 0x9E44035E369A350D, + 0xC5D50435C440C251, + 0xF74A45433550F2E5, + 0x9A8E6B4A015297CF, + 0xC132061C81A73DC3, + 0xF17E87A3A2110D34, + 0x96EF14C6454AA840, + 0xBCAAD9F7D69D5250, + 0xEBD59075CC44A6E4, + 0x93657A499FAAE84F, + 0xB83ED8DC0795A262, + 0xE64E8F13097B0AFB, + 0x8FF1196BE5ECE6DD, + 0xB3ED5FC6DF682094, + 0xE0E8B7B8974228B9, + 0x8C9172D35E895974, + 0xAFB5CF88362BAFD1, + 0xDBA3436A43B69BC5, + 0x89460A226A52215B, + 0xAB978CAB04E6A9B2, + 0xD67D6FD5C620541E, + 0x860E65E59BD43493, + 0xA791FF5F02C941B8, + 0xD1767F36C37B9226, + 0x82EA0F823A2D3B57, + 0xA3A49362C8B88A2D, + 0xCC8DB83B7AE6ACB9, + 0xFFB1264A59A057E7, + 0x9FCEB7EE780436F0, + 0xC7C265EA160544AC, + 0xF9B2FF649B8695D7, + 0x9C0FDF9EE1341DA7, + 0xC313D78699812510, + 0xF3D8CD683FE16E54, + 0x9867806127ECE4F5, + 0xBE81607971E81E32, + 0xEE21B897CE6225BE, + 0x94D5135EE0FD5797, + 0xBA0A5836993CAD7D, + 0xE88CEE443F8BD8DC, + 0x915814EAA7B76789, + 0xB5AE1A2551A5416C, + 0xE319A0AEA60E91C7, +]; + +static immutable align(16) ulong[1025] p10_coefficients_l = [ + 0x7132D332E3F204D5, + 0x8D7F87FF9CEE860A, + 0x70DF69FF842A278D, + 0xC68BA23FB29A58B8, + 0xB82E8ACF9F40EEE6, + 0xA63A2D8387112A9F, + 0xA7E45C72346ABAA4, + 0xD1DD738EC185694D, + 0xC654D07271E6C3A0, + 0xDBF5024787303A44, + 0x12F242D968FC48D5, + 0x17AED38FC33B5B0A, + 0x8ECD4439DA0518E6, + 0x7280954850865F20, + 0x4F20BA9A64A7F6E8, + 0x917474A07EE8FA51, + 0xB5D191C89EA338E5, + 0xE345F63AC64C071E, + 0x9C1773C977DF08E6, + 0x218EA85DEAEB6590, + 0x29F2527565A63EF4, + 0xF46EE712BF0FCEB0, + 0x18C5506BB769E12E, + 0x1EF6A486A544597A, + 0xA6B44DA84E956FD8, + 0x6830B089311D65E7, + 0x423CDCAB7D64BF61, + 0x52CC13D65CBDEF39, + 0xB3BF8C65F9F6B584, + 0x20AF6F7F787462E5, + 0x68DB4B5F56917B9E, + 0x81890F1B961AED43, + 0x61EB52E27BA1A893, + 0xFA66279B1A8A12B8, + 0x7C7FD8C0F0964BB3, + 0x1B9FCEF12CBBDEA0, + 0xE287C2AD77EAD648, + 0xAD94D9AC6AF2C5ED, + 0x98FA101785AF7768, + 0xBF38941D671B5542, + 0xF7835C9260711549, + 0x756433B6F88D5A9C, + 0x12BD40A4B6B0B143, + 0x4BB64866F22E6ECA, + 0xDEA3DA80AEBA0A7C, + 0x164CD120DA688D1B, + 0x5BE005691102B062, + 0xB96C0361AAA1AE3D, + 0x67C7043A154A19CC, + 0x01B8C5489A9CA040, + 0x01137B4D60A1E428, + 0xC1585A20B8CA5D32, + 0xB1AE70A8E6FCF47E, + 0x4F0D0669905E18CF, + 0xA2D04803F4759F02, + 0xCB845A04F19306C3, + 0x1F32B84316FBE43A, + 0x66FF6653DCBADD48, + 0x00BF3FE8D3E9949A, + 0x807787F18471FCE1, + 0x209569EDE58E7C19, + 0xA8BAC4695EF21B1F, + 0x6974BAC1DB5750F3, + 0x03D1E972522D2530, + 0x84C663CEE6B86E7C, + 0xD2FBFE615033450E, + 0x87BAFDF9A4401651, + 0xE9A9BD780D501BE5, + 0x520A166B0852116F, + 0xE68C9C05CA6695CB, + 0xA02FC3073D003B3E, + 0x241DD9E486202507, + 0x6D25505DA7A82E48, + 0x886EA475119239DA, + 0xEA8A4D9255F6C851, + 0xD296707B75BA3D33, + 0x073C0C9A5328CC7F, + 0xC90B0FC0E7F2FF9F, + 0x3DA6E9D890F7DFC3, + 0x0D10A44EB535D7B4, + 0xD054CD6262834DA1, + 0xA235005D7D921085, + 0xCAC24074DCF694A6, + 0x3D72D092143439D0, + 0x6667C25B4CA0A422, + 0x4001B2F21FC8CD2A, + 0x10021FAEA7BB0075, + 0x8A0153CD28D4E049, + 0x2C81A8C0730A185B, + 0xB7A212F08FCC9E72, + 0x12C54BD659DFE307, + 0x97769ECBF057DBC9, + 0xBD54467EEC6DD2BB, + 0x5654AC0F53C4A3B5, + 0x2BE9D71328B5CCA2, + 0xF6E44CD7F2E33FCB, + 0x1A4EB006F7CE07DF, + 0xA0E25C08B5C189D7, + 0x891AF30AE331EC4C, + 0x35B0D7E6CDFF33B0, + 0xC31D0DE0817F009C, + 0x73E45158A1DEC0C2, + 0x086EB2D7652B387A, + 0x4A8A5F8D3E760698, + 0x5D2CF7708E13883E, + 0x3478354CB1986A4D, + 0x00CB214FEEFF4270, + 0x00FDE9A3EABF130C, + 0xC13D640CE56ED7D0, + 0x98C65E880F6546E2, + 0x3EF7F62A133E989A, + 0x8EB5F3B4980E3EC1, + 0x3931B850DF08E738, + 0xC77E266516CB2106, + 0xF95DAFFE5C7DE948, + 0x5BDA8DFEF9CEB1CD, + 0x72D1317EB8425E40, + 0x0F857DDE6652F5D0, + 0xC9B36EAAFFF3D9A2, + 0x3C204A55BFF0D00B, + 0xCB285CEB2FED040E, + 0x5EF93A12FDF42288, + 0x76B78897BD712B2B, + 0xD4656ABDACCD75F5, + 0x24BF62B68C0069B9, + 0x2DEF3B642F008428, + 0xB96B0A3D3AC0A531, + 0xD3E2E66644B8673F, + 0x88DB9FFFD5E6810F, + 0x6B1287FFCB602152, + 0x62EB94FFDF1C14D3, + 0x7BA67A3FD6E31A08, + 0xDA9018CFCC9BE08A, + 0x91341F03BFC2D8AD, + 0x9AC0936257D9C76C, + 0xC170B83AEDD03947, + 0xF1CCE649A9444799, + 0x17200FEE09CAACC0, + 0x9CE813E98C3D57EF, + 0xC42218E3EF4CADEB, + 0xBA954F8E758FECB3, + 0xA93AA37212F3E7E0, + 0xD3894C4E97B0E1D8, + 0xE435CFB11ECE8D27, + 0xDD43439D66823071, + 0x14941484C022BC8D, + 0xECDC8CD2F815B5D8, + 0x6813B007B61B234E, + 0x82189C09A3A1EC21, + 0x714F618606453395, + 0x8DA339E787D6807A, + 0xF10C086169CC2099, + 0x16A7853CE21F945F, + 0x9C51668C1AA77977, + 0x4365C02F215157D5, + 0x0A1F981D74D2D6E5, + 0x4CA77E24D2078C9E, + 0xDFD15DAE06896FC6, + 0xEBE2DA8CC415E5DC, + 0x66DB912FF51B5F53, + 0x0092757BF2623727, + 0x205B896D777D6279, + 0xA8726BC8D55CBB17, + 0x128F06BB0AB3E9DD, + 0x1732C869CD60E454, + 0x0E7FBD42205C8EB4, + 0x521FAC92A873B261, + 0xE6A797B752909EFA, + 0x9028BED2939A635C, + 0x7432EE873880FC33, + 0x113FAA2906A13B40, + 0x4AC7CA59A424C508, + 0x5D79BCF00D2DF64A, + 0xF4D82C2C107973DC, + 0x79071B9B8A4BE86A, + 0x9748E2826CDEE284, + 0xFD1B1B2308169B25, + 0xFE30F0F5E50E20F7, + 0xBDBD2D335E51A935, + 0xAD2C788035E61382, + 0x4C3BCB5021AFCC31, + 0xDF4ABE242A1BBF3E, + 0xD71D6DAD34A2AF0D, + 0x8672648C40E5AD68, + 0x680EFDAF511F18C2, + 0x0212BD1B2566DEF3, + 0x014BB630F7604B58, + 0x419EA3BD35385E2E, + 0x52064CAC828675B9, + 0x7343EFEBD1940994, + 0x1014EBE6C5F90BF9, + 0xD41A26E077774EF7, + 0x8920B098955522B5, + 0x55B46E5F5D5535B1, + 0xEB2189F734AA831D, + 0xA5E9EC7501D523E4, + 0x47B233C92125366F, + 0x999EC0BB696E840A, + 0xC00670EA43CA250D, + 0x380406926A5E5728, + 0xC605083704F5ECF2, + 0xF7864A44C633682F, + 0x7AB3EE6AFBE0211D, + 0x5960EA05BAD82965, + 0x6FB92487298E33BE, + 0xA5D3B6D479F8E057, + 0x8F48A4899877186C, + 0x331ACDABFE94DE87, + 0x9FF0C08B7F1D0B15, + 0x07ECF0AE5EE44DDA, + 0xC9E82CD9F69D6150, + 0xBE311C083A225CD2, + 0x6DBD630A48AAF407, + 0x092CBBCCDAD5B108, + 0x25BBF56008C58EA5, + 0xAF2AF2B80AF6F24E, + 0x1AF5AF660DB4AEE2, + 0x50D98D9FC890ED4D, + 0xE50FF107BAB528A1, + 0x1E53ED49A96272C9, + 0x25E8E89C13BB0F7B, + 0x77B191618C54E9AD, + 0xD59DF5B9EF6A2418, + 0x4B0573286B44AD1E, + 0x4EE367F9430AEC33, + 0x229C41F793CDA73F, + 0x6B43527578C1110F, + 0x830A13896B78AAAA, + 0x23CC986BC656D554, + 0x2CBFBE86B7EC8AA9, + 0x7BF7D71432F3D6AA, + 0xDAF5CCD93FB0CC54, + 0xD1B3400F8F9CFF69, + 0x23100809B9C21FA2, + 0xABD40A0C2832A78A, + 0x16C90C8F323F516D, + 0xAE3DA7D97F6792E4, + 0x99CD11CFDF41779D, + 0x40405643D711D584, + 0x482835EA666B2572, + 0xDA3243650005EECF, + 0x90BED43E40076A83, + 0x5A7744A6E804A292, + 0x711515D0A205CB36, + 0x0D5A5B44CA873E04, + 0xE858790AFE9486C2, + 0x626E974DBE39A873, + 0xFB0A3D212DC81290, + 0x7CE66634BC9D0B9A, + 0x1C1FFFC1EBC44E80, + 0xA327FFB266B56220, + 0x4BF1FF9F0062BAA8, + 0x6F773FC3603DB4A9, + 0xCB550FB4384D21D4, + 0x7E2A53A146606A48, + 0x2EDA7444CBFC426D, + 0xFA911155FEFB5309, + 0x793555AB7EBA27CB, + 0x4BC1558B2F3458DF, + 0x9EB1AAEDFB016F16, + 0x465E15A979C1CADC, + 0x0BFACD89EC191ECA, + 0xCEF980EC671F667C, + 0x82B7E12780E7401B, + 0xD1B2ECB8B0908811, + 0x861FA7E6DCB4AA15, + 0x67A791E093E1D49A, + 0xE0C8BB2C5C6D24E0, + 0x58FAE9F773886E19, + 0xAF39A475506A899F, + 0x6D8406C952429603, + 0xC8E5087BA6D33B84, + 0xFB1E4A9A90880A65, + 0x5CF2EEA09A55067F, + 0xF42FAA48C0EA481F, + 0xF13B94DAF124DA27, + 0x76C53D08D6B70858, + 0x54768C4B0C64CA6E, + 0xA9942F5DCF7DFD0A, + 0xD3F93B35435D7C4C, + 0xC47BC5014A1A6DB0, + 0x359AB6419CA1091B, + 0xC30163D203C94B62, + 0x79E0DE63425DCF1D, + 0x985915FC12F542E5, + 0x3E6F5B7B17B2939E, + 0xA705992CEECF9C43, + 0x50C6FF782A838353, + 0xA4F8BF5635246428, + 0x871B7795E136BE99, + 0x28E2557B59846E3F, + 0x331AEADA2FE589CF, + 0x3FF0D2C85DEF7622, + 0x0FED077A756B53AA, + 0xD3E8495912C62894, + 0x64712DD7ABBBD95D, + 0xBD8D794D96AACFB4, + 0xECF0D7A0FC5583A1, + 0xF41686C49DB57245, + 0x311C2875C522CED6, + 0x7D633293366B828B, + 0xAE5DFF9C02033197, + 0xD9F57F830283FDFD, + 0xD072DF63C324FD7C, + 0x4247CB9E59F71E6D, + 0x52D9BE85F074E609, + 0x67902E276C921F8B, + 0x00BA1CD8A3DB53B7, + 0x80E8A40ECCD228A5, + 0x6122CD128006B2CE, + 0x796B805720085F81, + 0xCBE3303674053BB1, + 0xBEDBFC4411068A9D, + 0xEE92FB5515482D44, + 0x751BDD152D4D1C4B, + 0xD262D45A78A0635D, + 0x86FB897116C87C35, + 0xD45D35E6AE3D4DA1, + 0x8974836059CCA109, + 0x2BD1A438703FC94B, + 0x7B6306A34627DDCF, + 0x1A3BC84C17B1D543, + 0x20CABA5F1D9E4A94, + 0x547EB47B7282EE9C, + 0xE99E619A4F23AA43, + 0x6405FA00E2EC94D4, + 0xDE83BC408DD3DD05, + 0x9624AB50B148D446, + 0x3BADD624DD9B0957, + 0xE54CA5D70A80E5D6, + 0x5E9FCF4CCD211F4C, + 0x7647C3200069671F, + 0x29ECD9F40041E073, + 0xF468107100525890, + 0x7182148D4066EEB4, + 0xC6F14CD848405531, + 0xB8ADA00E5A506A7D, + 0xA6D90811F0E4851C, + 0x908F4A166D1DA663, + 0x9A598E4E043287FE, + 0x40EFF1E1853F29FE, + 0xD12BEE59E68EF47D, + 0x82BB74F8301958CE, + 0xE36A52363C1FAF02, + 0xDC44E6C3CB279AC2, + 0x29AB103A5EF8C0B9, + 0x7415D448F6B6F0E8, + 0x111B495B3464AD21, + 0xCAB10DD900BEEC35, + 0x3D5D514F40EEA742, + 0x0CB4A5A3112A5113, + 0x47F0E785EABA72AC, + 0x59ED216765690F57, + 0x306869C13EC3532C, + 0x1E414218C73A13FC, + 0xE5D1929EF90898FB, + 0xDF45F746B74ABF39, + 0x6B8BBA8C328EB784, + 0x066EA92F3F326565, + 0xC80A537B0EFEFEBE, + 0xBD06742CE95F5F37, + 0x2C48113823B73704, + 0xF75A15862CA504C5, + 0x9A984D73DBE722FB, + 0xC13E60D0D2E0EBBA, + 0x318DF905079926A9, + 0xFDF17746497F7053, + 0xFEB6EA8BEDEFA634, + 0xFE64A52EE96B8FC1, + 0x3DFDCE7AA3C673B1, + 0x06BEA10CA65C084F, + 0x486E494FCFF30A62, + 0x5A89DBA3C3EFCCFB, + 0xF89629465A75E01D, + 0xF6BBB397F1135824, + 0x746AA07DED582E2D, + 0xA8C2A44EB4571CDC, + 0x92F34D62616CE413, + 0x77B020BAF9C81D18, + 0x0ACE1474DC1D122F, + 0x0D819992132456BB, + 0x10E1FFF697ED6C69, + 0xCA8D3FFA1EF463C2, + 0xBD308FF8A6B17CB2, + 0xAC7CB3F6D05DDBDF, + 0x6BCDF07A423AA96B, + 0x86C16C98D2C953C6, + 0xE871C7BF077BA8B8, + 0x11471CD764AD4973, + 0xD598E40D3DD89BCF, + 0x4AFF1D108D4EC2C3, + 0xCEDF722A585139BA, + 0xC2974EB4EE658829, + 0x733D226229FEEA33, + 0x0806357D5A3F5260, + 0xCA07C2DCB0CF26F8, + 0xFC89B393DD02F0B6, + 0xBBAC2078D443ACE3, + 0xD54B944B84AA4C0E, + 0x0A9E795E65D4DF11, + 0x4D4617B5FF4A16D6, + 0x504BCED1BF8E4E46, + 0xE45EC2862F71E1D7, + 0x5D767327BB4E5A4D, + 0x3A6A07F8D510F870, + 0x890489F70A55368C, + 0x2B45AC74CCEA842F, + 0x3B0B8BC90012929D, + 0x09CE6EBB40173745, + 0xCC420A6A101D0516, + 0x9FA946824A12232E, + 0x47939822DC96ABF9, + 0x59787E2B93BC56F7, + 0x57EB4EDB3C55B65B, + 0xEDE622920B6B23F1, + 0xE95FAB368E45ECED, + 0x11DBCB0218EBB414, + 0xD652BDC29F26A11A, + 0x4BE76D3346F04960, + 0x6F70A4400C562DDC, + 0xCB4CCD500F6BB953, + 0x7E2000A41346A7A8, + 0x8ED400668C0C28C9, + 0x728900802F0F32FB, + 0x4F2B40A03AD2FFBA, + 0xE2F610C84987BFA8, + 0x0DD9CA7D2DF4D7C9, + 0x91503D1C79720DBB, + 0x75A44C6397CE912A, + 0xC986AFBE3EE11ABA, + 0xFBE85BADCE996169, + 0xFAE27299423FB9C3, + 0xDCCD879FC967D41A, + 0x5400E987BBC1C921, + 0x290123E9AAB23B69, + 0xF9A0B6720AAF6521, + 0xF808E40E8D5B3E6A, + 0xB60B1D1230B20E04, + 0xB1C6F22B5E6F48C3, + 0x1E38AEB6360B1AF3, + 0x25C6DA63C38DE1B0, + 0x579C487E5A38AD0E, + 0x2D835A9DF0C6D852, + 0xF8E431456CF88E66, + 0x1B8E9ECB641B5900, + 0xE272467E3D222F40, + 0x5B0ED81DCC6ABB10, + 0x98E947129FC2B4EA, + 0x3F2398D747B36224, + 0x8EEC7F0D19A03AAD, + 0x1953CF68300424AC, + 0x5FA8C3423C052DD7, + 0x3792F412CB06794D, + 0xE2BBD88BBEE40BD0, + 0x5B6ACEAEAE9D0EC4, + 0xF245825A5A445275, + 0xEED6E2F0F0D56713, + 0x55464DD69685606C, + 0xAA97E14C3C26B887, + 0xD53DD99F4B3066A8, + 0xE546A8038EFE4029, + 0xDE98520472BDD033, + 0x963E66858F6D4440, + 0xDDE7001379A44AA8, + 0x5560C018580D5D52, + 0xAAB8F01E6E10B4A7, + 0xCAB3961304CA70E8, + 0x3D607B97C5FD0D22, + 0x8CB89A7DB77C506B, + 0x77F3608E92ADB243, + 0x55F038B237591ED3, + 0x6B6C46DEC52F6688, + 0x2323AC4B3B3DA015, + 0xABEC975E0A0D081B, + 0x96E7BD358C904A21, + 0x7E50D64177DA2E55, + 0xDDE50BD1D5D0B9EA, + 0x955E4EC64B44E864, + 0xBD5AF13BEF0B113F, + 0xECB1AD8AEACDD58E, + 0x67DE18EDA5814AF2, + 0x80EACF948770CED7, + 0xA1258379A94D028D, + 0x096EE45813A04330, + 0x8BCA9D6E188853FC, + 0x775EA264CF55347E, + 0x95364AFE032A819D, + 0x3A83DDBD83F52205, + 0xC4926A9672793543, + 0x75B7053C0F178294, + 0x5324C68B12DD6338, + 0xD3F6FC16EBCA5E03, + 0x88F4BB1CA6BCF584, + 0x2B31E9E3D06C32E5, + 0x3AFF322E62439FCF, + 0x09BEFEB9FAD487C3, + 0x4C2EBE687989A9B4, + 0x0F9D37014BF60A10, + 0x538484C19EF38C94, + 0x2865A5F206B06FBA, + 0xF93F87B7442E45D4, + 0xF78F69A51539D749, + 0xB573440E5A884D1B, + 0x31680A88F8953031, + 0xFDC20D2B36BA7C3D, + 0x3D32907604691B4D, + 0xA63F9A49C2C1B110, + 0x0FCF80DC33721D54, + 0xD3C36113404EA4A9, + 0x645A1CAC083126E9, + 0x3D70A3D70A3D70A4, + 0xCCCCCCCCCCCCCCCD, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x4000000000000000, + 0x5000000000000000, + 0xA400000000000000, + 0x4D00000000000000, + 0xF020000000000000, + 0x6C28000000000000, + 0xC732000000000000, + 0x3C7F400000000000, + 0x4B9F100000000000, + 0x1E86D40000000000, + 0x1314448000000000, + 0x17D955A000000000, + 0x5DCFAB0800000000, + 0x5AA1CAE500000000, + 0xF14A3D9E40000000, + 0x6D9CCD05D0000000, + 0xE4820023A2000000, + 0xDDA2802C8A800000, + 0xD50B2037AD200000, + 0x4526F422CC340000, + 0x9670B12B7F410000, + 0x3C0CDD765F114000, + 0xA5880A69FB6AC800, + 0x8EEA0D047A457A00, + 0x72A4904598D6D880, + 0x47A6DA2B7F864750, + 0x999090B65F67D924, + 0xFFF4B4E3F741CF6D, + 0xBFF8F10E7A8921A4, + 0xAFF72D52192B6A0D, + 0x9BF4F8A69F764490, + 0x02F236D04753D5B5, + 0x01D762422C946591, + 0x424D3AD2B7B97EF5, + 0xD2E0898765A7DEB2, + 0x63CC55F49F88EB2F, + 0x3CBF6B71C76B25FB, + 0x8BEF464E3945EF7A, + 0x97758BF0E3CBB5AC, + 0x3D52EEED1CBEA317, + 0x4CA7AAA863EE4BDD, + 0x8FE8CAA93E74EF6A, + 0xB3E2FD538E122B45, + 0x60DBBCA87196B616, + 0xBC8955E946FE31CE, + 0x6BABAB6398BDBE41, + 0xC696963C7EED2DD2, + 0xFC1E1DE5CF543CA3, + 0x3B25A55F43294BCC, + 0x49EF0EB713F39EBF, + 0x6E3569326C784337, + 0x49C2C37F07965405, + 0xDC33745EC97BE906, + 0x69A028BB3DED71A4, + 0xC40832EA0D68CE0D, + 0xF50A3FA490C30190, + 0x792667C6DA79E0FA, + 0x577001B891185939, + 0xED4C0226B55E6F87, + 0x544F8158315B05B4, + 0x696361AE3DB1C721, + 0x03BC3A19CD1E38EA, + 0x04AB48A04065C724, + 0x62EB0D64283F9C76, + 0x3BA5D0BD324F8394, + 0xCA8F44EC7EE36479, + 0x7E998B13CF4E1ECC, + 0x9E3FEDD8C321A67F, + 0xC5CFE94EF3EA101E, + 0xBBA1F1D158724A13, + 0x2A8A6E45AE8EDC98, + 0xF52D09D71A3293BE, + 0x593C2626705F9C56, + 0x6F8B2FB00C77836C, + 0x0B6DFB9C0F956447, + 0x4724BD4189BD5EAC, + 0x58EDEC91EC2CB658, + 0x2F2967B66737E3ED, + 0xBD79E0D20082EE74, + 0xECD8590680A3AA11, + 0xE80E6F4820CC9496, + 0x3109058D147FDCDE, + 0xBD4B46F0599FD415, + 0x6C9E18AC7007C91A, + 0x03E2CF6BC604DDB0, + 0x84DB8346B786151D, + 0xE612641865679A64, + 0x4FCB7E8F3F60C07E, + 0xE3BE5E330F38F09E, + 0x5CADF5BFD3072CC5, + 0x73D9732FC7C8F7F7, + 0x2867E7FDDCDD9AFA, + 0xB281E1FD541501B9, + 0x1F225A7CA91A4227, + 0x3375788DE9B06958, + 0x0052D6B1641C83AE, + 0xC0678C5DBD23A49A, + 0xF840B7BA963646E0, + 0xB650E5A93BC3D898, + 0xA3E51F138AB4CEBE, + 0xC66F336C36B10137, + 0xB80B0047445D4185, + 0xA60DC059157491E6, + 0x87C89837AD68DB30, + 0x29BABE4598C311FC, + 0xF4296DD6FEF3D67B, + 0x1899E4A65F58660D, + 0x5EC05DCFF72E7F90, + 0x76707543F4FA1F74, + 0x6A06494A791C53A8, + 0x0487DB9D17636892, + 0x45A9D2845D3C42B7, + 0x0B8A2392BA45A9B2, + 0x8E6CAC7768D7141F, + 0x3207D795430CD927, + 0x7F44E6BD49E807B8, + 0x5F16206C9C6209A6, + 0x36DBA887C37A8C10, + 0xC2494954DA2C978A, + 0xF2DB9BAA10B7BD6C, + 0x6F92829494E5ACC7, + 0xCB772339BA1F17F9, + 0xFF2A760414536EFC, + 0xFEF5138519684ABB, + 0x7EB258665FC25D69, + 0xEF2F773FFBD97A62, + 0xAAFB550FFACFD8FA, + 0x95BA2A53F983CF39, + 0xDD945A747BF26184, + 0x94F971119AEEF9E4, + 0x7A37CD5601AAB85E, + 0xAC62E055C10AB33B, + 0x577B986B314D6009, + 0xED5A7E85FDA0B80B, + 0x14588F13BE847307, + 0x596EB2D8AE258FC9, + 0x6FCA5F8ED9AEF3BB, + 0x25DE7BB9480D5855, + 0xAF561AA79A10AE6A, + 0x1B2BA1518094DA05, + 0x90FB44D2F05D0843, + 0x353A1607AC744A54, + 0x42889B8997915CE9, + 0x69956135FEBADA11, + 0x43FAB9837E699096, + 0x94F967E45E03F4BB, + 0x1D1BE0EEBAC278F5, + 0x6462D92A69731732, + 0x7D7B8F7503CFDCFF, + 0x5CDA735244C3D43F, + 0x3A0888136AFA64A7, + 0x088AAA1845B8FDD1, + 0x8AAD549E57273D45, + 0x36AC54E2F678864B, + 0x84576A1BB416A7DE, + 0x656D44A2A11C51D5, + 0x9F644AE5A4B1B325, + 0x873D5D9F0DDE1FEF, + 0xA90CB506D155A7EA, + 0x09A7F12442D588F3, + 0x0C11ED6D538AEB2F, + 0x8F1668C8A86DA5FB, + 0xF96E017D694487BD, + 0x37C981DCC395A9AC, + 0x85BBE253F47B1417, + 0x93956D7478CCEC8E, + 0x387AC8D1970027B2, + 0x06997B05FCC0319F, + 0x441FECE3BDF81F03, + 0xD527E81CAD7626C4, + 0x8A71E223D8D3B075, + 0xF6872D5667844E49, + 0xB428F8AC016561DB, + 0xE13336D701BEBA52, + 0xECC0024661173473, + 0x27F002D7F95D0190, + 0x31EC038DF7B441F4, + 0x7E67047175A15271, + 0x0F0062C6E984D387, + 0x52C07B78A3E60868, + 0xA7709A56CCDF8A83, + 0x88A66076400BB692, + 0x6ACFF893D00EA436, + 0x0583F6B8C4124D43, + 0xC3727A337A8B704A, + 0x744F18C0592E4C5D, + 0x1162DEF06F79DF74, + 0x8ADDCB5645AC2BA8, + 0x6D953E2BD7173693, + 0xC8FA8DB6CCDD0437, + 0x1D9C9892400A22A2, + 0x2503BEB6D00CAB4B, + 0x2E44AE64840FD61E, + 0x5CEAECFED289E5D3, + 0x7425A83E872C5F47, + 0xD12F124E28F77719, + 0x82BD6B70D99AAA70, + 0x636CC64D1001550C, + 0x3C47F7E05401AA4F, + 0x65ACFAEC34810A71, + 0x7F1839A741A14D0D, + 0x1EDE48111209A051, + 0x934AED0AAB460432, + 0xF81DA84D5617853F, + 0x36251260AB9D668F, + 0xC1D72B7C6B426019, + 0xB24CF65B8612F820, + 0xDEE033F26797B628, + 0x169840EF017DA3B1, + 0x8E1F289560EE864F, + 0xF1A6F2BAB92A27E3, + 0xAE10AF696774B1DB, + 0xACCA6DA1E0A8EF29, + 0x17FD090A58D32AF3, + 0xDDFC4B4CEF07F5B0, + 0x4ABDAF101564F98E, + 0x9D6D1AD41ABE37F2, + 0x84C86189216DC5EE, + 0x32FD3CF5B4E49BB5, + 0x3FBC8C33221DC2A2, + 0x0FABAF3FEAA5334A, + 0x29CB4D87F2A7400E, + 0x743E20E9EF511012, + 0x914DA9246B255417, + 0x1AD089B6C2F7548E, + 0xA184AC2473B529B2, + 0xC9E5D72D90A2741E, + 0x7E2FA67C7A658893, + 0xDDBB901B98FEEAB8, + 0x552A74227F3EA565, + 0xD53A88958F87275F, + 0x8A892ABAF368F137, + 0x2D2B7569B0432D85, + 0x9C3B29620E29FC73, + 0x8349F3BA91B47B90, + 0x241C70A936219A74, + 0xED238CD383AA0111, + 0xF4363804324A40AB, + 0xB143C6053EDCD0D5, + 0xDD94B7868E94050A, + 0xCA7CF2B4191C8327, + 0xFD1C2F611F63A3F0, + 0xBC633B39673C8CEC, + 0xD5BE0503E085D814, + 0x4B2D8644D8A74E19, + 0xDDF8E7D60ED1219F, + 0xCABB90E5C942B503, + 0x3D6A751F3B936244, + 0x0CC512670A783AD5, + 0x27FB2B80668B24C5, + 0xB1F9F660802DEDF6, + 0x5E7873F8A0396974, + 0xDB0B487B6423E1E8, + 0x91CE1A9A3D2CDA63, + 0x7641A140CC7810FB, + 0xA9E904C87FCB0A9D, + 0x546345FA9FBDCD44, + 0xA97C177947AD4095, + 0x49ED8EABCCCC485D, + 0x5C68F256BFFF5A75, + 0x73832EEC6FFF3112, + 0xC831FD53C5FF7EAB, + 0xBA3E7CA8B77F5E56, + 0x28CE1BD2E55F35EB, + 0x7980D163CF5B81B3, + 0xD7E105BCC3326220, + 0x8DD9472BF3FEFAA8, + 0xB14F98F6F0FEB952, + 0x6ED1BF9A569F33D3, + 0x0A862F80EC4700C8, + 0xCD27BB612758C0FA, + 0x8038D51CB897789C, + 0xE0470A63E6BD56C3, + 0x1858CCFCE06CAC74, + 0x0F37801E0C43EBC9, + 0xD30560258F54E6BB, + 0x47C6B82EF32A2069, + 0x4CDC331D57FA5442, + 0xE0133FE4ADF8E952, + 0x58180FDDD97723A7, + 0x570F09EAA7EA7648, + 0x2CD2CC6551E513DA, + 0xF8077F7EA65E58D1, + 0xFB04AFAF27FAF783, + 0x79C5DB9AF1F9B563, + 0x18375281AE7822BC, + 0x8F2293910D0B15B6, + 0xB2EB3875504DDB23, + 0x5FA60692A46151EC, + 0xDBC7C41BA6BCD333, + 0x12B9B522906C0800, + 0xD768226B34870A00, + 0xE6A1158300D46640, + 0x60495AE3C1097FD0, + 0x385BB19CB14BDFC4, + 0x46729E03DD9ED7B5, + 0x6C07A2C26A8346D1, + 0xC7098B7305241886, + 0xB8CBEE4FC66D1EA7, + 0x737F74F1DC043328, + 0x505F522E53053FF2, + 0x647726B9E7C68FEF, + 0x5ECA783430DC19F5, + 0xB67D16413D132073, + 0xE41C5BD18C57E88F, + 0x8E91B962F7B6F15A, + 0x723627BBB5A4ADB0, + 0xCEC3B1AAA30DD91C, + 0x213A4F0AA5E8A7B2, + 0xA988E2CD4F62D19E, + 0x93EB1B80A33B8605, + 0xBC72F130660533C3, + 0xEB8FAD7C7F8680B4, + 0xA67398DB9F6820E1, + 0x88083F8943A1148D, + 0x6A0A4F6B948959B0, + 0x848CE34679ABB01C, + 0xF2D80E0C0C0B4E12, + 0x6F8E118F0F0E2196, + 0x4B7195F2D2D1A9FB, + 0x8F26FDB7C3C30A3D, + 0xB2F0BD25B4B3CCCC, + 0xDFACEC6F21E0C000, + 0x9798278AEA58EFFF, + 0x5EBF18B6D2779600, + 0xF66EDEE487157B80, + 0xB40A969DA8DADA5F, + 0x70869E228988C87C, + 0xCCA845AB2BEAFA9B, + 0x3FD25715F6E5B941, + 0x07E3766DBA4F93C9, + 0x89DC540928E378BB, + 0x2C53690B731C56EA, + 0x9BB421A727F1B652, + 0x42A12A10F1EE23E7, + 0x134974952E69ACE0, + 0x2C0DE8DD3D020C0C, + 0x771163148C428F0F, + 0x54D5BBD9AF5332D3, + 0x350595680D93FFC4, + 0x8246FAC210F8FFB5, + 0x62D8B97295373FA2, + 0xFDC773E79D4287C5, + 0x7D3950E1849329B7, + 0xDC87A519E5B7F424, + 0xA9D4C7302F92F897, + 0xD449F8FC3B77B6BC, + 0xC95C773B4A55A46B, + 0x7DD9CA850E7586C3, + 0x5D503D265212E874, + 0x34A44C6FE697A291, + 0x40E6AFC5F01EC59B, + 0x91205BB76C267701, + 0x356872A5473014C1, + 0xC2C28F4E98FC19F2, + 0xD9B999911F9D9037, + 0x1027FFF56784F445, + 0xD431FFF2C1663156, + 0x049F3FF7B8DFDED6, + 0x85C70FF5A717D68B, + 0x2738D3F310DDCC2E, + 0xB8838477EA8A9F9D, + 0xE6A46595E52D4784, + 0x604D7EFB5E789965, + 0x1C306F5D1B0B5FDF, + 0x633C8B3461CE37D7, + 0x3C0BAE017A41C5CD, + 0xC5874CC0EC691BA0, + 0xF6E91FF127836288, + 0xB4A367ED71643B2A, + 0x50E620F466DEA4FA, + 0xA51FA93180964E39, + 0x8E67937DE0BBE1C7, + 0x7900BC2EAC756D1C, + 0x5740EB3A5792C863, + 0x2D112608ED777A7C, + 0x5C2AB7C5946AAC8E, + 0xF33565B6F98557B1, + 0xF002BF24B7E6AD9D, + 0xB601B776F2F02C82, + 0xE3822554AFAC37A3, + 0xDC62AEA9DB97458C, + 0x537B5A54527D16EE, + 0x742D1874B38E2E55, + 0xD1385E91E071B9EA, + 0x45867636588E2865, + 0x4B7409E1F758D93F, + 0x5E510C5A752F0F8F, + 0xB5E54F71127AD373, + 0x71AF51A6AB8CC428, + 0x4E1B2610566FF531, + 0xA1A1EF946C0BF27E, + 0x250535BCC387778F, + 0x6E46832BF4695572, + 0x89D823F6F183AACF, + 0x9627167A56F24AC1, + 0xBBB0DC18ECAEDD72, + 0x6A9D131F27DA94CE, + 0xA2A22BF378E89D01, + 0x0B4AB6F05722C441, + 0x4E1D64AC6CEB7551, + 0x90D25EEBC4132953, + 0xF506F6A6B517F3A7, + 0xF248B450625DF091, + 0xD76D70B23D7AB65B, + 0x0D48CCDECCD963F2, + 0x109B0016800FBCEE, + 0xCA60E00E1009D615, + 0x3CF91811940C4B9A, + 0xCC375E15F90F5E80, + 0x3FA29ACDBBA99B10, + 0x8F8B41812A9401D4, + 0x336E11E175390249, + 0x80499659D28742DC, + 0x302DFDF8239489C9, + 0xBC397D762C79AC3C, + 0x2B47DCD3B798174B, + 0xDB0CEA0452BF0E8F, + 0x51D02485676ED232, + 0xA6442DA6C14A86BF, + 0xA7EA9C8838CE9437, + 0x91E543AA47023945, + 0xB65E9494D8C2C796, + 0xB1FB1CDD0779BCBE, + 0xDE79E41449582BED, + 0xD6185D195BAE36E9, + 0x05CF3A2FD94CE251, + 0x074308BBCFA01AE6, + 0x4913CAEAC388219F, + 0x6DAC5ED2BA351504, + 0x8917768768C25A44, + 0xAB5D542942F2F0D6, + 0x4B1A5499C9D7D685, + 0x1DE0E9C03C4DCC27, + 0x255924304B613F31, + 0x3757B69E2F1CC77E, + 0xC52DA445BAE3F95E, + 0xF6790D57299CF7B5, + 0xFA0BA8567A021AD1, + 0xF88E926C1882A186, + 0xF6B237071EA349E7, + 0xF45EC4C8E64C1C61, + 0x78BB3AFD8FEF91BD, + 0xD6EA09BCF3EB762C, + 0x0CA48C2C30E653B7, + 0x27E6D79B9E8FF452, + 0xF1E08D828633F167, + 0xAE58B0E327C0EDC0, + 0x4CF76E8DF8D89498, + 0x60354A31770EB9BE, + 0x78429CBDD4D2682E, + 0xCB29A1F6A503811D, + 0x7DF40A744E446164, + 0x1D710D1161D579BD, + 0xF266A82ADD256C16, + 0x2F005235946EC71C, + 0x3AC066C2F98A78E2, + 0xC4B84039DBF68B8E, + 0xB5E6504852F42E71, + 0xE35FE45A67B13A0D, + 0x0E1BEEB880CEC448, + 0xD1A2EA66A102755A, + 0x460BA500494312B1, + 0xEBC747202DC9EBAF, + 0xA6B918E8393C669A, + 0x90675F22478B8041, + 0x7A409B756CB73028, + 0x58D0C252C7E4FC33, + 0xAF04F2E779DE3B3F, + 0xDAC62FA15855CA0F, + 0x48BBDDC4D7359E49, + 0x5AEAD5360D0305DC, + 0x71A58A839043C753, + 0xA70776923A2A5C94, + 0x50C95436C8B4F3B9, + 0x64FBA9447AE230A7, + 0xBF1D49CACCCD5E68, + 0xEEE49C3D8000B602, + 0x6A9DC34CE000E383, + 0x02A29A100C008E32, + 0xC34B40940F00B1BE, + 0xF41E10B912C0DE2E, + 0x7892CA73ABB88ADD, + 0xD6B77D1096A6AD94, + 0xCC655C54BC5058F9, +]; diff --git a/source/mir/bignum/internal/kernel.d b/source/mir/bignum/internal/kernel.d new file mode 100644 index 00000000..3f4cc547 --- /dev/null +++ b/source/mir/bignum/internal/kernel.d @@ -0,0 +1,225 @@ +module mir.bignum.internal.kernel; + +import mir.bignum.internal.phobos_kernel; +public import mir.bignum.internal.phobos_kernel: karatsubaRequiredBuffSize, divisionRequiredBuffSize; + +private inout(uint)[] toUints(return scope inout ulong[] data) + @trusted pure nothrow @nogc +{ + auto ret = cast(typeof(return)) data; + if (ret.length && ret[$ - 1] == 0) + ret = ret[0 .. $ - 1]; + return ret; +} + +static if (is(size_t == ulong)) +size_t multiply( + scope ulong[] c, + scope const(ulong)[] a, + scope const(ulong)[] b, + scope ulong[] buffer, +) + @safe pure nothrow @nogc + in (c.length >= a.length + b.length) +{ + pragma (inline, false); + + c[a.length + b.length - 1] = 0; + + auto length = multiply( + cast(uint[]) c[], + a.toUints, + b.toUints, + cast(uint[]) buffer[], + ); + + return length / 2 + length % 2; +} + +size_t multiply( + scope uint[] c, + scope const(uint)[] a, + scope const(uint)[] b, + scope uint[] buffer, +) + @trusted pure nothrow @nogc + in (c.length >= a.length + b.length) +{ + pragma (inline, false); + + if (a.length < b.length) + { + auto t = a; + a = b; + b = t; + } + + if (b.length == 0) + return 0; + + assert(a.length); + assert(b.length); + assert(c.length); + + c = c[0 .. a.length + b.length]; + + if (a is b) + squareInternal(c, a, buffer); + else + mulInternal(c, a, b, buffer); + + auto ret = a.length + b.length; + ret -= ret && !c[ret - 1]; + return ret; +} + + +static if (is(size_t == ulong)) +size_t divMod( + scope ulong[] q, + scope ulong[] r, + scope const(ulong)[] u, + scope const(ulong)[] v, + scope ulong[] buffer, +) + @trusted pure nothrow @nogc + in (u.length == 0 || u[$ - 1]) + in (v.length) + in (v[$ - 1]) + in (q.length + v.length >= u.length + 1) +{ + pragma (inline, false); + + sizediff_t idx = u.length - v.length; + if (idx < 0) + return 0; + + auto ui = u.toUints; + auto vi = v.toUints; + + auto length = divMod( + cast(uint[])q, + cast(uint[])r, + ui, + vi, + cast(uint[]) buffer, + ); + + if (r.length && vi.length % 2) + r[vi.length / 2] &= uint.max; + + if (q.ptr is r.ptr) + return 0; + + auto ret = length / 2; + if (length % 2) + q[ret++] &= uint.max; + return ret; +} + +size_t divMod( + scope uint[] q, + scope uint[] r, + scope const(uint)[] u, + scope const(uint)[] v, + scope uint[] buffer, +) + @trusted pure nothrow @nogc + in (u.length == 0 || u[$ - 1]) + in (v.length) + in (v[$ - 1]) + in (q.length + v.length >= u.length + 1) +{ + pragma (inline, false); + + import mir.bitop: ctlz; + import mir.utility: _expect; + + if (u.length < v.length) + return 0; + + q = q[0 .. u.length - v.length + 1]; + if (v.length == 1) + { + if (q !is u) + q[] = u; + auto rem = multibyteDivAssign(q, v[0], 0); + if (r) + { + r[0] = rem; + r[1 .. $] = 0; + } + } + else + { + divModInternal(q[], r ? r[0 .. v.length] : null, u, v, buffer); + } + + auto length = u.length - v.length; + return length + (q[length] != 0); +} + +/// +version(mir_bignum_test) +unittest +{ + import mir.bignum.fixed: UInt; + import mir.bignum.low_level_view: BigUIntView; + + uint[100] buffer = void; + auto divMod(BigUIntView!uint a, BigUIntView!uint b, BigUIntView!uint c) + { + .divMod( + c.coefficients, + a.coefficients, + a.coefficients, + b.coefficients, + buffer, + ); + a.coefficients[b.coefficients.length .. $] = 0; + } + + { + // Test division by a single-digit divisor here. + auto dividend = BigUIntView!uint.fromHexString("5"); + auto divisor = BigUIntView!uint.fromHexString("5"); + auto quotient = BigUIntView!uint([0u]); + // auto remainder = BigUIntView!uint.fromHexString("0"); + + divMod(dividend, divisor, quotient); + assert(quotient == BigUIntView!uint.fromHexString("1")); + } + + { + // Test division by a single-digit divisor here. + auto dividend = BigUIntView!uint.fromHexString("55a325ad18b2a77120d870d987d5237473790532acab45da44bc07c92c92babf"); + auto divisor = BigUIntView!uint.fromHexString("5"); + auto quotient = BigUIntView!uint.fromHexString("1000000000000000000000000000000000000000000000000000000000000000"); + divMod(dividend, divisor, quotient); + assert(dividend.normalized == BigUIntView!uint.fromHexString("3")); + assert(quotient == BigUIntView!uint.fromHexString("1120a1229e8a217d0691b02b819107174a4b677088ef0df874259b283c1d588c")); + } + + // Test big number division + { + auto dividend = BigUIntView!uint.fromHexString("55a325ad18b2a77120d870d987d5237473790532acab45da44bc07c92c92babf0b5e2e2c7771cd472ae5d7acdb159a56fbf74f851a058ae341f69d1eb750d7e3"); + auto divisor = BigUIntView!uint.fromHexString("55e5669576d31726f4a9b58a90159de5923adc6c762ebd3c4ba518d495229072"); + auto quotient = BigUIntView!uint.fromHexString("10000000000000000000000000000000000000000000000000000000000000000"); + // auto remainder = BigUIntView!uint.fromHexString("10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + + divMod(dividend, divisor, quotient); + assert(quotient.normalized == BigUIntView!uint.fromHexString("ff3a8aa4da35237811a0ffbf007fe938630dee8a810f2f82ae01f80c033291f6")); + assert(dividend.normalized == BigUIntView!uint.fromHexString("27926263cf248bef1c2cd63ea004d9f7041bffc8568560ec30fc9a9548057857")); + } + + // Trigger the adding back sequence + { + auto dividend = BigUIntView!uint.fromHexString("800000000000000000000003"); + auto divisor = BigUIntView!uint.fromHexString("200000000000000000000001"); + auto quotient = BigUIntView!uint([0u]); + // auto remainder = BigUIntView!uint.fromHexString("100000000000000000000000"); + divMod(dividend, divisor, quotient); + assert(quotient == BigUIntView!uint.fromHexString("3")); + assert(dividend == BigUIntView!uint.fromHexString("200000000000000000000000")); + } +} diff --git a/source/mir/bignum/internal/parse.d b/source/mir/bignum/internal/parse.d new file mode 100644 index 00000000..9cd77d38 --- /dev/null +++ b/source/mir/bignum/internal/parse.d @@ -0,0 +1,411 @@ +module mir.bignum.internal.parse; + +import std.traits: isSomeChar; +import mir.parse: DecimalExponentKey; + +/+ +https://arxiv.org/abs/2101.11408 +Number Parsing at a Gigabyte per Second +Daniel Lemire ++/ +bool isMadeOfEightDigits()(ref const char[8] chars) +{ + pragma(inline, true); + ulong val = (cast(ulong[1]) cast(ubyte[8]) chars)[0]; + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & 0x8080808080808080)); +} + +// ditto +uint parseEightDigits()(ref const char[8] chars) +{ + pragma(inline, true); + ulong val = (cast(ulong[1]) cast(ubyte[8]) chars)[0]; + val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; + val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; + return uint((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); +} + +/++ ++/ +struct SmallDecimalParsingResult +{ + /// + bool success; + /// + bool sign; + /// + DecimalExponentKey key; + /// + long exponent; + /// + ulong coefficient; +} + +/++ ++/ +SmallDecimalParsingResult parseJsonNumberImpl()(scope const(char)[] str) + @trusted pure nothrow @nogc + in (str.length) +{ + pragma(inline, true); + + alias W = ulong; + enum bool allowSpecialValues = false; + enum bool allowDotOnBounds = false; + enum bool allowDExponent = false; + enum bool allowStartingPlus = false; + enum bool allowUnderscores = false; + enum bool allowLeadingZeros = false; + enum bool allowExponent = true; + enum bool checkEmpty = false; + + typeof(return) result; + + bool mullAdd(ulong multiplier, ulong carry) + { + import mir.checkedint: mulu, addu; + bool overflow; + result.coefficient = mulu(result.coefficient, multiplier, overflow); + if (overflow) + return overflow; + result.coefficient = addu(result.coefficient, carry, overflow); + return overflow; + } + + alias impl = decimalFromStringImpl!(mullAdd, ulong); + alias specialization = impl!(char, + allowSpecialValues, + allowDotOnBounds, + allowDExponent, + allowStartingPlus, + allowUnderscores, + allowLeadingZeros, + allowExponent, + checkEmpty, + ); + + with(result) + success = specialization(str, key, exponent, sign); + return result; +} + +version(mir_test) +unittest +{ + import mir.test; + auto res = "-0.1234567890e-30".parseJsonNumberImpl; + res.key.should == DecimalExponentKey.e; + res.sign.should == true; + res.exponent.should == -40; + res.coefficient.should == 1234567890; +} + +version(mir_test) +unittest +{ + import mir.test; + auto res = "-12345.67890e-10".parseJsonNumberImpl; + res.key.should == DecimalExponentKey.e; + res.sign.should == true; + res.exponent.should == -15; + res.coefficient.should == 1234567890; +} + + +version(mir_test) +unittest +{ + import mir.test; + auto res = "2.9802322387695312E-8".parseJsonNumberImpl; + res.key.should == DecimalExponentKey.E; + res.sign.should == false; + res.exponent.should == -24; + res.coefficient.should == 29802322387695312; +} + +/++ +Returns: false in case of overflow or incorrect string. ++/ +template decimalFromStringImpl(alias mullAdd, W = size_t) +{ + bool decimalFromStringImpl(C, + bool allowSpecialValues, + bool allowDotOnBounds, + bool allowDExponent, + bool allowStartingPlus, + bool allowUnderscores, + bool allowLeadingZeros, + bool allowExponent, + bool checkEmpty, + ) + (scope const(C)[] str, out DecimalExponentKey key, scope ref long exponent, scope ref bool sign) + scope @trusted pure @nogc nothrow + if (isSomeChar!C) + { + version(LDC) + pragma(inline, true); + + import mir.utility: _expect; + static if (checkEmpty) + { + if (_expect(str.length == 0, false)) + return false; + } + + sign = str[0] == '-'; + if (sign) + { + str = str[1 .. $]; + if (_expect(str.length == 0, false)) + return false; + } + else + static if (allowStartingPlus) + { + if (_expect(str[0] == '+', false)) + { + str = str[1 .. $]; + if (_expect(str.length == 0, false)) + return false; + } + } + + W multiplier = 10; + W d = str[0] - C('0'); + str = str[1 .. $]; + + if (_expect(d >= 10, false)) + { + static if (allowDotOnBounds) + { + if (d == '.' - '0') + { + if (str.length == 0) + return false; + key = DecimalExponentKey.dot; + d = str[0] - C('0'); + str = str[1 .. $]; + if (_expect(d < 10, true)) + goto FI; + return false; + } + } + static if (allowSpecialValues) + goto NI; + else + return false; + } + + static if (!allowLeadingZeros) + { + if (_expect(d == 0, false)) + { + if (str.length == 0) + return true; + d = str[0] - C('0'); + str = str[1 .. $]; + if (d < 10) + return false; + goto DOT; + } + } + + goto F0; + + S: + if (str.length == 0) + return true; + d = str[0] - C('0'); + str = str[1 .. $]; + + if (d < 10) + { + multiplier = 10; + static if (is(C == char) && is(W == ulong)) + if (!__ctfe) + if (str.length >= 8 && isMadeOfEightDigits(str[0 .. 8])) + { + multiplier = 1000000000UL; + d *= 100000000; + d += parseEightDigits(str[0 .. 8]); + str = str[8 .. $]; + if (str.length >= 8 && isMadeOfEightDigits(str[0 .. 8])) + { + multiplier = 1000000000UL * 100000000; + d *= 100000000; + d += parseEightDigits(str[0 .. 8]); + str = str[8 .. $]; + } + } + + F0: + if (_expect(mullAdd(multiplier, d), false)) + return false; + goto S; + } + + static if (allowUnderscores) + { + if (d == '_' - '0') + { + if (str.length == 0) + return false; + d = str[0] - C('0'); + str = str[1 .. $]; + if (_expect(d < 10, true)) + goto F0; + return false; + } + } + DOT: + if (d == DecimalExponentKey.dot) + { + // The dot modifier CANNOT be preceded by any modifiers. + if (key != DecimalExponentKey.none) + return false; + + key = DecimalExponentKey.dot; + + if (str.length == 0) + { + return allowDotOnBounds; + } + + IF: + multiplier = 10; + static if (is(C == char) && is(W == ulong)) + if (!__ctfe) + { + if (str.length >= 8 && isMadeOfEightDigits(str[0 .. 8])) + { + multiplier = 100000000; + d = parseEightDigits(str[0 .. 8]); + str = str[8 .. $]; + exponent -= 8; + if (str.length >= 7) + { + if (isMadeOfEightDigits((str.ptr - 1)[0 .. 8])) + { + multiplier = 100000000UL * 10000000; + d -= str.ptr[-1] - '0'; + d *= 10000000; + d += parseEightDigits((str.ptr - 1)[0 .. 8]); + str = str[7 .. $]; + exponent -= 7; + if (str.length) + { + auto md = str[0] - C('0'); + if (md < 10) + { + d *= 10; + multiplier = 100000000UL * 100000000; + d += md; + str = str[1 .. $]; + exponent -= 1; + } + } + } + else + { + TrySix: + if (isMadeOfEightDigits((str.ptr - 2)[0 .. 8])) + { + multiplier = 100000000UL * 1000000; + d -= str.ptr[-1] - '0'; + d -= (str.ptr[-2] - '0') * 10; + d *= 1000000; + d += parseEightDigits((str.ptr - 2)[0 .. 8]); + str = str[6 .. $]; + exponent -= 6; + } + } + + } + else + if (str.length == 6) + goto TrySix; + goto FIL; + } + } + + d = str[0] - C('0'); + str = str[1 .. $]; + if (_expect(d >= 10, false)) + goto DOB; + FI: + exponent--; + multiplier = 10; + FIL: + if (_expect(mullAdd(multiplier, d), false)) + return false; + if (str.length == 0) + return true; + d = str[0] - C('0'); + str = str[1 .. $]; + if (d < 10) + goto FI; + + static if (allowUnderscores) + { + if (d == '_' - '0') + { + if (str.length == 0) + return false; + d = str[0] - C('0'); + str = str[1 .. $]; + if (_expect(d < 10, true)) + goto FI; + return false; + } + } + DOB: + static if (!allowDotOnBounds) + { + if (exponent == 0) + return false; + } + } + + static if (allowExponent) + { + if (d == DecimalExponentKey.e + || d == DecimalExponentKey.E + || d == DecimalExponentKey.d && allowDExponent + || d == DecimalExponentKey.D && allowDExponent + ) + { + import mir.parse: parse; + key = cast(DecimalExponentKey)d; + long exponentPlus; + if (parse(str, exponentPlus) && str.length == 0) + { + import mir.checkedint: adds; + bool overflow; + exponent = exponent.adds(exponentPlus, overflow); + return !overflow; + } + } + } + return false; + + static if (allowSpecialValues) + { + NI: + if (str.length == 2) + { + auto stail = cast(C[2])str[0 .. 2]; + if (d == 'i' - '0' && stail == cast(C[2])"nf" || d == 'I' - '0' && (stail == cast(C[2])"nf" || stail == cast(C[2])"NF")) + { + key = DecimalExponentKey.infinity; + return true; + } + if (d == 'n' - '0' && stail == cast(C[2])"an" || d == 'N' - '0' && (stail == cast(C[2])"aN" || stail == cast(C[2])"AN")) + { + key = DecimalExponentKey.nan; + return true; + } + } + return false; + } + } +} diff --git a/source/mir/bignum/internal/phobos_kernel.d b/source/mir/bignum/internal/phobos_kernel.d new file mode 100644 index 00000000..4a77b30e --- /dev/null +++ b/source/mir/bignum/internal/phobos_kernel.d @@ -0,0 +1,1705 @@ +/** Fundamental operations for arbitrary-precision arithmetic + * + * These functions are for internal use only. + */ + + // Original Phobos code: +/* Copyright Don Clugston 2008 - 2010. + * 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) + */ + + // All other changes: +/* Copyright Ilia Ki 2022 + * Distributed under Apache-2.0 License + */ + +/* References: + "Modern Computer Arithmetic" (MCA) is the primary reference for all + algorithms used in this library. + - R.P. Brent and P. Zimmermann, "Modern Computer Arithmetic", + Version 0.5.9, (Oct 2010). + - C. Burkinel and J. Ziegler, "Fast Recursive Division", MPI-I-98-1-022, + Max-Planck Institute fuer Informatik, (Oct 1998). + - G. Hanrot, M. Quercia, and P. Zimmermann, "The Middle Product Algorithm, I.", + INRIA 4664, (Dec 2002). + - M. Bodrato and A. Zanoni, "What about Toom-Cook Matrices Optimality?", + http://bodrato.it/papers (2006). + - A. Fog, "Optimizing subroutines in assembly language", + www.agner.org/optimize (2008). + - A. Fog, "The microarchitecture of Intel and AMD CPU's", + www.agner.org/optimize (2008). + - A. Fog, "Instruction tables: Lists of instruction latencies, throughputs + and micro-operation breakdowns for Intel and AMD CPU's.", www.agner.org/optimize (2008). +Idioms: + Many functions in this module use + 'func(Tulong)(Tulong x) if (is(Tulong == ulong))' rather than 'func(ulong x)' + in order to disable implicit conversion. +*/ +module mir.bignum.internal.phobos_kernel; + +version (D_InlineAsm_X86) + static import std.internal.math.biguintx86; + +version (DMD) +{ + version (D_InlineAsm_X86) + version = HaveAsmVersion; +} + +version (LDC) +{ + version (D_InlineAsm_X86) + version = HaveAsmVersion; + + version (ARM) + { + version (ARM_Thumb) {} + else + { + static import std.internal.math.biguintarm; + version = LDC_ARM_asm; + version = HaveAsmVersion; + } + } +} +static import std.internal.math.biguintnoasm; +alias multibyteAdd = multibyteAddSub!('+'); +alias multibyteSub = multibyteAddSub!('-'); + +// import std.internal.math.biguintnoasm : BigDigit, KARATSUBALIMIT, +// KARATSUBASQUARELIMIT; + +import std.internal.math.biguintnoasm : BigDigit; +enum FASTDIVLIMIT = 16; // crossover to recursive division +enum CACHELIMIT = 256; +enum KARATSUBALIMIT = 32; // Minimum value for which Karatsuba is worthwhile. +enum KARATSUBASQUARELIMIT = 32; // Minimum value for which square Karatsuba is worthwhile + +import std.ascii: LetterCase; + +pure nothrow @nogc: +package(mir.bignum): + +// dipatchers to the right low-level primitives. Added to allow BigInt CTFE for +// 32 bit systems (https://issues.dlang.org/show_bug.cgi?id=14767) although it's +// used by the other architectures too. +// See comments below in case it has to be refactored. +version (HaveAsmVersion) +uint multibyteAddSub(char op)(uint[] dest, const(uint)[] src1, const (uint)[] src2, uint carry) +{ + // must be checked before, otherwise D_InlineAsm_X86 is true. + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteAddSub!op(dest, src1, src2, carry); + // Runtime. + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteAddSub!op(dest, src1, src2, carry); + else version (LDC_ARM_asm) + return std.internal.math.biguintarm.multibyteAddSub!op(dest, src1, src2, carry); + // Runtime if no asm available. + else + return std.internal.math.biguintnoasm.multibyteAddSub!op(dest, src1, src2, carry); +} +// Any other architecture +else alias multibyteAddSub = std.internal.math.biguintnoasm.multibyteAddSub; + +version (HaveAsmVersion) +uint multibyteIncrementAssign(char op)(uint[] dest, uint carry) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteIncrementAssign!op(dest, carry); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteIncrementAssign!op(dest, carry); + else version (LDC_ARM_asm) + return std.internal.math.biguintarm.multibyteIncrementAssign!op(dest, carry); + else + return std.internal.math.biguintnoasm.multibyteIncrementAssign!op(dest, carry); +} +else alias multibyteIncrementAssign = std.internal.math.biguintnoasm.multibyteIncrementAssign; + +version (HaveAsmVersion) +uint multibyteShl()(uint[] dest, const(uint)[] src, uint numbits) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteShl(dest, src, numbits); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteShl(dest, src, numbits); + else version (LDC_ARM_asm) + return std.internal.math.biguintarm.multibyteShl(dest, src, numbits); + else + return std.internal.math.biguintnoasm.multibyteShl(dest, src, numbits); +} +else alias multibyteShl = std.internal.math.biguintnoasm.multibyteShl; + +version (HaveAsmVersion) +void multibyteShr()(uint[] dest, const(uint)[] src, uint numbits) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteShr(dest, src, numbits); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteShr(dest, src, numbits); + else version (LDC_ARM_asm) + std.internal.math.biguintarm.multibyteShr(dest, src, numbits); + else + std.internal.math.biguintnoasm.multibyteShr(dest, src, numbits); +} +else alias multibyteShr = std.internal.math.biguintnoasm.multibyteShr; + +version (HaveAsmVersion) +uint multibyteMul()(uint[] dest, const(uint)[] src, uint multiplier, uint carry) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteMul(dest, src, multiplier, carry); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteMul(dest, src, multiplier, carry); + else version (LDC_ARM_asm) + return std.internal.math.biguintarm.multibyteMul(dest, src, multiplier, carry); + else + return std.internal.math.biguintnoasm.multibyteMul(dest, src, multiplier, carry); +} +else alias multibyteMul = std.internal.math.biguintnoasm.multibyteMul; + +version (HaveAsmVersion) +uint multibyteMulAdd(char op)(uint[] dest, const(uint)[] src, uint multiplier, uint carry) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteMulAdd!op(dest, src, multiplier, carry); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteMulAdd!op(dest, src, multiplier, carry); + else version (LDC_ARM_asm) + return std.internal.math.biguintarm.multibyteMulAdd!op(dest, src, multiplier, carry); + else + return std.internal.math.biguintnoasm.multibyteMulAdd!op(dest, src, multiplier, carry); +} +else alias multibyteMulAdd = std.internal.math.biguintnoasm.multibyteMulAdd; + +version (HaveAsmVersion) +void multibyteMultiplyAccumulate()(uint[] dest, const(uint)[] left, const(uint)[] right) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteMultiplyAccumulate(dest, left, right); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteMultiplyAccumulate(dest, left, right); + else version (LDC_ARM_asm) + std.internal.math.biguintarm.multibyteMultiplyAccumulate(dest, left, right); + else + std.internal.math.biguintnoasm.multibyteMultiplyAccumulate(dest, left, right); +} +else alias multibyteMultiplyAccumulate = std.internal.math.biguintnoasm.multibyteMultiplyAccumulate; + +version (HaveAsmVersion) +uint multibyteDivAssign()(uint[] dest, uint divisor, uint overflow) +{ + if (__ctfe) + return std.internal.math.biguintnoasm.multibyteDivAssign(dest, divisor, overflow); + else version (D_InlineAsm_X86) + return std.internal.math.biguintx86.multibyteDivAssign(dest, divisor, overflow); + else version (LDC_ARM_asm) + return std.internal.math.biguintarm.multibyteDivAssign(dest, divisor, overflow); + else + return std.internal.math.biguintnoasm.multibyteDivAssign(dest, divisor, overflow); +} +else alias multibyteDivAssign = std.internal.math.biguintnoasm.multibyteDivAssign; + +version (HaveAsmVersion) +void multibyteAddDiagonalSquares()(uint[] dest, const(uint)[] src) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteAddDiagonalSquares(dest, src); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteAddDiagonalSquares(dest, src); + else version (LDC_ARM_asm) + std.internal.math.biguintarm.multibyteAddDiagonalSquares(dest, src); + else + std.internal.math.biguintnoasm.multibyteAddDiagonalSquares(dest, src); +} +else alias multibyteAddDiagonalSquares = std.internal.math.biguintnoasm.multibyteAddDiagonalSquares; + +version (HaveAsmVersion) +void multibyteTriangleAccumulate()(uint[] dest, const(uint)[] x) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteTriangleAccumulate(dest, x); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteTriangleAccumulate(dest, x); + else version (LDC_ARM_asm) + std.internal.math.biguintarm.multibyteTriangleAccumulate(dest, x); + else + std.internal.math.biguintnoasm.multibyteTriangleAccumulate(dest, x); +} +else alias multibyteTriangleAccumulate = std.internal.math.biguintnoasm.multibyteTriangleAccumulate; + +version (HaveAsmVersion) +void multibyteSquare()(BigDigit[] result, const(BigDigit)[] x) +{ + if (__ctfe) + std.internal.math.biguintnoasm.multibyteSquare(result, x); + else version (D_InlineAsm_X86) + std.internal.math.biguintx86.multibyteSquare(result, x); + else version (LDC_ARM_asm) + std.internal.math.biguintarm.multibyteSquare(result, x); + else + std.internal.math.biguintnoasm.multibyteSquare(result, x); +} +else alias multibyteSquare = std.internal.math.biguintnoasm.multibyteSquare; + + +// These constants are used by shift operations +static if (BigDigit.sizeof == int.sizeof) +{ + enum { LG2BIGDIGITBITS = 5, BIGDIGITSHIFTMASK = 31 } + alias BIGHALFDIGIT = ushort; +} +else static if (BigDigit.sizeof == long.sizeof) +{ + alias BIGHALFDIGIT = uint; + enum { LG2BIGDIGITBITS = 6, BIGDIGITSHIFTMASK = 63 } +} +else static assert(0, "Unsupported BigDigit size"); + +import std.traits : isIntegral; +enum BigDigitBits = BigDigit.sizeof*8; +template maxBigDigits(T) +if (isIntegral!T) +{ + enum maxBigDigits = (T.sizeof+BigDigit.sizeof-1)/BigDigit.sizeof; +} + +static immutable BigDigit[] ZERO = [0]; +static immutable BigDigit[] ONE = [1]; +static immutable BigDigit[] TWO = [2]; +static immutable BigDigit[] TEN = [10]; + +void twosComplement(const(BigDigit) [] x, BigDigit[] result) +pure nothrow @safe +{ + foreach (i; 0 .. x.length) + { + result[i] = ~x[i]; + } + result[x.length..$] = BigDigit.max; + + foreach (i; 0 .. result.length) + { + if (result[i] == BigDigit.max) + { + result[i] = 0; + } + else + { + result[i] += 1; + break; + } + } +} + +// works for any type +T intpow(T)(T x, ulong n) pure nothrow @safe +{ + T p; + + switch (n) + { + case 0: + p = 1; + break; + + case 1: + p = x; + break; + + case 2: + p = x * x; + break; + + default: + p = 1; + while (1) + { + if (n & 1) + p *= x; + n >>= 1; + if (!n) + break; + x *= x; + } + break; + } + return p; +} + + +// returns the maximum power of x that will fit in a uint. +int highestPowerBelowUintMax(uint x) pure nothrow @safe +{ + assert(x > 1, "x must be greater than 1"); + static immutable ubyte [22] maxpwr = [ 31, 20, 15, 13, 12, 11, 10, 10, 9, 9, + 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7]; + if (x<24) return maxpwr[x-2]; + if (x<41) return 6; + if (x<85) return 5; + if (x<256) return 4; + if (x<1626) return 3; + if (x<65_536) return 2; + return 1; +} + +// returns the maximum power of x that will fit in a ulong. +int highestPowerBelowUlongMax(uint x) pure nothrow @safe +{ + assert(x > 1, "x must be greater than 1"); + static immutable ubyte [39] maxpwr = [ 63, 40, 31, 27, 24, 22, 21, 20, 19, 18, + 17, 17, 16, 16, 15, 15, 15, 15, 14, 14, + 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12]; + if (x<41) return maxpwr[x-2]; + if (x<57) return 11; + if (x<85) return 10; + if (x<139) return 9; + if (x<256) return 8; + if (x<566) return 7; + if (x<1626) return 6; + if (x<7132) return 5; + if (x<65_536) return 4; + if (x<2_642_246) return 3; + return 2; +} + +version (StdUnittest) +{ + +private int slowHighestPowerBelowUintMax(uint x) pure nothrow @safe +{ + int pwr = 1; + for (ulong q = x;x*q < cast(ulong) uint.max; ) + { + q*=x; ++pwr; + } + return pwr; +} + +version(mir_bignum_test) +@safe pure unittest +{ + assert(highestPowerBelowUintMax(10)==9); + for (int k=82; k<88; ++k) + { + assert(highestPowerBelowUintMax(k)== slowHighestPowerBelowUintMax(k)); + } +} + +} + +/** General unsigned multiply routine for bigints. + * Sets result = x * y. + * + * The length of y must not be larger than the length of x. + * Different algorithms are used, depending on the lengths of x and y. + * TODO: "Modern Computer Arithmetic" suggests the OddEvenKaratsuba algorithm for the + * unbalanced case. (But I doubt it would be faster in practice). + * + */ +void mulInternal(BigDigit[] result, const(BigDigit)[] x, const(BigDigit)[] y, BigDigit[] scratchbuff) + pure nothrow @safe +{ + assert( result.length == x.length + y.length, + "result array must have enough space to store computed result"); + assert( y.length > 0, "y must not be empty"); + assert( x.length >= y.length, "x must be greater or equal than y"); + if (y.length < KARATSUBALIMIT) + { + // Small multiplier, we'll just use the asm classic multiply. + if (y.length == 1) + { // Trivial case, no cache effects to worry about + result[x.length] = multibyteMul(result[0 .. x.length], x, y[0], 0); + return; + } + + static assert (CACHELIMIT >= KARATSUBALIMIT * 2); + + auto chunksize = CACHELIMIT - y.length * 2; + + if (chunksize > x.length) + chunksize = x.length; + + mulSimple(result[0 .. chunksize + y.length], x[0 .. chunksize], y); + + auto done = chunksize; + + while (done < x.length) + { + if (done + chunksize > x.length) + chunksize = x.length - done; + BigDigit[KARATSUBALIMIT] partial = void; + partial[0 .. y.length] = result[done .. done+y.length]; + mulSimple(result[done .. done+chunksize+y.length], x[done .. done+chunksize], y); + addAssignSimple(result[done .. done+chunksize + y.length], partial[0 .. y.length]); + done += chunksize; + } + return; + } + + immutable half = (x.length >> 1) + (x.length & 1); + if (2*y.length*y.length <= x.length*x.length) + { + // UNBALANCED MULTIPLY + // Use school multiply to cut into quasi-squares of Karatsuba-size + // or larger. The ratio of the two sides of the 'square' must be + // between 1.414:1 and 1:1. Use Karatsuba on each chunk. + // + // For maximum performance, we want the ratio to be as close to + // 1:1 as possible. To achieve this, we can either pad x or y. + // The best choice depends on the modulus x%y. + auto numchunks = x.length / y.length; + auto chunksize = y.length; + auto extra = x.length % y.length; + auto maxchunk = chunksize + extra; + bool paddingY; // true = we're padding Y, false = we're padding X. + bool isExtraSmall = extra * extra * 2 < y.length * y.length; + if (numchunks == 1 && isExtraSmall) + { + // We divide (x_first_half * y) and (x_last_half * y) + // between 1.414:1 and 1.707:1 (1.707 = 1+1/sqrt(2)). + // (1.414 ~ 1.707)/2:1 is balanced. + BigDigit [] partial = scratchbuff[$ - y.length .. $]; + scratchbuff = scratchbuff[0 .. $ - y.length]; + mulKaratsuba(result[0 .. half + y.length], y, x[0 .. half], scratchbuff); + partial[] = result[half .. half + y.length]; + mulKaratsuba(result[half .. $], y, x[half .. $], scratchbuff); + BigDigit c = addAssignSimple(result[half .. half + y.length], partial); + if (c) multibyteIncrementAssign!('+')(result[half + y.length..$], c); + } + else + { + if (isExtraSmall) + { + // The leftover bit is small enough that it should be incorporated + // in the existing chunks. + // Make all the chunks a tiny bit bigger + // (We're padding y with zeros) + chunksize += extra / numchunks; + extra = x.length - chunksize*numchunks; + // there will probably be a few left over. + // Every chunk will either have size chunksize, or chunksize+1. + maxchunk = chunksize + 1; + paddingY = true; + assert(chunksize + extra + chunksize *(numchunks-1) == x.length, + "Unexpected size"); + } + else + { + // the extra bit is large enough that it's worth making a new chunk. + // (This means we're padding x with zeros, when doing the first one). + maxchunk = chunksize; + ++numchunks; + paddingY = false; + assert(extra + chunksize *(numchunks-1) == x.length, + "Unexpected size"); + } + // We make the buffer a bit bigger so we have space for the partial sums. + BigDigit [] partial = scratchbuff[$ - y.length .. $]; + scratchbuff = scratchbuff[0 .. $ - y.length]; + size_t done; // how much of X have we done so far? + if (paddingY) + { + // If the first chunk is bigger, do it first. We're padding y. + mulKaratsuba(result[0 .. y.length + chunksize + (extra > 0 ? 1 : 0 )], + x[0 .. chunksize + (extra>0?1:0)], y, scratchbuff); + done = chunksize + (extra > 0 ? 1 : 0); + if (extra) --extra; + } + else + { // We're padding X. Begin with the extra bit. + mulKaratsuba(result[0 .. y.length + extra], y, x[0 .. extra], scratchbuff); + done = extra; + extra = 0; + } + immutable basechunksize = chunksize; + while (done < x.length) + { + chunksize = basechunksize + (extra > 0 ? 1 : 0); + if (extra) --extra; + partial[] = result[done .. done+y.length]; + mulKaratsuba(result[done .. done + y.length + chunksize], + x[done .. done+chunksize], y, scratchbuff); + addAssignSimple(result[done .. done + y.length + chunksize], partial); + done += chunksize; + } + } + } + else + { + // Balanced. Use Karatsuba directly. + mulKaratsuba(result, x, y, scratchbuff); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=20493 +version(mir_bignum_test) +@safe unittest +{ + // the bug report has a testcase with very large numbers (~10^3800 and ~10^2300) + // the number itself isn't important, only the amount of digits, so we do a simpler + // multiplication of the same size, analogous to: + // 11111111 * 11111111 = 0123456787654321 + // but instead of base 10, it's in base `BigDigit` + + BigDigit[398] x = 1; + BigDigit[236] y = 1; + BigDigit[x.length + y.length] result; + BigDigit[x.length.karatsubaRequiredBuffSize] buff; + mulInternal(result[], x[], y[], buff); + + // create an array of the form [1, 2, ..., y.length, ..., y.length, y.length-1, ..., 1, 0] + BigDigit[x.length + y.length] expected = y.length; + foreach (BigDigit i; 0 .. y.length) + { + expected[i] = i+1; + expected[$-1-i] = i; + } + + assert(result == expected); +} + +/** General unsigned squaring routine for BigInts. + * Sets result = x*x. + * NOTE: If the highest half-digit of x is zero, the highest digit of result will + * also be zero. + */ +void squareInternal(BigDigit[] result, const BigDigit[] x, BigDigit [] scratchbuff) pure nothrow @safe + in (scratchbuff.length >= x.length.karatsubaRequiredBuffSize) +{ + // Squaring is potentially half a multiply, plus add the squares of + // the diagonal elements. + assert(result.length == 2*x.length, + "result needs to have twice the capacity of x"); + if (x.length <= KARATSUBASQUARELIMIT) + { + if (x.length == 1) + { + result[1] = multibyteMul(result[0 .. 1], x, x[0], 0); + return; + } + return squareSimple(result, x); + } + // The nice thing about squaring is that it always stays balanced + squareKaratsuba(result, x, scratchbuff); +} + + +/// if remainder is null, only calculate quotient. +void divModInternal(BigDigit [] quotient, BigDigit[] remainder, const BigDigit [] u, + const BigDigit [] v, BigDigit[] buffer) pure nothrow @safe +{ + import core.bitop : bsr; + + assert(quotient.length == u.length - v.length + 1, + "Invalid quotient length"); + assert(remainder == null || remainder.length == v.length, + "Invalid remainder"); + assert(v.length > 1, "v must have more than 1 element"); + assert(u.length >= v.length, "u must be as longer or longer than v"); + + // Normalize by shifting v left just enough so that + // its high-order bit is on, and shift u left the + // same amount. The highest bit of u will never be set. + + auto vn = buffer[0 .. v.length]; + buffer = buffer[v.length .. $]; + + auto un = buffer[0 .. u.length + 1]; + buffer = buffer[u.length + 1 .. $]; + + // How much to left shift v, so that its MSB is set. + uint s = BIGDIGITSHIFTMASK - bsr(v[$-1]); + if (s != 0) + { + multibyteShl(vn, v, s); + un[$-1] = multibyteShl(un[0..$-1], u, s); + } + else + { + vn[] = v[]; + un[0..$-1] = u[]; + un[$-1] = 0; + } + if (quotient.length < FASTDIVLIMIT) + { + schoolbookDivMod(quotient, un, vn); + } + else + { + blockDivMod(quotient, un, vn, buffer); + } + + // Unnormalize remainder, if required. + if (remainder != null) + { + if (s == 0) remainder[] = un[0 .. vn.length]; + else multibyteShr(remainder, un[0 .. vn.length+1], s); + } +} + +version(mir_bignum_test) +pure @safe unittest +{ //7fff_00007fff_ffffffff_00020000 + uint[3] u = [0, 0xFFFF_FFFE, 0x8000_0000]; + uint[2] v = [0xFFFF_FFFF, 0x8000_0000]; + uint[10] buffer = void; + uint[u.length - v.length + 1] q; + uint[2] r; + divModInternal(q, r, u, v, buffer); + assert(q[]==[0xFFFF_FFFFu, 0]); + assert(r[]==[0xFFFF_FFFFu, 0x7FFF_FFFF]); + u = [0, 0xFFFF_FFFE, 0x8000_0001]; + v = [0xFFFF_FFFF, 0x8000_0000]; + divModInternal(q, r, u, v, buffer); +} + + +// Converts a big uint to a hexadecimal string. +// +// Optionally, a separator character (eg, an underscore) may be added between +// every 8 digits. +// buff.length must be data.length*8 if separator is zero, +// or data.length*9 if separator is non-zero. It will be completely filled. +char [] biguintToHex(return scope char [] buff, const scope BigDigit [] data, char separator=0, + LetterCase letterCase = LetterCase.upper) pure nothrow @safe +{ + int x=0; + for (ptrdiff_t i=data.length - 1; i >= 0; --i) + { + toHexZeroPadded(buff[x .. x+8], data[i], letterCase); + x+=8; + if (separator) + { + if (i>0) buff[x] = separator; + ++x; + } + } + return buff; +} + +/** + * Convert a big uint into an octal string. + * + * Params: + * buff = The destination buffer for the octal string. Must be large enough to + * store the result, including leading zeroes, which is + * ceil(data.length * BigDigitBits / 3) characters. + * The buffer is filled from back to front, starting from `buff[$-1]`. + * data = The biguint to be converted. + * + * Returns: The index of the leading non-zero digit in `buff`. Will be + * `buff.length - 1` if the entire big uint is zero. + */ +size_t biguintToOctal(char[] buff, const(BigDigit)[] data) + pure nothrow @safe @nogc +{ + ubyte carry = 0; + int shift = 0; + size_t penPos = buff.length - 1; + size_t lastNonZero = buff.length - 1; + + pragma(inline) void output(uint digit) @nogc nothrow + { + if (digit != 0) + lastNonZero = penPos; + buff[penPos--] = cast(char)('0' + digit); + } + + foreach (bigdigit; data) + { + if (shift < 0) + { + // Some bits were carried over from previous word. + assert(shift > -3, "shift must be greater than -3"); + output(((bigdigit << -shift) | carry) & 0b111); + shift += 3; + assert(shift > 0, "shift must be 1 or greater"); + } + + while (shift <= BigDigitBits - 3) + { + output((bigdigit >>> shift) & 0b111); + shift += 3; + } + + if (shift < BigDigitBits) + { + // Some bits need to be carried forward. + carry = (bigdigit >>> shift) & 0b11; + } + shift -= BigDigitBits; + assert(shift >= -2 && shift <= 0, "shift must in [-2,0]"); + } + + if (shift < 0) + { + // Last word had bits that haven't been output yet. + assert(shift > -3, "Shift must be greater than -3"); + output(carry); + } + + return lastNonZero; +} + +/** Convert a big uint into a decimal string. + * + * Params: + * buff = The destination buffer for the decimal string. Must be + * large enough to store the result, including leading zeros. + * Will be filled backwards, starting from buff[$-1]. + * data = The biguint to be converted. Will be destroyed. + * + * buff.length must be >= (data.length*32)/log2(10) = 9.63296 * data.length. + * Returns: + * the lowest index of buff which was used. + */ +size_t biguintToDecimal(char [] buff, BigDigit [] data) pure nothrow @safe +{ + ptrdiff_t sofar = buff.length; + // Might be better to divide by (10^38/2^32) since that gives 38 digits for + // the price of 3 divisions and a shr; this version only gives 27 digits + // for 3 divisions. + while (data.length>1) + { + uint rem = multibyteDivAssign(data, 10_0000_0000, 0); + itoaZeroPadded(buff[sofar-9 .. sofar], rem); + sofar -= 9; + if (data[$-1] == 0 && data.length > 1) + { + data = data[0 .. $ - 1]; + } + } + itoaZeroPadded(buff[sofar-10 .. sofar], data[0]); + sofar -= 10; + // and strip off the leading zeros + while (sofar != buff.length-1 && buff[sofar] == '0') + sofar++; + return sofar; +} + +/** Convert a decimal string into a big uint. + * + * Params: + * data = The biguint to be receive the result. Must be large enough to + * store the result. + * s = The decimal string. May contain _ or 0 .. 9 + * + * The required length for the destination buffer is slightly less than + * 1 + s.length/log2(10) = 1 + s.length/3.3219. + * + * Returns: + * the highest index of data which was used. 0 if case of failure. + */ +int biguintFromDecimal(Range)(BigDigit[] data, Range s) +if ( + isInputRange!Range && + isSomeChar!(ElementType!Range) && + !isInfinite!Range) +in +{ + static if (hasLength!Range) + assert((data.length >= 2) || (data.length == 1 && s.length == 1), + "data has a invalid length"); +} +do +{ + // Convert to base 1e19 = 10_000_000_000_000_000_000. + // (this is the largest power of 10 that will fit into a long). + // The length will be less than 1 + s.length/log2(10) = 1 + s.length/3.3219. + // 485 bits will only just fit into 146 decimal digits. + // As we convert the string, we record the number of digits we've seen in base 19: + // hi is the number of digits/19, lo is the extra digits (0 to 18). + // TODO: This is inefficient for very large strings (it is O(n^^2)). + // We should take advantage of fast multiplication once the numbers exceed + // Karatsuba size. + uint lo = 0; // number of powers of digits, 0 .. 18 + uint x = 0; + ulong y = 0; + uint hi = 0; // number of base 1e19 digits + data[0] = 0; // initially number is 0. + if (data.length > 1) + data[1] = 0; + + foreach (character; s) + { + if (character == '_') + continue; + + if (character < '0' || character > '9') + return 0; + x *= 10; + x += character - '0'; + ++lo; + if (lo == 9) + { + y = x; + x = 0; + } + if (lo == 18) + { + y *= 10_0000_0000; + y += x; + x = 0; + } + if (lo == 19) + { + y *= 10; + y += x; + x = 0; + // Multiply existing number by 10^19, then add y1. + if (hi>0) + { + data[hi] = multibyteMul(data[0 .. hi], data[0 .. hi], 1_220_703_125*2u, 0); // 5^13*2 = 0x9184_E72A + ++hi; + data[hi] = multibyteMul(data[0 .. hi], data[0 .. hi], 15_625*262_144u, 0); // 5^6*2^18 = 0xF424_0000 + ++hi; + } + else + hi = 2; + uint c = multibyteIncrementAssign!('+')(data[0 .. hi], cast(uint)(y&0xFFFF_FFFF)); + c += multibyteIncrementAssign!('+')(data[1 .. hi], cast(uint)(y >> 32)); + if (c != 0) + { + data[hi]=c; + ++hi; + } + y = 0; + lo = 0; + } + } + // Now set y = all remaining digits. + if (lo >= 18) + { + } + else if (lo >= 9) + { + for (int k=9; k>> 32); + hi=2; + } + } + else + { + while (lo>0) + { + immutable c = multibyteMul(data[0 .. hi], data[0 .. hi], 10, 0); + if (c != 0) + { + data[hi]=c; + ++hi; + } + --lo; + } + uint c = multibyteIncrementAssign!('+')(data[0 .. hi], cast(uint)(y&0xFFFF_FFFF)); + if (y > 0xFFFF_FFFFL) + { + c += multibyteIncrementAssign!('+')(data[1 .. hi], cast(uint)(y >> 32)); + } + if (c != 0) + { + data[hi]=c; + ++hi; + } + } + } + while (hi>1 && data[hi-1]==0) + --hi; + return hi; +} + + +// ------------------------ +// These in-place functions are only for internal use; they are incompatible +// with COW. + +// Classic 'schoolbook' multiplication. +void mulSimple(BigDigit[] result, const(BigDigit) [] left, + const(BigDigit)[] right) pure nothrow @safe +in +{ + assert(result.length == left.length + right.length, + "Result must be able to store left + right"); + assert(right.length>1, "right must not be empty"); +} +do +{ + result[left.length] = multibyteMul(result[0 .. left.length], left, right[0], 0); + multibyteMultiplyAccumulate(result[1..$], left, right[1..$]); +} + +// Classic 'schoolbook' squaring +void squareSimple(BigDigit[] result, const(BigDigit) [] x) pure nothrow @safe +in +{ + assert(result.length == 2*x.length, "result must be twice as long as x"); + assert(x.length>1, "x must not be empty"); +} +do +{ + multibyteSquare(result, x); +} + + +// add two uints of possibly different lengths. Result must be as long +// as the larger length. +// Returns carry (0 or 1). +uint addSimple(BigDigit[] result, const BigDigit [] left, const BigDigit [] right) +pure nothrow @safe +in +{ + assert(result.length == left.length, + "result and left must be of the same length"); + assert(left.length >= right.length, + "left must be longer or of equal length to right"); + assert(right.length > 0, "right must not be empty"); +} +do +{ + uint carry = multibyteAdd(result[0 .. right.length], + left[0 .. right.length], right, 0); + if (right.length < left.length) + { + result[right.length .. left.length] = left[right.length .. $]; + carry = multibyteIncrementAssign!('+')(result[right.length..$], carry); + } + return carry; +} + +// result = left - right +// returns carry (0 or 1) +BigDigit subSimple(BigDigit [] result,const(BigDigit) [] left, + const(BigDigit) [] right) pure nothrow +in +{ + assert(result.length == left.length, + "result and left must be of the same length"); + assert(left.length >= right.length, + "left must be longer or of equal length to right"); + assert(right.length > 0, "right must not be empty"); +} +do +{ + BigDigit carry = multibyteSub(result[0 .. right.length], + left[0 .. right.length], right, 0); + if (right.length < left.length) + { + result[right.length .. left.length] = left[right.length .. $]; + carry = multibyteIncrementAssign!('-')(result[right.length..$], carry); + } //else if (result.length == left.length+1) { result[$-1] = carry; carry=0; } + return carry; +} + + +/* result = result - right + * Returns carry = 1 if result was less than right. +*/ +BigDigit subAssignSimple(BigDigit [] result, const(BigDigit) [] right) +pure nothrow @safe +{ + assert(result.length >= right.length, + "result must be longer or of equal length to right"); + uint c = multibyteSub(result[0 .. right.length], result[0 .. right.length], right, 0); + if (c && result.length > right.length) + c = multibyteIncrementAssign!('-')(result[right.length .. $], c); + return c; +} + +/* result = result + right +*/ +BigDigit addAssignSimple(BigDigit [] result, const(BigDigit) [] right) +pure nothrow @safe +{ + assert(result.length >= right.length, + "result must be longer or of equal length to right"); + uint c = multibyteAdd(result[0 .. right.length], result[0 .. right.length], right, 0); + if (c && result.length > right.length) + c = multibyteIncrementAssign!('+')(result[right.length .. $], c); + return c; +} + +/* performs result += wantSub? - right : right; +*/ +BigDigit addOrSubAssignSimple(BigDigit [] result, const(BigDigit) [] right, + bool wantSub) pure nothrow @safe +{ + if (wantSub) + return subAssignSimple(result, right); + else + return addAssignSimple(result, right); +} + + +// return true if x= y.length, + "x must be longer or of equal length to y"); + auto k = x.length-1; + while (x[k]==0 && k >= y.length) + --k; + if (k >= y.length) + return false; + while (k>0 && x[k]==y[k]) + --k; + return x[k] < y[k]; +} + +// Set result = abs(x-y), return true if result is negative(x= y.length) ? x.length : y.length), + "result must capable to store the maximum of x and y"); + + size_t minlen; + bool negative; + if (x.length >= y.length) + { + minlen = y.length; + negative = less(x, y); + } + else + { + minlen = x.length; + negative = !less(y, x); + } + const (BigDigit)[] large, small; + if (negative) + { + large = y; small = x; + } + else + { + large = x; small = y; + } + + BigDigit carry = multibyteSub(result[0 .. minlen], large[0 .. minlen], small[0 .. minlen], 0); + if (x.length != y.length) + { + result[minlen .. large.length]= large[minlen..$]; + result[large.length..$] = 0; + if (carry) + multibyteIncrementAssign!('-')(result[minlen..$], carry); + } + return negative; +} + +/* Determine how much space is required for the temporaries + * when performing a Karatsuba multiplication. + * TODO: determining a tight bound is non-trivial and depends on KARATSUBALIMIT, see: + * https://issues.dlang.org/show_bug.cgi?id=20493 + */ +size_t karatsubaRequiredBuffSize()(size_t xlen) pure nothrow @safe +{ + return xlen < KARATSUBALIMIT ? 0 : (xlen * 9) / 4; +} + +size_t divisionRequiredBuffSize()(size_t ulen, size_t vlen) pure nothrow @safe +{ + return 2 * vlen + ulen + 2 + vlen.karatsubaRequiredBuffSize; +} + +/* Sets result = x*y, using Karatsuba multiplication. +* x must be longer or equal to y. +* Valid only for balanced multiplies, where x is not shorter than y. +* It is superior to schoolbook multiplication if and only if +* sqrt(2)*y.length > x.length > y.length. +* Karatsuba multiplication is O(n^1.59), whereas schoolbook is O(n^2) +* The maximum allowable length of x and y is uint.max; but better algorithms +* should be used far before that length is reached. +* Params: +* scratchbuff An array long enough to store all the temporaries. Will be destroyed. +*/ +void mulKaratsuba(BigDigit [] result, const(BigDigit) [] x, + const(BigDigit)[] y, BigDigit [] scratchbuff) pure nothrow @safe +{ + assert(x.length >= y.length, "x must be greater or equal to y"); + assert(result.length < uint.max, "Operands too large"); + assert(result.length == x.length + y.length, + "result must be as large as x + y"); + if (x.length < KARATSUBALIMIT) + { + return mulSimple(result, x, y); + } + // Must be almost square (otherwise, a schoolbook iteration is better) + assert(2L * y.length * y.length > (x.length-1) * (x.length-1), + "Bigint Internal Error: Asymmetric Karatsuba"); + + // The subtractive version of Karatsuba multiply uses the following result: + // (Nx1 + x0)*(Ny1 + y0) = (N*N)*x1y1 + x0y0 + N * (x0y0 + x1y1 - mid) + // where mid = (x0-x1)*(y0-y1) + // requiring 3 multiplies of length N, instead of 4. + // The advantage of the subtractive over the additive version is that + // the mid multiply cannot exceed length N. But there are subtleties: + // (x0-x1),(y0-y1) may be negative or zero. To keep it simple, we + // retain all of the leading zeros in the subtractions + + // half length, round up. + auto half = (x.length >> 1) + (x.length & 1); + + const(BigDigit) [] x0 = x[0 .. half]; + const(BigDigit) [] x1 = x[half .. $]; + const(BigDigit) [] y0 = y[0 .. half]; + const(BigDigit) [] y1 = y[half .. $]; + BigDigit [] mid = scratchbuff[0 .. half*2]; + BigDigit [] newscratchbuff = scratchbuff[half*2 .. $]; + BigDigit [] resultLow = result[0 .. 2*half]; + BigDigit [] resultHigh = result[2*half .. $]; + // initially use result to store temporaries + BigDigit [] xdiff= result[0 .. half]; + BigDigit [] ydiff = result[half .. half*2]; + + // First, we calculate mid, and sign of mid + immutable bool midNegative = inplaceSub(xdiff, x0, x1) + ^ inplaceSub(ydiff, y0, y1); + mulKaratsuba(mid, xdiff, ydiff, newscratchbuff); + + // Low half of result gets x0 * y0. High half gets x1 * y1 + + mulKaratsuba(resultLow, x0, y0, newscratchbuff); + + if (2L * y1.length * y1.length < x1.length * x1.length) + { + // an asymmetric situation has been created. + // Worst case is if x:y = 1.414 : 1, then x1:y1 = 2.41 : 1. + // Applying one schoolbook multiply gives us two pieces each 1.2:1 + if (y1.length < KARATSUBALIMIT) + mulSimple(resultHigh, x1, y1); + else + { + // divide x1 in two, then use schoolbook multiply on the two pieces. + auto quarter = (x1.length >> 1) + (x1.length & 1); + immutable ysmaller = (quarter >= y1.length); + mulKaratsuba(resultHigh[0 .. quarter+y1.length], ysmaller ? x1[0 .. quarter] : y1, + ysmaller ? y1 : x1[0 .. quarter], newscratchbuff); + // Save the part which will be overwritten. + immutable ysmaller2 = ((x1.length - quarter) >= y1.length); + newscratchbuff[0 .. y1.length] = resultHigh[quarter .. quarter + y1.length]; + mulKaratsuba(resultHigh[quarter..$], ysmaller2 ? x1[quarter..$] : y1, + ysmaller2 ? y1 : x1[quarter..$], newscratchbuff[y1.length..$]); + + resultHigh[quarter..$].addAssignSimple(newscratchbuff[0 .. y1.length]); + } + } + else + mulKaratsuba(resultHigh, x1, y1, newscratchbuff); + + /* We now have result = x0y0 + (N*N)*x1y1 + Before adding or subtracting mid, we must calculate + result += N * (x0y0 + x1y1) + We can do this with three half-length additions. With a = x0y0, b = x1y1: + aHI aLO + + aHI aLO + + bHI bLO + + bHI bLO + = R3 R2 R1 R0 + R1 = aHI + bLO + aLO + R2 = aHI + bLO + aHI + carry_from_R1 + R3 = bHi + carry_from_R2 + It might actually be quicker to do it in two full-length additions: + newscratchbuff[2*half] = addSimple(newscratchbuff[0 .. 2*half], result[0 .. 2*half], result[2*half..$]); + addAssignSimple(result[half..$], newscratchbuff[0 .. 2*half+1]); + */ + BigDigit[] R1 = result[half .. half*2]; + BigDigit[] R2 = result[half*2 .. half*3]; + BigDigit[] R3 = result[half*3..$]; + BigDigit c1 = multibyteAdd(R2, R2, R1, 0); // c1:R2 = R2 + R1 + BigDigit c2 = multibyteAdd(R1, R2, result[0 .. half], 0); // c2:R1 = R2 + R1 + R0 + BigDigit c3 = addAssignSimple(R2, R3); // R2 = R2 + R1 + R3 + if (c1+c2) + multibyteIncrementAssign!('+')(result[half*2..$], c1+c2); + if (c1+c3) + multibyteIncrementAssign!('+')(R3, c1+c3); + + // And finally we subtract mid + addOrSubAssignSimple(result[half..$], mid, !midNegative); +} + +void squareKaratsuba(BigDigit [] result, const BigDigit [] x, + BigDigit [] scratchbuff) pure nothrow @safe +{ + // See mulKaratsuba for implementation comments. + // Squaring is simpler, since it never gets asymmetric. + assert(result.length < uint.max, "Operands too large"); + assert(result.length == 2*x.length, + "result must be twice the length of x"); + if (x.length <= KARATSUBASQUARELIMIT) + { + return squareSimple(result, x); + } + // half length, round up. + auto half = (x.length >> 1) + (x.length & 1); + + const(BigDigit)[] x0 = x[0 .. half]; + const(BigDigit)[] x1 = x[half .. $]; + BigDigit [] mid = scratchbuff[0 .. half*2]; + BigDigit [] newscratchbuff = scratchbuff[half*2 .. $]; + // initially use result to store temporaries + BigDigit [] xdiff= result[0 .. half]; + const BigDigit [] ydiff = result[half .. half*2]; + + // First, we calculate mid. We don't need its sign + inplaceSub(xdiff, x0, x1); + squareKaratsuba(mid, xdiff, newscratchbuff); + + // Set result = x0x0 + (N*N)*x1x1 + squareKaratsuba(result[0 .. 2*half], x0, newscratchbuff); + squareKaratsuba(result[2*half .. $], x1, newscratchbuff); + + /* result += N * (x0x0 + x1x1) + Do this with three half-length additions. With a = x0x0, b = x1x1: + R1 = aHI + bLO + aLO + R2 = aHI + bLO + aHI + carry_from_R1 + R3 = bHi + carry_from_R2 + */ + BigDigit[] R1 = result[half .. half*2]; + BigDigit[] R2 = result[half*2 .. half*3]; + BigDigit[] R3 = result[half*3..$]; + BigDigit c1 = multibyteAdd(R2, R2, R1, 0); // c1:R2 = R2 + R1 + BigDigit c2 = multibyteAdd(R1, R2, result[0 .. half], 0); // c2:R1 = R2 + R1 + R0 + BigDigit c3 = addAssignSimple(R2, R3); // R2 = R2 + R1 + R3 + if (c1+c2) multibyteIncrementAssign!('+')(result[half*2..$], c1+c2); + if (c1+c3) multibyteIncrementAssign!('+')(R3, c1+c3); + + // And finally we subtract mid, which is always positive + subAssignSimple(result[half..$], mid); +} + +/* Knuth's Algorithm D, as presented in + * H.S. Warren, "Hacker's Delight", Addison-Wesley Professional (2002). + * Also described in "Modern Computer Arithmetic" 0.2, Exercise 1.8.18. + * Given u and v, calculates quotient = u / v, u = u % v. + * v must be normalized (ie, the MSB of v must be 1). + * The most significant words of quotient and u may be zero. + * u[0 .. v.length] holds the remainder. + */ +void schoolbookDivMod(BigDigit [] quotient, BigDigit [] u, in BigDigit [] v) + pure nothrow @safe +{ + assert(quotient.length == u.length - v.length, + "quotient has wrong length"); + assert(v.length > 1, "v must not be empty"); + assert(u.length >= v.length, "u must be larger or equal to v"); + assert((v[$ - 1] & 0x8000_0000) != 0, "Invalid value at v[$ - 1]"); + assert(u[$ - 1] < v[$ - 1], "u[$ - 1] must be less than v[$ - 1]"); + // BUG: This code only works if BigDigit is uint. + uint vhi = v[$-1]; + uint vlo = v[$-2]; + + for (ptrdiff_t j = u.length - v.length - 1; j >= 0; j--) + { + // Compute estimate of quotient[j], + // qhat = (three most significant words of u)/(two most sig words of v). + uint qhat; + if (u[j + v.length] == vhi) + { + // uu/vhi could exceed uint.max (it will be 0x8000_0000 or 0x8000_0001) + qhat = uint.max; + } + else + { + uint ulo = u[j + v.length - 2]; + version (D_InlineAsm_X86) + { + // Note: On DMD, this is only ~10% faster than the non-asm code. + uint *p = &u[j + v.length - 1]; + asm pure nothrow @trusted @nogc + { + mov EAX, p; + mov EDX, [EAX+4]; + mov EAX, [EAX]; + div dword ptr [vhi]; + mov qhat, EAX; + mov ECX, EDX; +div3by2correction: + mul dword ptr [vlo]; // EDX:EAX = qhat * vlo + sub EAX, ulo; + sbb EDX, ECX; + jbe div3by2done; + mov EAX, qhat; + dec EAX; + mov qhat, EAX; + add ECX, dword ptr [vhi]; + jnc div3by2correction; +div3by2done: ; + } + } + else + { // version (InlineAsm) + ulong uu = (cast(ulong)(u[j + v.length]) << 32) | u[j + v.length - 1]; + immutable bigqhat = uu / vhi; + ulong rhat = uu - bigqhat * vhi; + qhat = cast(uint) bigqhat; +again: + if (cast(ulong) qhat * vlo > ((rhat << 32) + ulo)) + { + --qhat; + rhat += vhi; + if (!(rhat & 0xFFFF_FFFF_0000_0000L)) + goto again; + } + } // version (InlineAsm) + } + // Multiply and subtract. + uint carry = multibyteMulAdd!('-')(u[j .. j + v.length], v, qhat, 0); + + if (u[j+v.length] < carry) + { + // If we subtracted too much, add back + --qhat; + carry -= multibyteAdd(u[j .. j + v.length],u[j .. j + v.length], v, 0); + } + quotient[j] = qhat; + u[j + v.length] = u[j + v.length] - carry; + } +} + +// TODO: Replace with a library call +void itoaZeroPadded(char[] output, uint value) + pure nothrow @safe @nogc +{ + for (auto i = output.length; i--;) + { + if (value < 10) + { + output[i] = cast(char)(value + '0'); + value = 0; + } + else + { + output[i] = cast(char)(value % 10 + '0'); + value /= 10; + } + } +} + +void toHexZeroPadded(char[] output, uint value, + LetterCase letterCase = LetterCase.upper) pure nothrow @safe +{ + ptrdiff_t x = output.length - 1; + static immutable string upperHexDigits = "0123456789ABCDEF"; + static immutable string lowerHexDigits = "0123456789abcdef"; + for ( ; x >= 0; --x) + { + if (letterCase == LetterCase.upper) + { + output[x] = upperHexDigits[value & 0xF]; + } + else + { + output[x] = lowerHexDigits[value & 0xF]; + } + value >>= 4; + } +} + +// Returns the highest value of i for which left[i]!=right[i], +// or 0 if left[] == right[] +size_t highestDifferentDigit(const BigDigit [] left, const BigDigit [] right) +pure nothrow @nogc @safe +{ + assert(left.length == right.length, + "left have a length equal to that of right"); + for (ptrdiff_t i = left.length - 1; i>0; --i) + { + if (left[i] != right[i]) + return i; + } + return 0; +} + +// Returns the lowest value of i for which x[i]!=0. +int firstNonZeroDigit(const BigDigit [] x) pure nothrow @nogc @safe +{ + int k = 0; + while (x[k]==0) + { + ++k; + assert(k < x.length, "k must be less than x.length"); + } + return k; +} + +/* + Calculate quotient and remainder of u / v using fast recursive division. + v must be normalised, and must be at least half as long as u. + Given u and v, v normalised, calculates quotient = u/v, u = u%v. + scratch is temporary storage space, length must be >= quotient + 1. +Returns: + u[0 .. v.length] is the remainder. u[v.length..$] is corrupted. + Implements algorithm 1.8 from MCA. + This algorithm has an annoying special case. After the first recursion, the + highest bit of the quotient may be set. This means that in the second + recursive call, the 'in' contract would be violated. (This happens only + when the top quarter of u is equal to the top half of v. A base 10 + equivalent example of this situation is 5517/56; the first step gives + 55/5 = 11). To maintain the in contract, we pad a zero to the top of both + u and the quotient. 'mayOverflow' indicates that that the special case + has occurred. + (In MCA, a different strategy is used: the in contract is weakened, and + schoolbookDivMod is more general: it allows the high bit of u to be set). + See also: + - C. Burkinel and J. Ziegler, "Fast Recursive Division", MPI-I-98-1-022, + Max-Planck Institute fuer Informatik, (Oct 1998). +*/ +void recursiveDivMod(BigDigit[] quotient, BigDigit[] u, const(BigDigit)[] v, + BigDigit[] scratch, bool mayOverflow = false) + pure nothrow @safe +in +{ + // v must be normalized + assert(v.length > 1, "v must not be empty"); + assert((v[$ - 1] & 0x8000_0000) != 0, "Invalid value at v[$ - 1]"); + assert(!(u[$ - 1] & 0x8000_0000), "Invalid value at u[$ - 1]"); + assert(quotient.length == u.length - v.length, + "quotient must be of equal length of u - v"); + if (mayOverflow) + { + assert(u[$-1] == 0, "Invalid value at u[$ - 1]"); + assert(u[$-2] & 0x8000_0000, "Invalid value at u[$ - 2]"); + } + + // Must be symmetric. Use block schoolbook division if not. + assert((mayOverflow ? u.length-1 : u.length) <= 2 * v.length, + "Invalid length of u"); + assert((mayOverflow ? u.length-1 : u.length) >= v.length, + "Invalid length of u"); + assert(scratch.length >= quotient.length + (mayOverflow ? 0 : 1), + "Invalid quotient length"); +} +do +{ + if (quotient.length < FASTDIVLIMIT) + { + return schoolbookDivMod(quotient, u, v); + } + + // Split quotient into two halves, but keep padding in the top half + auto k = (mayOverflow ? quotient.length - 1 : quotient.length) >> 1; + + // RECURSION 1: Calculate the high half of the quotient + + // Note that if u and quotient were padded, they remain padded during + // this call, so in contract is satisfied. + recursiveDivMod(quotient[k .. $], u[2 * k .. $], v[k .. $], + scratch, mayOverflow); + + // quotient[k..$] is our guess at the high quotient. + // u[2*k .. 2.*k + v.length - k = k + v.length] is the high part of the + // first remainder. u[0 .. 2*k] is the low part. + + // Calculate the full first remainder to be + // remainder - highQuotient * lowDivisor + // reducing highQuotient until the remainder is positive. + // The low part of the remainder, u[0 .. k], cannot be altered by this. + + adjustRemainder(quotient[k .. $], u[k .. k + v.length], v, k, + scratch, mayOverflow); + + // RECURSION 2: Calculate the low half of the quotient + // The full first remainder is now in u[0 .. k + v.length]. + + if (u[k + v.length - 1] & 0x8000_0000) + { + // Special case. The high quotient is 0x1_00...000 or 0x1_00...001. + // This means we need an extra quotient word for the next recursion. + // We need to restore the invariant for the recursive calls. + // We do this by padding both u and quotient. Extending u is trivial, + // because the higher words will not be used again. But for the + // quotient, we're clobbering the low word of the high quotient, + // so we need save it, and add it back in after the recursive call. + + auto clobberedQuotient = quotient[k]; + u[k + v.length] = 0; + + recursiveDivMod(quotient[0 .. k + 1], u[k .. k + v.length + 1], + v[k .. $], scratch, true); + adjustRemainder(quotient[0 .. k + 1], u[0 .. v.length], v, k, + scratch, true); + + // Now add the quotient word that got clobbered earlier. + multibyteIncrementAssign!('+')(quotient[k..$], clobberedQuotient); + } + else + { + // The special case has NOT happened. + recursiveDivMod(quotient[0 .. k], u[k .. k + v.length], v[k .. $], + scratch, false); + + // high remainder is in u[k .. k+(v.length-k)] == u[k .. v.length] + + adjustRemainder(quotient[0 .. k], u[0 .. v.length], v, k, + scratch); + } +} + +// rem -= quot * v[0 .. k]. +// If would make rem negative, decrease quot until rem is >= 0. +// Needs (quot.length * k) scratch space to store the result of the multiply. +void adjustRemainder(BigDigit[] quot, BigDigit[] rem, const(BigDigit)[] v, + ptrdiff_t k, + BigDigit[] scratch, bool mayOverflow = false) pure nothrow @safe +{ + assert(rem.length == v.length, "rem must be as long as v"); + auto res = scratch[0 .. quot.length + k]; + mulInternal(res, quot, v[0 .. k], scratch[quot.length + k .. $]); + uint carry = subAssignSimple(rem, res[0..$-mayOverflow]); + if (mayOverflow) + carry += res[$-1]; + while (carry) + { + multibyteIncrementAssign!('-')(quot, 1); // quot-- + carry -= multibyteAdd(rem, rem, v, 0); + } +} +//u + 1, v, v kur, +// Cope with unbalanced division by performing block schoolbook division. +void blockDivMod(BigDigit [] q, BigDigit [] u, in BigDigit [] v, BigDigit [] scratch) +pure nothrow @safe +{ + assert(q.length == u.length - v.length, + "quotient must be of equal length of u - v"); + assert(v.length > 1, "v must not be empty"); + assert(u.length >= v.length, "u must be longer or of equal length as v"); + assert((v[$-1] & 0x8000_0000)!=0, "Invalid value at v[$ - 1]"); + assert((u[$-1] & 0x8000_0000)==0, "Invalid value at u[$ - 1]"); + + // Perform block schoolbook division, with 'v.length' blocks. + auto m = u.length - v.length; + auto n = u.length - v.length; + do + { + if (n <= v.length) + n = v.length; + auto mayOverflow = (u[m + v.length -1 ] & 0x8000_0000)!=0; + BigDigit saveq; + if (mayOverflow) + { + u[m + v.length] = 0; + saveq = q[m]; + } + + recursiveDivMod( + q[n - v.length .. m + mayOverflow], + u[n - v.length .. m + mayOverflow + v.length], + v, scratch, mayOverflow); + if (mayOverflow) + { + assert(q[m] == 0, "q must not be 0"); + q[m] = saveq; + } + m -= v.length; + n -= v.length; + } + while(n); +} + +version(mir_bignum_test) +@system unittest +{ + version (none) + { + import core.stdc.stdio; + + void printBiguint(const uint [] data) + { + char [] buff = biguintToHex(new char[data.length*9], data, '_'); + printf("%.*s\n", cast(int) buff.length, buff.ptr); + } + + void printDecimalBigUint(BigUint data) + { + auto str = data.toDecimalString(0); + printf("%.*s\n", cast(int) str.length, str.ptr); + } + } + + uint[43] a; + uint[179] b; + for (int i=0; i= 0); + assert(e <= 1 << 15); + return cast(uint) (((e * 163391164108059UL) >> 46) + 1); +} + +void mul_128_256_shift(const UInt!128 a, const UInt!256 b, const uint shift, const uint corr, ref UInt!256 result) +{ + version(LDC) pragma(inline, true); + assert(shift > 0); + assert(shift < 256); + result = (extendedMul(a, b) >> shift).toSize!256 + corr; +} + +// Computes 5^i in the form required by Ryu, and stores it in the given pointer. +void generic_computePow5(const uint i, ref UInt!256 result) +{ + import mir.bignum.internal.ryu.table; + + version(LDC) pragma(inline, true); + const uint base = i / POW5_TABLE_SIZE; + const uint base2 = base * POW5_TABLE_SIZE; + const mul = UInt!256(GENERIC_POW5_SPLIT[base]); + if (i == base2) + { + result = mul; + } + else + { + const uint offset = i - base2; + const m = UInt!128(GENERIC_POW5_TABLE[offset]); + const uint delta = pow5bits(i) - pow5bits(base2); + const uint corr = cast(uint) ((POW5_ERRORS[i / 32] >> (2 * (i % 32))) & 3); + mul_128_256_shift(m, mul, delta, corr, result); + } +} + +version(mir_bignum_test) unittest +{ + // We only test a few entries - we could test the fUL table instead, but should we? + static immutable uint[10] EXACT_POW5_IDS = [1, 10, 55, 56, 300, 1000, 2345, 3210, 4968 - 3, 4968 - 1]; + + static immutable ulong[4][10] EXACT_POW5 = [ + [ 0u, 0u, 0u, 90071992547409920u], + [ 0u, 0u, 0u, 83886080000000000u], + [ 0u, 15708555500268290048u, 14699724349295723422u, 117549435082228750u], + [ 0u, 5206161169240293376u, 4575641699882439235u, 73468396926392969u], + [ 2042133660145364371u, 9702060195405861314u, 6467325284806654637u, 107597969523956154u], + [15128847313296546509u, 11916317791371073891u, 788593023170869613u, 137108429762886488u], + [10998857860460266920u, 858411415306315808u, 12732466392391605111u, 136471991906002539u], + [ 5404652432687674341u, 18039986361557197657u, 2228774284272261859u, 94370442653226447u], + [15313487127299642753u, 9780770376910163681u, 15213531439620567348u, 93317108016191349u], + [ 7928436552078881485u, 723697829319983520u, 932817143438521969u, 72903990637649492u], + ]; + + for (int i = 0; i < 10; i++) + { + UInt!256 result; + generic_computePow5(EXACT_POW5_IDS[i], result); + assert(UInt!256(EXACT_POW5[i]) == result); + } +} + +// Computes 5^-i in the form required by Ryu, and stores it in the given pointer. +void generic_computeInvPow5(const uint i, ref UInt!256 result) +{ + import mir.bignum.internal.ryu.table; + + version(LDC) pragma(inline, true); + const uint base = (i + POW5_TABLE_SIZE - 1) / POW5_TABLE_SIZE; + const uint base2 = base * POW5_TABLE_SIZE; + const mul = UInt!256(GENERIC_POW5_INV_SPLIT[base]); // 1/5^base2 + if (i == base2) + { + result = mul + 1; + } + else + { + const uint offset = base2 - i; + const m = UInt!128(GENERIC_POW5_TABLE[offset]); // 5^offset + const uint delta = pow5bits(base2) - pow5bits(i); + const uint corr = cast(uint) ((POW5_INV_ERRORS[i / 32] >> (2 * (i % 32))) & 3) + 1; + mul_128_256_shift(m, mul, delta, corr, result); + } +} + +version(mir_bignum_test) unittest +{ + static immutable uint[9] EXACT_INV_POW5_IDS = [10, 55, 56, 300, 1000, 2345, 3210, 4897 - 3, 4897 - 1]; + + static immutable ulong[4][10] EXACT_INV_POW5 = [ + [13362655651931650467u, 3917988799323120213u, 9037289074543890586u, 123794003928538027u], + [ 983662216614650042u, 15516934687640009097u, 8839031818921249472u, 88342353238919216u], + [ 1573859546583440066u, 2691002611772552616u, 6763753280790178510u, 141347765182270746u], + [ 1607391579053635167u, 943946735193622172u, 10726301928680150504u, 96512915280967053u], + [ 7238603269427345471u, 17319296798264127544u, 14852913523241959878u, 75740009093741608u], + [ 2734564866961569744u, 13277212449690943834u, 17231454566843360565u, 76093223027199785u], + [ 5348945211244460332u, 14119936934335594321u, 15647307321253222579u, 110040743956546595u], + [ 2848579222248330872u, 15087265905644220040u, 4449739884766224405u, 100774177495370196u], + [ 1432572115632717323u, 9719393440895634811u, 3482057763655621045u, 128990947194073851u], + ]; + + for (int i = 0; i < 9; i++) + { + UInt!256 result; + generic_computeInvPow5(EXACT_INV_POW5_IDS[i], result); + assert(UInt!256(EXACT_INV_POW5[i]) == result); + } +} + +version(LittleEndian) + enum fiveReciprocal = UInt!128([0xCCCCCCCCCCCCCCCD, 0xCCCCCCCCCCCCCCCC]); +else + enum fiveReciprocal = UInt!128([0xCCCCCCCCCCCCCCCC, 0xCCCCCCCCCCCCCCCD]); + +enum baseDiv5 = UInt!128([0x3333333333333333, 0x3333333333333333]); + +uint divRem5(uint size)(ref UInt!size value) +{ + auto q = div5(value); + auto r = cast(uint)(value - q * 5); + value = q; + return r; +} + +uint divRem10(uint size)(ref UInt!size value) +{ + auto q = div10(value); + auto r = cast(uint)(value - q * 10); + value = q; + return r; +} + +uint rem5(uint size)(UInt!size value) +{ + return divRem5(value); +} + +uint rem10(uint size)(UInt!size value) +{ + return divRem10(value); +} + +UInt!size div5(uint size)(UInt!size value) +{ + return extendedMulHigh(value, fiveReciprocal.toSize!size) >> 2; +} + +UInt!size div10(uint size)(UInt!size value) +{ + return extendedMulHigh(value, fiveReciprocal.toSize!size) >> 3; +} + +// Returns true if value is divisible by 5^p. +bool multipleOfPowerOf5(uint size)(UInt!size value, const uint p) +{ + enum fiveReciprocal = .fiveReciprocal.toSize!size; + enum baseDiv5 = .baseDiv5.toSize!size; + version(LDC) pragma(inline, true); + assert(value); + for (uint count = 0;; ++count) + { + value *= fiveReciprocal; + if (value > baseDiv5) + return count >= p; + } +} + +version(mir_bignum_test) unittest +{ + assert(multipleOfPowerOf5(UInt!128(1), 0) == true); + assert(multipleOfPowerOf5(UInt!128(1), 1) == false); + assert(multipleOfPowerOf5(UInt!128(5), 1) == true); + assert(multipleOfPowerOf5(UInt!128(25), 2) == true); + assert(multipleOfPowerOf5(UInt!128(75), 2) == true); + assert(multipleOfPowerOf5(UInt!128(50), 2) == true); + assert(multipleOfPowerOf5(UInt!128(51), 2) == false); + assert(multipleOfPowerOf5(UInt!128(75), 4) == false); +} + +// Returns true if value is divisible by 2^p. +bool multipleOfPowerOf2(uint size)(const UInt!size value, const uint p) +{ + version(LDC) pragma(inline, true); + return (value & ((UInt!size(1) << p) - 1)) == 0; +} + +version(mir_bignum_test) unittest +{ + assert(multipleOfPowerOf5(UInt!128(1), 0) == true); + assert(multipleOfPowerOf5(UInt!128(1), 1) == false); + assert(multipleOfPowerOf2(UInt!128(2), 1) == true); + assert(multipleOfPowerOf2(UInt!128(4), 2) == true); + assert(multipleOfPowerOf2(UInt!128(8), 2) == true); + assert(multipleOfPowerOf2(UInt!128(12), 2) == true); + assert(multipleOfPowerOf2(UInt!128(13), 2) == false); + assert(multipleOfPowerOf2(UInt!128(8), 4) == false); +} + +UInt!size mulShift(uint size)(const UInt!size m, const UInt!256 mul, const uint j) +{ + version(LDC) pragma(inline, true); + assert(j > 128); + return (extendedMul(mul, m) >> 128 >> (j - 128)).toSize!size; +} + +version(mir_bignum_test) unittest +{ + UInt!256 m = cast(ulong[4])[0, 0, 2, 0]; + assert(mulShift(UInt!128(1), m, 129) == 1u); + assert(mulShift(UInt!128(12345), m, 129) == 12345u); +} + +version(mir_bignum_test) unittest +{ + UInt!256 m = cast(ulong[4])[0, 0, 8, 0]; + UInt!128 f = (UInt!128(123) << 64) | 321; + assert(mulShift(f, m, 131) == f); +} + +// Returns floor(log_10(2^e)). +uint log10Pow2(const int e) +{ + version(LDC) pragma(inline, true); + // The first value this approximation fails for is 2^1651 which is just greater than 10^297. + assert(e >= 0); + assert(e <= 1 << 15); + return (e * 0x9A209A84FBCFUL) >> 49; +} + +version(mir_bignum_test) unittest +{ + assert(log10Pow2(1) == 0u); + assert(log10Pow2(5) == 1u); + assert(log10Pow2(1 << 15) == 9864u); +} + +// Returns floor(log_10(5^e)). +uint log10Pow5(const int e) +{ + version(LDC) pragma(inline, true); + // The first value this approximation fails for is 5^2621 which is just greater than 10^1832. + assert(e >= 0); + assert(e <= 1 << 15); + return (e * 0xB2EFB2BD8218UL) >> 48; +} + +version(mir_bignum_test) unittest +{ + assert(log10Pow5(1) == 0u); + assert(log10Pow5(2) == 1u); + assert(log10Pow5(3) == 2u); + assert(log10Pow5(1 << 15) == 22903u); +} + +debug(ryu) +private char* s(UInt!128 v) +{ + import mir.conv: to; + return (v.to!string ~ "\0").ptr; +} + +// Converts the given binary floating point number to the shortest decimal floating point number +// that still accurately represents it. +Decimal!(T.mant_dig < 64 ? 1 : 2) genericBinaryToDecimal(T)(const T x) +{ + import mir.utility: _expect; + import mir.math: signbit, fabs; + enum coefficientSize = T.mant_dig <= 64 ? 64 : 128; + enum workSize = T.mant_dig < 64 ? 64 : 128; + enum wordCount = workSize / 64; + + Decimal!wordCount fd; + if (_expect(x != x, false)) + { + fd.coefficient = 1u; + fd.exponent = fd.exponent.max; + } + else + if (_expect(x.fabs == T.infinity, false)) + { + fd.exponent = fd.exponent.max; + } + else + if (x) + { + import mir.bignum.fp: Fp; + const fp = Fp!coefficientSize(x, false); + int e2 = cast(int) fp.exponent - 2; + UInt!workSize m2 = fp.coefficient; + + const bool even = (fp.coefficient & 1) == 0; + const bool acceptBounds = even; + + debug(ryu) if (!__ctfe) + { + printf("-> %s %s * 2^%d\n", (fp.sign ? "-" : "+").ptr, s(m2), e2 + 2); + } + + // Step 2: Determine the interval of legal decimal representations. + const UInt!workSize mv = m2 << 2; + // Implicit bool -> int conversion. True is 1, false is 0. + const bool mmShift = fp.coefficient != (UInt!coefficientSize(1) << (T.mant_dig - 1)); + + // Step 3: Convert to a decimal power base using 128-bit arithmetic. + UInt!workSize vr, vp, vm; + int e10; + bool vmIsTrailingZeros = false; + bool vrIsTrailingZeros = false; + if (e2 >= 0) + { + // I tried special-casing q == 0, but there was no effect on performance. + // This expression is slightly faster than max(0, log10Pow2(e2) - 1). + const uint q = log10Pow2(e2) - (e2 > 3); + e10 = q; + const int k = FLOAT_128_POW5_INV_BITCOUNT + pow5bits(q) - 1; + const int i = -e2 + q + k; + UInt!256 pow5; + generic_computeInvPow5(q, pow5); + vr = mulShift(mv, pow5, i); + vp = mulShift(mv + 2, pow5, i); + vm = mulShift(mv - 1 - mmShift, pow5, i); + debug(ryu) if (!__ctfe) + { + printf("%s * 2^%d / 10^%d\n", s(mv), e2, q); + printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); + } + // floor(log_5(2^128)) = 55, this is very conservative + if (q <= 55) + { + // Only one of mp, mv, and mm can be a multiple of 5, if any. + if (rem5(mv) == 0) + { + vrIsTrailingZeros = multipleOfPowerOf5(mv, q - 1); + } + else + if (acceptBounds) + { + // Same as min(e2 + (~mm & 1), pow5Factor(mm)) >= q + // <=> e2 + (~mm & 1) >= q && pow5Factor(mm) >= q + // <=> true && pow5Factor(mm) >= q, since e2 >= q. + vmIsTrailingZeros = multipleOfPowerOf5(mv - 1 - mmShift, q); + } + else + { + // Same as min(e2 + 1, pow5Factor(mp)) >= q. + vp -= multipleOfPowerOf5(mv + 2, q); + } + } + } + else + { + // This expression is slightly faster than max(0, log10Pow5(-e2) - 1). + const uint q = log10Pow5(-e2) - (-e2 > 1); + e10 = q + e2; + const int i = -e2 - q; + const int k = pow5bits(i) - FLOAT_128_POW5_BITCOUNT; + const int j = q - k; + UInt!256 pow5; + generic_computePow5(i, pow5); + vr = mulShift(mv, pow5, j); + vp = mulShift(mv + 2, pow5, j); + vm = mulShift(mv - 1 - mmShift, pow5, j); + debug(ryu) if (!__ctfe) + { + printf("%s * 5^%d / 10^%d\n", s(mv), -e2, q); + printf("%d %d %d %d\n", q, i, k, j); + printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); + } + if (q <= 1) + { + // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. + // mv = 4 m2, so it always has at least two trailing 0 bits. + vrIsTrailingZeros = true; + if (acceptBounds) + { + // mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1. + vmIsTrailingZeros = mmShift == 1; + } + else + { + // mp = mv + 2, so it always has at least one trailing 0 bit. + --vp; + } + } + else + if (q < workSize - 1) + { + // TODO(ulfjack): Use a tighter bound here. + // We need to compute min(ntz(mv), pow5Factor(mv) - e2) >= q-1 + // <=> ntz(mv) >= q-1 && pow5Factor(mv) - e2 >= q-1 + // <=> ntz(mv) >= q-1 (e2 is negative and -e2 >= q) + // <=> (mv & ((1 << (q-1)) - 1)) == 0 + // We also need to make sure that the left shift does not overflow. + vrIsTrailingZeros = multipleOfPowerOf2(mv, q - 1); + debug(ryu) if (!__ctfe) + { + printf("vr is trailing zeros=%s\n", (vrIsTrailingZeros ? "true" : "false").ptr); + } + } + } + debug(ryu) if (!__ctfe) + { + printf("e10=%d\n", e10); + printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); + printf("vm is trailing zeros=%s\n", (vmIsTrailingZeros ? "true" : "false").ptr); + printf("vr is trailing zeros=%s\n", (vrIsTrailingZeros ? "true" : "false").ptr); + } + + // Step 4: Find the shortest decimal representation in the interval of legal representations. + uint removed = 0; + uint lastRemovedDigit = 0; + UInt!workSize output; + + for (;;) + { + auto div10vp = div10(vp); + auto div10vm = div10(vm); + if (div10vp == div10vm) + break; + vmIsTrailingZeros &= vm - div10vm * 10 == 0; + vrIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = vr.divRem10; + vp = div10vp; + vm = div10vm; + ++removed; + } + debug(ryu) if (!__ctfe) + { + printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); + printf("d-10=%s\n", (vmIsTrailingZeros ? "true" : "false").ptr); + printf("lastRemovedDigit=%d\n", lastRemovedDigit); + } + if (vmIsTrailingZeros) + { + for (;;) + { + auto div10vm = div10(vm); + if (vm - div10vm * 10) + break; + vrIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = cast(uint) (vr - div10vm * 10); + vr = vp = vm = div10vm; + ++removed; + } + } + debug(ryu) if (!__ctfe) + { + printf("%s %d\n", s(vr), lastRemovedDigit); + printf("vr is trailing zeros=%s\n", (vrIsTrailingZeros ? "true" : "false").ptr); + printf("lastRemovedDigit=%d\n", lastRemovedDigit); + } + if (vrIsTrailingZeros && (lastRemovedDigit == 5) && ((vr & 1) == 0)) + { + // Round even if the exact numbers is .....50..0. + lastRemovedDigit = 4; + } + // We need to take vr+1 if vr is outside bounds or we need to round up. + output = vr + ((vr == vm && (!acceptBounds || !vmIsTrailingZeros)) || (lastRemovedDigit >= 5)); + + const int exp = e10 + removed; + + debug(ryu) if (!__ctfe) + { + printf("V+=%s\nV =%s\nV-=%s\n", s(vp), s(vr), s(vm)); + printf("acceptBounds=%d\n", acceptBounds); + printf("vmIsTrailingZeros=%d\n", vmIsTrailingZeros); + printf("lastRemovedDigit=%d\n", lastRemovedDigit); + printf("vrIsTrailingZeros=%d\n", vrIsTrailingZeros); + printf("O=%s\n", s(output)); + printf("EXP=%d\n", exp); + } + + import mir.bignum.integer: BigInt; + fd.coefficient.__ctor(output); + fd.exponent = exp; + } + fd.coefficient.sign = x.signbit; + return fd; +} + +private enum FLOAT_128_POW5_INV_BITCOUNT = 249; +private enum FLOAT_128_POW5_BITCOUNT = 249; diff --git a/source/mir/bignum/internal/ryu/table.d b/source/mir/bignum/internal/ryu/table.d new file mode 100644 index 00000000..3a6b0eb4 --- /dev/null +++ b/source/mir/bignum/internal/ryu/table.d @@ -0,0 +1,336 @@ +module mir.bignum.internal.ryu.table; + +package enum POW5_TABLE_SIZE = 56; + +// These tables are ~4.5 kByte total, compared to ~160 kByte for the fUL tables. + +// There's no way to define 128-bit constants in C, so we use little-endian +// pairs of 64-bit constants. +package static immutable ulong[2] [POW5_TABLE_SIZE] GENERIC_POW5_TABLE = [ + [ 1u, 0u], + [ 5u, 0u], + [ 25u, 0u], + [ 125u, 0u], + [ 625u, 0u], + [ 3125u, 0u], + [ 15625u, 0u], + [ 78125u, 0u], + [ 390625u, 0u], + [ 1953125u, 0u], + [ 9765625u, 0u], + [ 48828125u, 0u], + [ 244140625u, 0u], + [ 1220703125u, 0u], + [ 6103515625u, 0u], + [ 30517578125u, 0u], + [ 152587890625u, 0u], + [ 762939453125u, 0u], + [ 3814697265625u, 0u], + [ 19073486328125u, 0u], + [ 95367431640625u, 0u], + [ 476837158203125u, 0u], + [ 2384185791015625u, 0u], + [ 11920928955078125u, 0u], + [ 59604644775390625u, 0u], + [ 298023223876953125u, 0u], + [ 1490116119384765625u, 0u], + [ 7450580596923828125u, 0u], + [ 359414837200037393u, 2u], + [ 1797074186000186965u, 10u], + [ 8985370930000934825u, 50u], + [ 8033366502585570893u, 252u], + [ 3273344365508751233u, 1262u], + [16366721827543756165u, 6310u], + [ 8046632842880574361u, 31554u], + [ 3339676066983768573u, 157772u], + [16698380334918842865u, 788860u], + [ 9704925379756007861u, 3944304u], + [11631138751360936073u, 19721522u], + [ 2815461535676025517u, 98607613u], + [14077307678380127585u, 493038065u], + [15046306170771983077u, 2465190328u], + [ 1444554559021708921u, 12325951644u], + [ 7222772795108544605u, 61629758220u], + [17667119901833171409u, 308148791101u], + [14548623214327650581u, 1540743955509u], + [17402883850509598057u, 7703719777548u], + [13227442957709783821u, 38518598887744u], + [10796982567420264257u, 192592994438723u], + [17091424689682218053u, 962964972193617u], + [11670147153572883801u, 4814824860968089u], + [ 3010503546735764157u, 24074124304840448u], + [15052517733678820785u, 120370621524202240u], + [ 1475612373555897461u, 601853107621011204u], + [ 7378061867779487305u, 3009265538105056020u], + [18443565265187884909u, 15046327690525280101u], +]; + +package static immutable ulong[4][89] GENERIC_POW5_SPLIT = [ + [ 0u, 0u, 0u, 72057594037927936u], + [ 0u, 5206161169240293376u, 4575641699882439235u, 73468396926392969u], + [ 3360510775605221349u, 6983200512169538081u, 4325643253124434363u, 74906821675075173u], + [ 11917660854915489451u, 9652941469841108803u, 946308467778435600u, 76373409087490117u], + [ 1994853395185689235u, 16102657350889591545u, 6847013871814915412u, 77868710555449746u], + [ 958415760277438274u, 15059347134713823592u, 7329070255463483331u, 79393288266368765u], + [ 2065144883315240188u, 7145278325844925976u, 14718454754511147343u, 80947715414629833u], + [ 8980391188862868935u, 13709057401304208685u, 8230434828742694591u, 82532576417087045u], + [ 432148644612782575u, 7960151582448466064u, 12056089168559840552u, 84148467132788711u], + [ 484109300864744403u, 15010663910730448582u, 16824949663447227068u, 85795995087002057u], + [ 14793711725276144220u, 16494403799991899904u, 10145107106505865967u, 87475779699624060u], + [ 15427548291869817042u, 12330588654550505203u, 13980791795114552342u, 89188452518064298u], + [ 9979404135116626552u, 13477446383271537499u, 14459862802511591337u, 90934657454687378u], + [ 12385121150303452775u, 9097130814231585614u, 6523855782339765207u, 92715051028904201u], + [ 1822931022538209743u, 16062974719797586441u, 3619180286173516788u, 94530302614003091u], + [ 12318611738248470829u, 13330752208259324507u, 10986694768744162601u, 96381094688813589u], + [ 13684493829640282333u, 7674802078297225834u, 15208116197624593182u, 98268123094297527u], + [ 5408877057066295332u, 6470124174091971006u, 15112713923117703147u, 100192097295163851u], + [ 11407083166564425062u, 18189998238742408185u, 4337638702446708282u, 102153740646605557u], + [ 4112405898036935485u, 924624216579956435u, 14251108172073737125u, 104153790666259019u], + [ 16996739107011444789u, 10015944118339042475u, 2395188869672266257u, 106192999311487969u], + [ 4588314690421337879u, 5339991768263654604u, 15441007590670620066u, 108272133262096356u], + [ 2286159977890359825u, 14329706763185060248u, 5980012964059367667u, 110391974208576409u], + [ 9654767503237031099u, 11293544302844823188u, 11739932712678287805u, 112553319146000238u], + [ 11362964448496095896u, 7990659682315657680u, 251480263940996374u, 114756980673665505u], + [ 1423410421096377129u, 14274395557581462179u, 16553482793602208894u, 117003787300607788u], + [ 2070444190619093137u, 11517140404712147401u, 11657844572835578076u, 119294583757094535u], + [ 7648316884775828921u, 15264332483297977688u, 247182277434709002u, 121630231312217685u], + [ 17410896758132241352u, 10923914482914417070u, 13976383996795783649u, 124011608097704390u], + [ 9542674537907272703u, 3079432708831728956u, 14235189590642919676u, 126439609438067572u], + [ 10364666969937261816u, 8464573184892924210u, 12758646866025101190u, 128915148187220428u], + [ 14720354822146013883u, 11480204489231511423u, 7449876034836187038u, 131439155071681461u], + [ 1692907053653558553u, 17835392458598425233u, 1754856712536736598u, 134012579040499057u], + [ 5620591334531458755u, 11361776175667106627u, 13350215315297937856u, 136636387622027174u], + [ 17455759733928092601u, 10362573084069962561u, 11246018728801810510u, 139311567287686283u], + [ 2465404073814044982u, 17694822665274381860u, 1509954037718722697u, 142039123822846312u], + [ 2152236053329638369u, 11202280800589637091u, 16388426812920420176u, 72410041352485523u], + [ 17319024055671609028u, 10944982848661280484u, 2457150158022562661u, 73827744744583080u], + [ 17511219308535248024u, 5122059497846768077u, 2089605804219668451u, 75273205100637900u], + [ 10082673333144031533u, 14429008783411894887u, 12842832230171903890u, 76746965869337783u], + [ 16196653406315961184u, 10260180891682904501u, 10537411930446752461u, 78249581139456266u], + [ 15084422041749743389u, 234835370106753111u, 16662517110286225617u, 79781615848172976u], + [ 8199644021067702606u, 3787318116274991885u, 7438130039325743106u, 81343645993472659u], + [ 12039493937039359765u, 9773822153580393709u, 5945428874398357806u, 82936258850702722u], + [ 984543865091303961u, 7975107621689454830u, 6556665988501773347u, 84560053193370726u], + [ 9633317878125234244u, 16099592426808915028u, 9706674539190598200u, 86215639518264828u], + [ 6860695058870476186u, 4471839111886709592u, 7828342285492709568u, 87903640274981819u], + [ 14583324717644598331u, 4496120889473451238u, 5290040788305728466u, 89624690099949049u], + [ 18093669366515003715u, 12879506572606942994u, 18005739787089675377u, 91379436055028227u], + [ 17997493966862379937u, 14646222655265145582u, 10265023312844161858u, 93168537870790806u], + [ 12283848109039722318u, 11290258077250314935u, 9878160025624946825u, 94992668194556404u], + [ 8087752761883078164u, 5262596608437575693u, 11093553063763274413u, 96852512843287537u], + [ 15027787746776840781u, 12250273651168257752u, 9290470558712181914u, 98748771061435726u], + [ 15003915578366724489u, 2937334162439764327u, 5404085603526796602u, 100682155783835929u], + [ 5225610465224746757u, 14932114897406142027u, 2774647558180708010u, 102653393903748137u], + [ 17112957703385190360u, 12069082008339002412u, 3901112447086388439u, 104663226546146909u], + [ 4062324464323300238u, 3992768146772240329u, 15757196565593695724u, 106712409346361594u], + [ 5525364615810306701u, 11855206026704935156u, 11344868740897365300u, 108801712734172003u], + [ 9274143661888462646u, 4478365862348432381u, 18010077872551661771u, 110931922223466333u], + [ 12604141221930060148u, 8930937759942591500u, 9382183116147201338u, 113103838707570263u], + [ 14513929377491886653u, 1410646149696279084u, 587092196850797612u, 115318278760358235u], + [ 2226851524999454362u, 7717102471110805679u, 7187441550995571734u, 117576074943260147u], + [ 5527526061344932763u, 2347100676188369132u, 16976241418824030445u, 119878076118278875u], + [ 6088479778147221611u, 17669593130014777580u, 10991124207197663546u, 122225147767136307u], + [ 11107734086759692041u, 3391795220306863431u, 17233960908859089158u, 124618172316667879u], + [ 7913172514655155198u, 17726879005381242552u, 641069866244011540u, 127058049470587962u], + [ 12596991768458713949u, 15714785522479904446u, 6035972567136116512u, 129545696547750811u], + [ 16901996933781815980u, 4275085211437148707u, 14091642539965169063u, 132082048827034281u], + [ 7524574627987869240u, 15661204384239316051u, 2444526454225712267u, 134668059898975949u], + [ 8199251625090479942u, 6803282222165044067u, 16064817666437851504u, 137304702024293857u], + [ 4453256673338111920u, 15269922543084434181u, 3139961729834750852u, 139992966499426682u], + [ 15841763546372731299u, 3013174075437671812u, 4383755396295695606u, 142733864029230733u], + [ 9771896230907310329u, 4900659362437687569u, 12386126719044266361u, 72764212553486967u], + [ 9420455527449565190u, 1859606122611023693u, 6555040298902684281u, 74188850200884818u], + [ 5146105983135678095u, 2287300449992174951u, 4325371679080264751u, 75641380576797959u], + [ 11019359372592553360u, 8422686425957443718u, 7175176077944048210u, 77122349788024458u], + [ 11005742969399620716u, 4132174559240043701u, 9372258443096612118u, 78632314633490790u], + [ 8887589641394725840u, 8029899502466543662u, 14582206497241572853u, 80171842813591127u], + [ 360247523705545899u, 12568341805293354211u, 14653258284762517866u, 81741513143625247u], + [ 12314272731984275834u, 4740745023227177044u, 6141631472368337539u, 83341915771415304u], + [ 441052047733984759u, 7940090120939869826u, 11750200619921094248u, 84973652399183278u], + [ 3436657868127012749u, 9187006432149937667u, 16389726097323041290u, 86637336509772529u], + [ 13490220260784534044u, 15339072891382896702u, 8846102360835316895u, 88333593597298497u], + [ 4125672032094859833u, 158347675704003277u, 10592598512749774447u, 90063061402315272u], + [ 12189928252974395775u, 2386931199439295891u, 7009030566469913276u, 91826390151586454u], + [ 9256479608339282969u, 2844900158963599229u, 11148388908923225596u, 93624242802550437u], + [ 11584393507658707408u, 2863659090805147914u, 9873421561981063551u, 95457295292572042u], + [ 13984297296943171390u, 1931468383973130608u, 12905719743235082319u, 97326236793074198u], + [ 5837045222254987499u, 10213498696735864176u, 14893951506257020749u, 99231769968645227u], +]; + +// Unfortunately, the results are sometimes off by one or two. We use an additional +// lookup table to store those cases and adjust the result. +package static immutable ulong[156] POW5_ERRORS = [ + 0x0000000000000000u, 0x0000000000000000u, 0x0000000000000000u, 0x9555596400000000u, + 0x65a6569525565555u, 0x4415551445449655u, 0x5105015504144541u, 0x65a69969a6965964u, + 0x5054955969959656u, 0x5105154515554145u, 0x4055511051591555u, 0x5500514455550115u, + 0x0041140014145515u, 0x1005440545511051u, 0x0014405450411004u, 0x0414440010500000u, + 0x0044000440010040u, 0x5551155000004001u, 0x4554555454544114u, 0x5150045544005441u, + 0x0001111400054501u, 0x6550955555554554u, 0x1504159645559559u, 0x4105055141454545u, + 0x1411541410405454u, 0x0415555044545555u, 0x0014154115405550u, 0x1540055040411445u, + 0x0000000500000000u, 0x5644000000000000u, 0x1155555591596555u, 0x0410440054569565u, + 0x5145100010010005u, 0x0555041405500150u, 0x4141450455140450u, 0x0000000144000140u, + 0x5114004001105410u, 0x4444100404005504u, 0x0414014410001015u, 0x5145055155555015u, + 0x0141041444445540u, 0x0000100451541414u, 0x4105041104155550u, 0x0500501150451145u, + 0x1001050000004114u, 0x5551504400141045u, 0x5110545410151454u, 0x0100001400004040u, + 0x5040010111040000u, 0x0140000150541100u, 0x4400140400104110u, 0x5011014405545004u, + 0x0000000044155440u, 0x0000000010000000u, 0x1100401444440001u, 0x0040401010055111u, + 0x5155155551405454u, 0x0444440015514411u, 0x0054505054014101u, 0x0451015441115511u, + 0x1541411401140551u, 0x4155104514445110u, 0x4141145450145515u, 0x5451445055155050u, + 0x4400515554110054u, 0x5111145104501151u, 0x565a655455500501u, 0x5565555555525955u, + 0x0550511500405695u, 0x4415504051054544u, 0x6555595965555554u, 0x0100915915555655u, + 0x5540001510001001u, 0x5450051414000544u, 0x1405010555555551u, 0x5555515555644155u, + 0x5555055595496555u, 0x5451045004415000u, 0x5450510144040144u, 0x5554155555556455u, + 0x5051555495415555u, 0x5555554555555545u, 0x0000000010005455u, 0x4000005000040000u, + 0x5565555555555954u, 0x5554559555555505u, 0x9645545495552555u, 0x4000400055955564u, + 0x0040000000000001u, 0x4004100100000000u, 0x5540040440000411u, 0x4565555955545644u, + 0x1140659549651556u, 0x0100000410010000u, 0x5555515400004001u, 0x5955545555155255u, + 0x5151055545505556u, 0x5051454510554515u, 0x0501500050415554u, 0x5044154005441005u, + 0x1455445450550455u, 0x0010144055144545u, 0x0000401100000004u, 0x1050145050000010u, + 0x0415004554011540u, 0x1000510100151150u, 0x0100040400001144u, 0x0000000000000000u, + 0x0550004400000100u, 0x0151145041451151u, 0x0000400400005450u, 0x0000100044010004u, + 0x0100054100050040u, 0x0504400005410010u, 0x4011410445500105u, 0x0000404000144411u, + 0x0101504404500000u, 0x0000005044400400u, 0x0000000014000100u, 0x0404440414000000u, + 0x5554100410000140u, 0x4555455544505555u, 0x5454105055455455u, 0x0115454155454015u, + 0x4404110000045100u, 0x4400001100101501u, 0x6596955956966a94u, 0x0040655955665965u, + 0x5554144400100155u, 0xa549495401011041u, 0x5596555565955555u, 0x5569965959549555u, + 0x969565a655555456u, 0x0000001000000000u, 0x0000000040000140u, 0x0000040100000000u, + 0x1415454400000000u, 0x5410415411454114u, 0x0400040104000154u, 0x0504045000000411u, + 0x0000001000000010u, 0x5554000000001040u, 0x5549155551556595u, 0x1455541055515555u, + 0x0510555454554541u, 0x9555555555540455u, 0x6455456555556465u, 0x4524565555654514u, + 0x5554655255559545u, 0x9555455441155556u, 0x0000000051515555u, 0x0010005040000550u, + 0x5044044040000000u, 0x1045040440010500u, 0x0000400000040000u, 0x0000000000000000u, +]; + +package static immutable ulong[4][89] GENERIC_POW5_INV_SPLIT = [ + [ 0u, 0u, 0u, 144115188075855872u ], + [ 1573859546583440065u, 2691002611772552616u, 6763753280790178510u, 141347765182270746u ], + [ 12960290449513840412u, 12345512957918226762u, 18057899791198622765u, 138633484706040742u ], + [ 7615871757716765416u, 9507132263365501332u, 4879801712092008245u, 135971326161092377u ], + [ 7869961150745287587u, 5804035291554591636u, 8883897266325833928u, 133360288657597085u ], + [ 2942118023529634767u, 15128191429820565086u, 10638459445243230718u, 130799390525667397u ], + [ 14188759758411913794u, 5362791266439207815u, 8068821289119264054u, 128287668946279217u ], + [ 7183196927902545212u, 1952291723540117099u, 12075928209936341512u, 125824179589281448u ], + [ 5672588001402349748u, 17892323620748423487u, 9874578446960390364u, 123407996258356868u ], + [ 4442590541217566325u, 4558254706293456445u, 10343828952663182727u, 121038210542800766u ], + [ 3005560928406962566u, 2082271027139057888u, 13961184524927245081u, 118713931475986426u ], + [ 13299058168408384786u, 17834349496131278595u, 9029906103900731664u, 116434285200389047u ], + [ 5414878118283973035u, 13079825470227392078u, 17897304791683760280u, 114198414639042157u ], + [ 14609755883382484834u, 14991702445765844156u, 3269802549772755411u, 112005479173303009u ], + [ 15967774957605076027u, 2511532636717499923u, 16221038267832563171u, 109854654326805788u ], + [ 9269330061621627145u, 3332501053426257392u, 16223281189403734630u, 107745131455483836u ], + [ 16739559299223642282u, 1873986623300664530u, 6546709159471442872u, 105676117443544318u ], + [ 17116435360051202055u, 1359075105581853924u, 2038341371621886470u, 103646834405281051u ], + [ 17144715798009627550u, 3201623802661132408u, 9757551605154622431u, 101656519392613377u ], + [ 17580479792687825857u, 6546633380567327312u, 15099972427870912398u, 99704424108241124u ], + [ 9726477118325522902u, 14578369026754005435u, 11728055595254428803u, 97789814624307808u ], + [ 134593949518343635u, 5715151379816901985u, 1660163707976377376u, 95911971106466306u ], + [ 5515914027713859358u, 7124354893273815720u, 5548463282858794077u, 94070187543243255u ], + [ 6188403395862945512u, 5681264392632320838u, 15417410852121406654u, 92263771480600430u ], + [ 15908890877468271457u, 10398888261125597540u, 4817794962769172309u, 90492043761593298u ], + [ 1413077535082201005u, 12675058125384151580u, 7731426132303759597u, 88754338271028867u ], + [ 1486733163972670293u, 11369385300195092554u, 11610016711694864110u, 87050001685026843u ], + [ 8788596583757589684u, 3978580923851924802u, 9255162428306775812u, 85378393225389919u ], + [ 7203518319660962120u, 15044736224407683725u, 2488132019818199792u, 83738884418690858u ], + [ 4004175967662388707u, 18236988667757575407u, 15613100370957482671u, 82130858859985791u ], + [ 18371903370586036463u, 53497579022921640u, 16465963977267203307u, 80553711981064899u ], + [ 10170778323887491315u, 1999668801648976001u, 10209763593579456445u, 79006850823153334u ], + [ 17108131712433974546u, 16825784443029944237u, 2078700786753338945u, 77489693813976938u ], + [ 17221789422665858532u, 12145427517550446164u, 5391414622238668005u, 76001670549108934u ], + [ 4859588996898795878u, 1715798948121313204u, 3950858167455137171u, 74542221577515387u ], + [ 13513469241795711526u, 631367850494860526u, 10517278915021816160u, 73110798191218799u ], + [ 11757513142672073111u, 2581974932255022228u, 17498959383193606459u, 143413724438001539u ], + [ 14524355192525042817u, 5640643347559376447u, 1309659274756813016u, 140659771648132296u ], + [ 2765095348461978538u, 11021111021896007722u, 3224303603779962366u, 137958702611185230u ], + [ 12373410389187981037u, 13679193545685856195u, 11644609038462631561u, 135309501808182158u ], + [ 12813176257562780151u, 3754199046160268020u, 9954691079802960722u, 132711173221007413u ], + [ 17557452279667723458u, 3237799193992485824u, 17893947919029030695u, 130162739957935629u ], + [ 14634200999559435155u, 4123869946105211004u, 6955301747350769239u, 127663243886350468u ], + [ 2185352760627740240u, 2864813346878886844u, 13049218671329690184u, 125211745272516185u ], + [ 6143438674322183002u, 10464733336980678750u, 6982925169933978309u, 122807322428266620u ], + [ 1099509117817174576u, 10202656147550524081u, 754997032816608484u, 120449071364478757u ], + [ 2410631293559367023u, 17407273750261453804u, 15307291918933463037u, 118136105451200587u ], + [ 12224968375134586697u, 1664436604907828062u, 11506086230137787358u, 115867555084305488u ], + [ 3495926216898000888u, 18392536965197424288u, 10992889188570643156u, 113642567358547782u ], + [ 8744506286256259680u, 3966568369496879937u, 18342264969761820037u, 111460305746896569u ], + [ 7689600520560455039u, 5254331190877624630u, 9628558080573245556u, 109319949786027263u ], + [ 11862637625618819436u, 3456120362318976488u, 14690471063106001082u, 107220694767852583u ], + [ 5697330450030126444u, 12424082405392918899u, 358204170751754904u, 105161751436977040u ], + [ 11257457505097373622u, 15373192700214208870u, 671619062372033814u, 103142345693961148u ], + [ 16850355018477166700u, 1913910419361963966u, 4550257919755970531u, 101161718304283822u ], + [ 9670835567561997011u, 10584031339132130638u, 3060560222974851757u, 99219124612893520u ], + [ 7698686577353054710u, 11689292838639130817u, 11806331021588878241u, 97313834264240819u ], + [ 12233569599615692137u, 3347791226108469959u, 10333904326094451110u, 95445130927687169u ], + [ 13049400362825383933u, 17142621313007799680u, 3790542585289224168u, 93612312028186576u ], + [ 12430457242474442072u, 5625077542189557960u, 14765055286236672238u, 91814688482138969u ], + [ 4759444137752473128u, 2230562561567025078u, 4954443037339580076u, 90051584438315940u ], + [ 7246913525170274758u, 8910297835195760709u, 4015904029508858381u, 88322337023761438u ], + [ 12854430245836432067u, 8135139748065431455u, 11548083631386317976u, 86626296094571907u ], + [ 4848827254502687803u, 4789491250196085625u, 3988192420450664125u, 84962823991462151u ], + [ 7435538409611286684u, 904061756819742353u, 14598026519493048444u, 83331295300025028u ], + [ 11042616160352530997u, 8948390828345326218u, 10052651191118271927u, 81731096615594853u ], + [ 11059348291563778943u, 11696515766184685544u, 3783210511290897367u, 80161626312626082u ], + [ 7020010856491885826u, 5025093219346041680u, 8960210401638911765u, 78622294318500592u ], + [ 17732844474490699984u, 7820866704994446502u, 6088373186798844243u, 77112521891678506u ], + [ 688278527545590501u, 3045610706602776618u, 8684243536999567610u, 75631741404109150u ], + [ 2734573255120657297u, 3903146411440697663u, 9470794821691856713u, 74179396127820347u ], + [ 15996457521023071259u, 4776627823451271680u, 12394856457265744744u, 72754940025605801u ], + [ 13492065758834518331u, 7390517611012222399u, 1630485387832860230u, 142715675091463768u ], + [ 13665021627282055864u, 9897834675523659302u, 17907668136755296849u, 139975126841173266u ], + [ 9603773719399446181u, 10771916301484339398u, 10672699855989487527u, 137287204938390542u ], + [ 3630218541553511265u, 8139010004241080614u, 2876479648932814543u, 134650898807055963u ], + [ 8318835909686377084u, 9525369258927993371u, 2796120270400437057u, 132065217277054270u ], + [ 11190003059043290163u, 12424345635599592110u, 12539346395388933763u, 129529188211565064u ], + [ 8701968833973242276u, 820569587086330727u, 2315591597351480110u, 127041858141569228u ], + [ 5115113890115690487u, 16906305245394587826u, 9899749468931071388u, 124602291907373862u ], + [ 15543535488939245974u, 10945189844466391399u, 3553863472349432246u, 122209572307020975u ], + [ 7709257252608325038u, 1191832167690640880u, 15077137020234258537u, 119862799751447719u ], + [ 7541333244210021737u, 9790054727902174575u, 5160944773155322014u, 117561091926268545u ], + [ 12297384708782857832u, 1281328873123467374u, 4827925254630475769u, 115303583460052092u ], + [ 13243237906232367265u, 15873887428139547641u, 3607993172301799599u, 113089425598968120u ], + [ 11384616453739611114u, 15184114243769211033u, 13148448124803481057u, 110917785887682141u ], + [ 17727970963596660683u, 1196965221832671990u, 14537830463956404138u, 108787847856377790u ], + [ 17241367586707330931u, 8880584684128262874u, 11173506540726547818u, 106698810713789254u ], + [ 7184427196661305643u, 14332510582433188173u, 14230167953789677901u, 104649889046128358u ], +]; + +package static immutable ulong[154] POW5_INV_ERRORS = [ + 0x1144155514145504u, 0x0000541555401141u, 0x0000000000000000u, 0x0154454000000000u, + 0x4114105515544440u, 0x0001001111500415u, 0x4041411410011000u, 0x5550114515155014u, + 0x1404100041554551u, 0x0515000450404410u, 0x5054544401140004u, 0x5155501005555105u, + 0x1144141000105515u, 0x0541500000500000u, 0x1104105540444140u, 0x4000015055514110u, + 0x0054010450004005u, 0x4155515404100005u, 0x5155145045155555u, 0x1511555515440558u, + 0x5558544555515555u, 0x0000000000000010u, 0x5004000000000050u, 0x1415510100000010u, + 0x4545555444514500u, 0x5155151555555551u, 0x1441540144044554u, 0x5150104045544400u, + 0x5450545401444040u, 0x5554455045501400u, 0x4655155555555145u, 0x1000010055455055u, + 0x1000004000055004u, 0x4455405104000005u, 0x4500114504150545u, 0x0000000014000000u, + 0x5450000000000000u, 0x5514551511445555u, 0x4111501040555451u, 0x4515445500054444u, + 0x5101500104100441u, 0x1545115155545055u, 0x0000000000000000u, 0x1554000000100000u, + 0x5555545595551555u, 0x5555051851455955u, 0x5555555555555559u, 0x0000400011001555u, + 0x0000004400040000u, 0x5455511555554554u, 0x5614555544115445u, 0x6455156145555155u, + 0x5455855455415455u, 0x5515555144555545u, 0x0114400000145155u, 0x0000051000450511u, + 0x4455154554445100u, 0x4554150141544455u, 0x65955555559a5965u, 0x5555555854559559u, + 0x9569654559616595u, 0x1040044040005565u, 0x1010010500011044u, 0x1554015545154540u, + 0x4440555401545441u, 0x1014441450550105u, 0x4545400410504145u, 0x5015111541040151u, + 0x5145051154000410u, 0x1040001044545044u, 0x4001400000151410u, 0x0540000044040000u, + 0x0510555454411544u, 0x0400054054141550u, 0x1001041145001100u, 0x0000000140000000u, + 0x0000000014100000u, 0x1544005454000140u, 0x4050055505445145u, 0x0011511104504155u, + 0x5505544415045055u, 0x1155154445515554u, 0x0000000000004555u, 0x0000000000000000u, + 0x5101010510400004u, 0x1514045044440400u, 0x5515519555515555u, 0x4554545441555545u, + 0x1551055955551515u, 0x0150000011505515u, 0x0044005040400000u, 0x0004001004010050u, + 0x0000051004450414u, 0x0114001101001144u, 0x0401000001000001u, 0x4500010001000401u, + 0x0004100000005000u, 0x0105000441101100u, 0x0455455550454540u, 0x5404050144105505u, + 0x4101510540555455u, 0x1055541411451555u, 0x5451445110115505u, 0x1154110010101545u, + 0x1145140450054055u, 0x5555565415551554u, 0x1550559555555555u, 0x5555541545045141u, + 0x4555455450500100u, 0x5510454545554555u, 0x1510140115045455u, 0x1001050040111510u, + 0x5555454555555504u, 0x9954155545515554u, 0x6596656555555555u, 0x0140410051555559u, + 0x0011104010001544u, 0x965669659a680501u, 0x5655a55955556955u, 0x4015111014404514u, + 0x1414155554505145u, 0x0540040011051404u, 0x1010000000015005u, 0x0010054050004410u, + 0x5041104014000100u, 0x4440010500100001u, 0x1155510504545554u, 0x0450151545115541u, + 0x4000100400110440u, 0x1004440010514440u, 0x0000115050450000u, 0x0545404455541500u, + 0x1051051555505101u, 0x5505144554544144u, 0x4550545555515550u, 0x0015400450045445u, + 0x4514155400554415u, 0x4555055051050151u, 0x1511441450001014u, 0x4544554510404414u, + 0x4115115545545450u, 0x5500541555551555u, 0x5550010544155015u, 0x0144414045545500u, + 0x4154050001050150u, 0x5550511111000145u, 0x1114504055000151u, 0x5104041101451040u, + 0x0010501401051441u, 0x0010501450504401u, 0x4554585440044444u, 0x5155555951450455u, + 0x0040000400105555u, 0x0000000000000001u, +]; diff --git a/source/mir/bignum/low_level_view.d b/source/mir/bignum/low_level_view.d new file mode 100644 index 00000000..4a7fd635 --- /dev/null +++ b/source/mir/bignum/low_level_view.d @@ -0,0 +1,2443 @@ +/++ +Low-level betterC utilities for big integer arithmetic libraries. + +The module provides $(REF BigUIntView), and $(LREF BigIntView), $(REF DecimalView). + +Note: + The module doesn't provide full arithmetic API for now. ++/ +module mir.bignum.low_level_view; + +import mir.checkedint; +import std.traits; + +private alias cop(string op : "-") = subu; +private alias cop(string op : "+") = addu; +private enum inverseSign(string op) = op == "+" ? "-" : "+"; + +package immutable hexStringErrorMsg = "Incorrect hex string for BigUIntView.fromHexString"; +package immutable binaryStringErrorMsg = "Incorrect binary string for BigUIntView.fromBinaryString"; +version (D_Exceptions) +{ + package immutable hexStringException = new Exception(hexStringErrorMsg); + package immutable binaryStringException = new Exception(binaryStringErrorMsg); +} + +package template MaxWordPow10(T) +{ + static if (is(T == ubyte)) + enum MaxWordPow10 = 2; + else + static if (is(T == ushort)) + enum MaxWordPow10 = 4; + else + static if (is(T == uint)) + enum MaxWordPow10 = 9; + else + static if (is(T == ulong)) + enum MaxWordPow10 = 19; + else + static assert(0); +} + +/++ +Fast integer computation of `ceil(log10(exp2(e)))` with 64-bit mantissa precision. +The result is guaranted to be greater then `log10(exp2(e))`, which is irrational number. ++/ +T ceilLog10Exp2(T)(const T e) + @safe pure nothrow @nogc + if (is(T == ubyte) || is(T == ushort) || is(T == uint) || is(T == ulong)) +{ + import mir.utility: extMul; + auto result = extMul(0x9a209a84fbcff799UL, e); + return cast(T) ((result.high >> 1) + ((result.low != 0) | (result.high & 1))); +} + +/// +version(mir_bignum_test_llv) +@safe pure nothrow @nogc unittest +{ + assert(ceilLog10Exp2(ubyte(10)) == 4); // ubyte + assert(ceilLog10Exp2(10U) == 4); // uint + assert(ceilLog10Exp2(10UL) == 4); // ulong +} + +/++ +Arbitrary length unsigned integer view. ++/ +struct BigUIntView(W) + if (__traits(isUnsigned, W)) +{ + import mir.bignum.fp: Fp, half; + import mir.bignum.fixed: UInt; + + /++ + A group of coefficients for a radix `W.max + 1`. + + The order corresponds to endianness. + +/ + W[] coefficients; + +@safe: + + /++ + Retrurns: signed integer view using the same data payload + +/ + size_t length()() @safe pure nothrow @nogc const @property + { + return coefficients.length; + } + + /++ + Retrurns: signed integer view using the same data payload + +/ + BigIntView!W signed()(bool sign = false) @safe pure nothrow @nogc return scope @property + { + return typeof(return)(this, sign); + } + + static if (W.sizeof >= size_t.sizeof) + /// + T opCast(T)() const scope + if (isFloatingPoint!T && isMutable!T) + { + import mir.bignum.internal.dec2float: binaryTo; + return normalized.coefficients.binaryTo!T; + } + + static if (W.sizeof >= size_t.sizeof) + /// + @safe + T opCast(T : Fp!coefficientSize, uint coefficientSize)() const scope + { + static if (isMutable!W) + { + return lightConst.opCast!T; + } + else + static if (W.sizeof > size_t.sizeof) + { + return lightConst.opCast!(BigUIntView!(const size_t)).opCast!T; + } + else + { + import mir.bignum.internal.dec2float: binaryToFp; + auto coefficients = normalized.coefficients; + return coefficients.length + ? coefficients.binaryToFp!coefficientSize + : Fp!coefficientSize.init; + } + } + + /// + T opCast(T, bool nonZero = false)() const scope + if (isIntegral!T && isUnsigned!T && isMutable!T) + { + auto work = lightConst; + static if (!nonZero) + { + if (coefficients.length == 0) + { + return 0; + } + } + static if (T.sizeof <= W.sizeof) + { + return cast(T) work.coefficients[0]; + } + else + { + T ret; + do + { + ret <<= W.sizeof * 8; + ret |= work.coefficients[$ - 1]; + work.popMostSignificant; + } + while(work.coefficients.length); + return ret; + } + } + + /// + pure nothrow @nogc + BigUIntView!V opCast(T : BigUIntView!V, V)() return scope + if (V.sizeof <= W.sizeof) + { + return typeof(return)(cast(V[])this.coefficients); + } + + pure nothrow @nogc + BigUIntView!V opCast(T : BigUIntView!V, V)() const return scope + if (V.sizeof <= W.sizeof) + { + return typeof(return)(cast(V[])this.coefficients); + } + + /// + BigUIntView!(const W) lightConst()() return scope + const @safe pure nothrow @nogc @property + { + return typeof(return)(coefficients); + } + ///ditto + alias lightConst this; + + /++ + +/ + sizediff_t opCmp(scope BigUIntView!(const W) rhs) + const @safe pure nothrow @nogc scope + { + import mir.algorithm.iteration: cmp; + auto l = this.lightConst.normalized; + auto r = rhs.lightConst.normalized; + if (sizediff_t d = l.coefficients.length - r.coefficients.length) + return d; + return cmp(l.mostSignificantFirst, r.mostSignificantFirst); + } + + /// + bool opEquals(BigUIntView!(const W) rhs) + const @safe pure nothrow @nogc scope + { + return this.coefficients == rhs.coefficients; + } + + /++ + +/ + void popMostSignificant() scope + { + coefficients = coefficients[0 .. $ - 1]; + } + + /++ + +/ + void popLeastSignificant() scope + { + coefficients = coefficients[1 .. $]; + } + + /++ + +/ + BigUIntView topMostSignificantPart(size_t length) + in (length <= coefficients.length) + { + return BigUIntView(coefficients[$ - length .. $]); + } + + /++ + +/ + BigUIntView topLeastSignificantPart(size_t length) + in (length <= coefficients.length) + { + return BigUIntView(coefficients[0 .. length]); + } + + /++ + Shifts left using at most `size_t.sizeof * 8 - 1` bits + +/ + void smallLeftShiftInPlace()(uint shift) scope + { + assert(shift < W.sizeof * 8); + if (shift == 0) + return; + auto csh = W.sizeof * 8 - shift; + auto d = coefficients[]; + assert(d.length); + foreach_reverse (i; 1 .. d.length) + d[i] = (d[i] << shift) | (d[i - 1] >>> csh); + d[0] <<= shift; + } + + /++ + Shifts right using at most `size_t.sizeof * 8 - 1` bits + +/ + void smallRightShiftInPlace()(uint shift) + { + assert(shift < W.sizeof * 8); + if (shift == 0) + return; + auto csh = W.sizeof * 8 - shift; + auto d = coefficients[]; + assert(d.length); + foreach (i; 0 .. d.length - 1) + d[i] = (d[i] >>> shift) | (d[i + 1] << csh); + d[$ - 1] >>>= shift; + } + + /++ + +/ + static BigUIntView fromHexString(C, bool allowUnderscores = false)(scope const(C)[] str) + @trusted pure + if (isSomeChar!C) + { + auto length = str.length / (W.sizeof * 2) + (str.length % (W.sizeof * 2) != 0); + auto data = new Unqual!W[length]; + auto view = BigUIntView!(Unqual!W)(data); + if (view.fromHexStringImpl!(C, allowUnderscores)(str)) + return BigUIntView(cast(W[])view.coefficients); + version(D_Exceptions) + { import mir.exception : toMutable; throw hexStringException.toMutable; } + else + assert(0, hexStringErrorMsg); + } + + static if (isMutable!W) + /++ + +/ + bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) + @safe pure @nogc nothrow scope + if (isSomeChar!C) + { + pragma(inline, false); + import mir.utility: _expect; + static if (allowUnderscores) { + if (_expect(str.length == 0, false)) // can't tell how big the coeff array needs to be, rely on a runtime check + return false; + } else { + if (_expect(str.length == 0 || str.length > coefficients.length * W.sizeof * 2, false)) + return false; + } + + coefficients[0] = 0; + auto work = topLeastSignificantPart(1); + W current; + size_t i, j; + static if (allowUnderscores) bool recentUnderscore; + + do + { + ubyte c; + switch(str[$ - ++i]) + { + case '0': c = 0x0; break; + case '1': c = 0x1; break; + case '2': c = 0x2; break; + case '3': c = 0x3; break; + case '4': c = 0x4; break; + case '5': c = 0x5; break; + case '6': c = 0x6; break; + case '7': c = 0x7; break; + case '8': c = 0x8; break; + case '9': c = 0x9; break; + case 'A': + case 'a': c = 0xA; break; + case 'B': + case 'b': c = 0xB; break; + case 'C': + case 'c': c = 0xC; break; + case 'D': + case 'd': c = 0xD; break; + case 'E': + case 'e': c = 0xE; break; + case 'F': + case 'f': c = 0xF; break; + static if (allowUnderscores) + { + case '_': + if (recentUnderscore) return false; + recentUnderscore = true; + continue; + } + default: return false; + } + ++j; + static if (allowUnderscores) recentUnderscore = false; + // how far do we need to shift to get to the top 4 bits + enum s = W.sizeof * 8 - 4; + // shift number to the top most 4 bits + W cc = cast(W)(W(c) << s); + // shift unsigned right 4 bits + current >>>= 4; + // add number to top most 4 bits of current var + current |= cc; + if (j % (W.sizeof * 2) == 0) // is this packed var full? + { + work.coefficients[$ - 1] = current; + current = 0; + if (_expect(work.coefficients.length < coefficients.length, true)) + { + work = topLeastSignificantPart(work.coefficients.length + 1); + } + else if (i < str.length) // if we've run out of coefficients before reaching the end of the string, error + { + return false; + } + } + } + while(i < str.length); + + static if (allowUnderscores) + { + // check for a underscore at the beginning or the end + if (recentUnderscore || str[$ - 1] == '_') return false; + } + + if (current) + { + current >>>= 4 * (W.sizeof * 2 - j % (W.sizeof * 2)); + work.coefficients[$ - 1] = current; + } + else + { + work.coefficients = work.coefficients[0 .. (j / (W.sizeof * 2) + (j % (W.sizeof * 2) != 0))]; + work = work.normalized; + } + + ()@trusted {this = work;}(); + return true; + } + + /++ + +/ + static BigUIntView fromBinaryString(C, bool allowUnderscores = false)(scope const(C)[] str) + @trusted pure + if (isSomeChar!C) + { + auto length = str.length / (W.sizeof * 8) + (str.length % (W.sizeof * 8) != 0); + auto data = new Unqual!W[length]; + auto view = BigUIntView!(Unqual!W)(data); + if (view.fromBinaryStringImpl!(C, allowUnderscores)(str)) + return BigUIntView(cast(W[])view.coefficients); + version(D_Exceptions) + { import mir.exception : toMutable; throw binaryStringException.toMutable; } + else + assert(0, binaryStringErrorMsg); + } + + static if (isMutable!W) + /++ + +/ + bool fromBinaryStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) + @safe pure @nogc nothrow scope + if (isSomeChar!C) + { + pragma(inline, false); + import mir.utility: _expect; + static if (allowUnderscores) { + if (_expect(str.length == 0, false)) // can't tell how big the coeff array needs to be, rely on a runtime check + return false; + } else { + if (_expect(str.length == 0 || str.length > coefficients.length * W.sizeof * 8, false)) + return false; + } + + coefficients[0] = 0; + auto work = topLeastSignificantPart(1); + W current; + size_t i, j; + static if (allowUnderscores) bool recentUnderscore; + + do + { + ubyte c; + switch(str[$ - ++i]) + { + case '0': c = 0x0; break; + case '1': c = 0x1; break; + static if (allowUnderscores) + { + case '_': + if (recentUnderscore) return false; + recentUnderscore = true; + continue; + } + default: return false; + } + ++j; + static if (allowUnderscores) recentUnderscore = false; + // how far do we need to shift to get to the top bit? + enum s = W.sizeof * 8 - 1; + // shift number to the top most bit + W cc = cast(W)(W(c) << s); + // shift unsigned right 1 bit + current >>>= 1; + // add number to top most bit of current var + current |= cc; + if (j % (W.sizeof * 8) == 0) // is this packed var full? + { + work.coefficients[$ - 1] = current; + current = 0; + if (_expect(work.coefficients.length < coefficients.length, true)) + { + work = topLeastSignificantPart(work.coefficients.length + 1); + } + else if (i < str.length) // if we've run out of coefficients before reaching the end of the string, error + { + return false; + } + } + } + while(i < str.length); + + static if (allowUnderscores) + { + // check for a underscore at the beginning or the end + if (recentUnderscore || str[$ - 1] == '_') return false; + } + + if (current) + { + current >>>= (W.sizeof * 8 - j % (W.sizeof * 8)); + work.coefficients[$ - 1] = current; + } + else + { + work.coefficients = work.coefficients[0 .. (j / (W.sizeof * 8) + (j % (W.sizeof * 8) != 0))]; + work = work.normalized; + } + + ()@trusted {this = work;}(); + return true; + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Returns: false in case of overflow or incorrect string. + Precondition: non-empty coefficients + Note: doesn't support signs. + +/ + bool fromStringImpl(C)(scope const(C)[] str) + scope @trusted pure @nogc nothrow + if (isSomeChar!C) + { + import mir.utility: _expect; + + assert(coefficients.length); + + if (_expect(str.length == 0, false)) + return false; + + coefficients[0] = 0; + uint d = str[0] - '0'; + str = str[1 .. $]; + + W v; + W t = 1; + + if (d == 0) + { + if (str.length == 0) + { + coefficients = null; + return true; + } + return false; + } + else + if (d >= 10) + return false; + + size_t len = 1; + goto S; + + for(;;) + { + enum mp10 = W(10) ^^ MaxWordPow10!W; + d = str[0] - '0'; + str = str[1 .. $]; + if (_expect(d > 10, false)) + break; + v *= 10; + S: + t *= 10; + v += d; + + if (_expect(t == mp10 || str.length == 0, false)) + { + L: + if (auto overflow = topLeastSignificantPart(len).opOpAssign!"*"(t, v)) + { + if (_expect(len < coefficients.length, true)) + { + coefficients[len++] = overflow; + } + else + { + return false; + } + } + v = 0; + t = 1; + if (str.length == 0) + { + this = topLeastSignificantPart(len); + return true; + } + } + } + return false; + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Performs `bool overflow = big +(-)= big` operatrion. + Params: + rhs = value to add with non-empty coefficients + overflow = (overflow) initial iteration overflow + Precondition: non-empty coefficients length of greater or equal to the `rhs` coefficients length. + Returns: + true in case of unsigned overflow + +/ + bool opOpAssign(string op)(scope BigUIntView!(const W) rhs, bool overflow = false) + @safe pure nothrow @nogc scope + if (op == "+" || op == "-") + { + assert(this.coefficients.length > 0); + assert(rhs.coefficients.length <= this.coefficients.length); + auto ls = this.coefficients; + auto rs = rhs.coefficients; + do + { + bool overflowM, overflowG; + ls[0] = ls[0].cop!op(rs[0], overflowM).cop!op(overflow, overflowG); + overflow = overflowG | overflowM; + ls = ls[1 .. $]; + rs = rs[1 .. $]; + } + while(rs.length); + if (overflow && ls.length) + return topMostSignificantPart(ls.length).opOpAssign!op(W(overflow)); + return overflow; + } + + static if (isMutable!W && W.sizeof >= 4) + /// ditto + bool opOpAssign(string op)(scope BigIntView!(const W) rhs, bool overflow = false) + @safe pure nothrow @nogc scope + if (op == "+" || op == "-") + { + return rhs.sign == false ? + opOpAssign!op(rhs.unsigned, overflow): + opOpAssign!(inverseSign!op)(rhs.unsigned, overflow); + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Performs `bool Overflow = big +(-)= scalar` operatrion. + Precondition: non-empty coefficients + Params: + rhs = value to add + Returns: + true in case of unsigned overflow + +/ + bool opOpAssign(string op, T)(const T rhs) + @safe pure nothrow @nogc scope + if ((op == "+" || op == "-") && is(T == W)) + { + assert(this.coefficients.length > 0); + auto ns = this.coefficients; + W additive = rhs; + do + { + bool overflow; + ns[0] = ns[0].cop!op(additive, overflow); + if (!overflow) + return overflow; + additive = overflow; + ns = ns[1 .. $]; + } + while (ns.length); + return true; + } + + static if (isMutable!W && W.sizeof >= 4) + /// ditto + bool opOpAssign(string op, T)(const T rhs) + @safe pure nothrow @nogc scope + if ((op == "+" || op == "-") && is(T == Signed!W)) + { + return rhs >= 0 ? + opOpAssign!op(cast(W)rhs): + opOpAssign!(inverseSign!op)(cast(W)(-rhs)); + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Performs `W overflow = (big += overflow) *= scalar` operatrion. + Precondition: non-empty coefficients + Params: + rhs = unsigned value to multiply by + overflow = initial overflow + Returns: + unsigned overflow value + +/ + W opOpAssign(string op : "*")(W rhs, W overflow = 0u) + @safe pure nothrow @nogc scope + { + auto ns = this.coefficients; + while (ns.length) + { + import mir.utility: extMul; + auto ext = ns[0].extMul(rhs); + bool overflowM; + ns[0] = ext.low.cop!"+"(overflow, overflowM); + overflow = ext.high + overflowM; + ns = ns[1 .. $]; + } + return overflow; + } + + static if (isMutable!W && W.sizeof == 4 || W.sizeof == 8) + /++ + Performs `uint remainder = (overflow$big) /= scalar` operatrion, where `$` denotes big-endian concatenation. + Precondition: non-empty coefficients, `overflow < rhs` + Params: + rhs = unsigned value to devide by + overflow = initial unsigned overflow + Returns: + unsigned remainder value (evaluated overflow) + +/ + uint opOpAssign(string op : "/")(uint rhs, uint overflow = 0) + @safe pure nothrow @nogc scope + { + assert(overflow < rhs); + assert(coefficients.length); + static if (W.sizeof == 4) + { + auto ns = this.mostSignificantFirst; + size_t i; + do + { + auto ext = (ulong(overflow) << 32) ^ ns[i]; + ns[i] = cast(uint)(ext / rhs); + overflow = ext % rhs; + } + while (++i < ns.length); + if (coefficients[$ - 1] == 0) + popMostSignificant; + return overflow; + } + else + { + auto work = opCast!(BigUIntView!uint); + if (work.coefficients[$ - 1] == 0) + work.popMostSignificant; + auto remainder = work.opOpAssign!op(rhs, overflow); + coefficients = coefficients[0 .. work.coefficients.length / 2 + work.coefficients.length % 2]; + return remainder; + } + } + + static if (isMutable!W && W.sizeof == size_t.sizeof) + /++ + Performs `W overflow = (big += overflow) *= scalar` operatrion. + Precondition: non-empty coefficients + Params: + rhs = unsigned fixed-length integer to multiply by + overflow = initial overflow + Returns: + unsigned fixed-length integer overflow value + +/ + UInt!size + opOpAssign(string op : "*", size_t size)(UInt!size rhs, UInt!size overflow = 0) + @safe pure nothrow @nogc scope + { + assert(coefficients.length); + auto ns = this.coefficients; + do + { + auto t = rhs; + auto overflowW = t.view *= ns[0]; + auto overflowM = t += overflow; + overflowW += overflowM; + ns[0] = cast(size_t) t; + static if (size > size_t.sizeof * 8) + overflow = t.toSize!(size - size_t.sizeof * 8, false).toSize!size; + BigUIntView!size_t(overflow.data).coefficients[$ - 1] = overflowW; + ns = ns[1 .. $]; + } + while (ns.length); + return overflow; + } + + /++ + Returns: the same intger view with inversed sign + +/ + BigIntView!W opUnary(string op : "-")() + { + return typeof(return)(this, true); + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + +/ + void bitwiseNotInPlace() scope + { + foreach (ref coefficient; this.coefficients) + coefficient = cast(W)~(0 + coefficient); + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Performs `number=-number` operatrion. + Precondition: non-empty coefficients + Returns: + true if 'number=-number=0' and false otherwise + +/ + bool twoComplementInPlace() scope + { + assert(coefficients.length); + bitwiseNotInPlace(); + return this.opOpAssign!"+"(W(1)); + } + + /++ + Returns: a slice of coefficients starting from the most significant. + +/ + auto mostSignificantFirst() + @safe pure nothrow @nogc @property + { + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: retro; + return coefficients.sliced.retro; + } + + /// + auto mostSignificantFirst() + const @safe pure nothrow @nogc @property + { + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: retro; + return coefficients.sliced.retro; + } + + /++ + Strips most significant zero coefficients. + +/ + BigUIntView normalized() return scope + { + auto number = this; + if (number.coefficients.length) do + { + if (number.coefficients[$ - 1]) + break; + number.coefficients = number.coefficients[0 .. $ - 1]; + } + while (number.coefficients.length); + return number; + } + + ///ditto + BigUIntView!(const W) normalized() const return scope + { + return lightConst.normalized; + } + + /++ + +/ + bool bt()(size_t position) scope + { + import mir.ndslice.topology: bitwise; + assert(position < coefficients.length * W.sizeof * 8); + return coefficients.bitwise[position]; + } + + /++ + +/ + size_t ctlz()() scope const @property + @safe pure nothrow @nogc + { + import mir.bitop: ctlz; + assert(coefficients.length); + auto d = mostSignificantFirst; + size_t ret; + do + { + if (auto c = d[0]) + { + ret += ctlz(c); + break; + } + ret += W.sizeof * 8; + d = d[1 .. $]; + } + while(d.length); + return ret; + } + + /++ + +/ + size_t cttz()() scope const @property + @safe pure nothrow @nogc + in (coefficients.length) + { + import mir.bitop: cttz; + auto d = coefficients[]; + size_t ret; + do + { + if (auto c = d[0]) + { + ret += cttz(c); + break; + } + ret += W.sizeof * 8; + d = d[1 .. $]; + } + while(d.length); + return ret; + } + + /// + BigIntView!W withSign()(bool sign) return scope + { + return typeof(return)(this, sign); + } + + /++ + Params: + value = (out) unsigned integer + Returns: true on success + +/ + bool get(U)(scope out U value) + @safe pure nothrow @nogc const + if (isUnsigned!U) + { + auto d = lightConst.mostSignificantFirst; + if (d.length == 0) + return false; + static if (U.sizeof > W.sizeof) + { + size_t i; + for(;;) + { + value |= d[0]; + d = d[1 .. $]; + if (d.length == 0) + return false; + i += cast(bool)value; + value <<= W.sizeof * 8; + import mir.utility: _expect; + if (_expect(i >= U.sizeof / W.sizeof, false)) + return true; + } + } + else + { + for(;;) + { + W f = d[0]; + d = d[1 .. $]; + if (d.length == 0) + { + value = cast(U)f; + static if (U.sizeof < W.sizeof) + { + if (value != f) + return true; + } + return false; + } + if (f) + return true; + } + } + } + + /++ + Returns: true if the integer and equals to `rhs`. + +/ + bool opEquals(ulong rhs) + @safe pure nothrow @nogc const scope + { + foreach (d; lightConst.coefficients) + { + static if (W.sizeof >= ulong.sizeof) + { + if (d != rhs) + return false; + rhs = 0; + } + else + { + if (d != (rhs & W.max)) + return false; + rhs >>>= W.sizeof * 8; + } + } + return rhs == 0; + } + + + static if (isMutable!W && W.sizeof >= 4) + /++ + Params: + str = string buffer, the tail paer + Precondition: mutable number with word size at least 4 bytes + Postconditoin: the number is destroyed + Returns: last N bytes used in the buffer + +/ + size_t toStringImpl(C)(scope C[] str) + @safe pure nothrow @nogc + if (isSomeChar!C && isMutable!C) + { + assert(str.length); + assert(str.length >= ceilLog10Exp2(coefficients.length * (W.sizeof * 8))); + + size_t i = str.length; + while(coefficients.length > 1) + { + uint rem = this /= 1_000_000_000; + foreach (_; 0 .. 9) + { + str[--i] = cast(char)(rem % 10 + '0'); + rem /= 10; + } + } + + W rem = coefficients.length == 1 ? coefficients[0] : W(0); + do + { + str[--i] = cast(char)(rem % 10 + '0'); + rem /= 10; + } + while(rem); + + return str.length - i; + } + +} + +/// +version(mir_bignum_test_llv) +@safe pure @nogc +unittest +{ + import mir.bignum.integer; + + auto a = BigInt!2("123456789098765432123456789098765432100"); + char[ceilLog10Exp2(a.data.length * (size_t.sizeof * 8))] buffer; + auto len = a.view.unsigned.toStringImpl(buffer); + assert(buffer[$ - len .. $] == "123456789098765432123456789098765432100"); +} + +/// +version(mir_bignum_test_llv) +@safe pure +unittest +{ + import mir.test; + auto view = BigUIntView!size_t.fromHexString!(char, true)("abcd_efab_cdef"); + (cast(ulong)view).should == 0xabcd_efab_cdef; +} + +/// +version(mir_bignum_test_llv) +@safe pure +unittest +{ + auto a = BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + a.smallLeftShiftInPlace(4); + assert(a == BigUIntView!size_t.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); + a.smallLeftShiftInPlace(0); + assert(a == BigUIntView!size_t.fromHexString("fbbfae3cd0aff2714a1de7022b0029d0")); +} + +/// +version(mir_bignum_test_llv) +@safe pure +unittest +{ + auto a = BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + a.smallRightShiftInPlace(4); + assert(a == BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029")); +} + + +/// +version(mir_bignum_test_llv) +@safe pure +unittest +{ + // Check that invalid underscores in hex literals throw an error. + void expectThrow(const(char)[] input) { + bool caught = false; + try { + auto view = BigUIntView!size_t.fromHexString!(char, true)(input); + } catch (Exception e) { + caught = true; + } + + assert(caught); + } + + expectThrow("abcd_efab_cef_"); + expectThrow("abcd__efab__cef"); + expectThrow("_abcd_efab_cdef"); + expectThrow("_abcd_efab_cdef_"); + expectThrow("_abcd_efab_cdef__"); + expectThrow("__abcd_efab_cdef"); + expectThrow("__abcd_efab_cdef_"); + expectThrow("__abcd_efab_cdef__"); + expectThrow("__abcd__efab_cdef__"); + expectThrow("__abcd__efab__cdef__"); +} + +/// +version(mir_bignum_test_llv) +@safe pure +unittest +{ + auto view = BigUIntView!size_t.fromBinaryString!(char, true)("1111_0000_0101"); + assert(cast(ulong)view == 0b1111_0000_0101); +} + +/// +version(mir_bignum_test_llv) +@safe pure +unittest +{ + // Check that invalid underscores in hex literals throw an error. + void expectThrow(const(char)[] input) { + bool caught = false; + try { + auto view = BigUIntView!size_t.fromBinaryString!(char, true)(input); + } catch (Exception e) { + caught = true; + } + + assert(caught); + } + + expectThrow("abcd"); + expectThrow("0101__1011__0111"); + expectThrow("_0101_1011_0111"); + expectThrow("_0101_1011_0111_"); + expectThrow("_0101_1011_0111__"); + expectThrow("__0101_1011_0111_"); + expectThrow("__0101_1011_0111__"); + expectThrow("__0101__1011_0111__"); + expectThrow("__1011__0111__1011__"); +} + +/// +version(mir_bignum_test_llv) +unittest +{ + auto a = cast(double) BigUIntView!size_t.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(a == 0xa.fbbfae3cd0bp+124); + assert(cast(double) BigUIntView!size_t.init == 0); + assert(cast(double) BigUIntView!size_t([0]) == 0); +} + +/// +version(mir_bignum_test_llv) +@safe pure +unittest +{ + import mir.bignum.fp: Fp; + import mir.bignum.fixed: UInt; + + auto fp = cast(Fp!128) BigUIntView!ulong.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(fp.exponent == 0); + assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); + + fp = cast(Fp!128) BigUIntView!size_t.fromHexString("ae3cd0aff2714a1de7022b0029d"); + assert(fp.exponent == -20); + assert(fp.coefficient == UInt!128.fromHexString("ae3cd0aff2714a1de7022b0029d00000")); + + fp = cast(Fp!128) BigUIntView!size_t.fromHexString("e7022b0029d"); + assert(fp.exponent == -84); + assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); + + fp = cast(Fp!128) BigUIntView!size_t.fromHexString("e7022b0029d"); + assert(fp.exponent == -84); + assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); + + fp = cast(Fp!128) BigUIntView!size_t.fromHexString("e7022b0029d"); + assert(fp.exponent == -84); + assert(fp.coefficient == UInt!128.fromHexString("e7022b0029d000000000000000000000")); + + fp = cast(Fp!128) BigUIntView!size_t.fromHexString("ffffffffffffffffffffffffffffffff1000000000000000"); + assert(fp.exponent == 64); + assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); + + fp = cast(Fp!128) BigUIntView!size_t.fromHexString("ffffffffffffffffffffffffffffffff8000000000000000"); + assert(fp.exponent == 65); + assert(fp.coefficient == UInt!128.fromHexString("80000000000000000000000000000000")); + + fp = cast(Fp!128) BigUIntView!size_t.fromHexString("fffffffffffffffffffffffffffffffe8000000000000000"); + assert(fp.exponent == 64); + assert(fp.coefficient == UInt!128.fromHexString("fffffffffffffffffffffffffffffffe")); + + fp = cast(Fp!128) BigUIntView!size_t.fromHexString("fffffffffffffffffffffffffffffffe8000000000000001"); + assert(fp.exponent == 64); + assert(fp.coefficient == UInt!128.fromHexString("ffffffffffffffffffffffffffffffff")); +} + +/// +version(mir_bignum_test_llv) +@safe pure +unittest +{ + auto view = BigUIntView!ulong.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(cast(ulong) view == 0x14a1de7022b0029d); + assert(cast(uint) view == 0x22b0029d); + assert(cast(ubyte) view == 0x9d); +} +version(mir_bignum_test_llv) +@safe pure +unittest +{ + auto view = BigUIntView!ushort.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(cast(ulong) view == 0x14a1de7022b0029d); + assert(cast(uint) view == 0x22b0029d); + assert(cast(ubyte) view == 0x9d); +} + +version(mir_bignum_test_llv) +@safe pure +unittest +{ + auto view = BigUIntView!uint.fromHexString("afbbfae3cd0aff2714a1de7022b0029d"); + assert(cast(ulong) view == 0x14a1de7022b0029d); + assert(cast(uint) view == 0x22b0029d); + assert(cast(ubyte) view == 0x9d); +} + + +/// +version(mir_bignum_test_llv) +@safe pure nothrow +unittest +{ + import std.traits; + alias AliasSeq(T...) = T; + + foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) + { + T[3] lhsData = [1, T.max-1, 0]; + T[3] rhsData = [T.max, T.max, 0]; + + auto lhs = BigUIntView!T(lhsData).normalized; + + /// bool overflow = bigUInt op= scalar + assert(lhs.coefficients == [1, T.max-1]); + assert(lhs.mostSignificantFirst == [T.max-1, 1]); + static if (T.sizeof >= 4) + { + assert((lhs += T.max) == false); + assert(lhs.coefficients == [0, T.max]); + assert((lhs += T.max) == false); + assert((lhs += T.max) == true); // overflow bit + assert(lhs.coefficients == [T.max-1, 0]); + assert((lhs -= T(1)) == false); + assert(lhs.coefficients == [T.max-2, 0]); + assert((lhs -= T.max) == true); // underflow bit + assert(lhs.coefficients == [T.max-1, T.max]); + assert((lhs -= Signed!T(-4)) == true); // overflow bit + assert(lhs.coefficients == [2, 0]); + assert((lhs += Signed!T.max) == false); // overflow bit + assert(lhs.coefficients == [Signed!T.max + 2, 0]); + + /// bool overflow = bigUInt op= bigUInt/bigInt + lhs = BigUIntView!T(lhsData); + auto rhs = BigUIntView!T(rhsData).normalized; + assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); + assert(rhs.coefficients == [T.max, T.max]); + assert((lhs += rhs) == false); + assert(lhs.coefficients == [Signed!T.max + 1, 0, 1]); + assert((lhs -= rhs) == false); + assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); + assert((lhs += -rhs) == true); + assert(lhs.coefficients == [Signed!T.max + 3, 0, T.max]); + assert((lhs += -(-rhs)) == true); + assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); + + /// W overflow = bigUInt *= scalar + assert((lhs *= T.max) == 0); + assert((lhs += T(Signed!T.max + 2)) == false); + assert(lhs.coefficients == [0, Signed!T.max + 2, 0]); + lhs = lhs.normalized; + lhs.coefficients[1] = T.max / 2 + 3; + assert(lhs.coefficients == [0, T.max / 2 + 3]); + assert((lhs *= 8u) == 4); + assert(lhs.coefficients == [0, 16]); + } + } +} + +/++ +Arbitrary length signed integer view. ++/ +struct BigIntView(W) + if (is(Unqual!W == ubyte) || is(Unqual!W == ushort) || is(Unqual!W == uint) || is(Unqual!W == ulong)) +{ + import mir.bignum.fp: Fp; + + /++ + Self-assigned to unsigned integer view $(MREF BigUIntView). + + Sign is stored in the most significant bit. + + The number is encoded as pair of `unsigned` and `sign`. + +/ + BigUIntView!W unsigned; + + /++ + Sign bit + +/ + bool sign; + +@safe: + + /// + inout(W)[] coefficients() inout @property + { + return unsigned.coefficients; + } + + /// + this(W[] coefficients, bool sign = false) + { + this(BigUIntView!W(coefficients), sign); + } + + /// + this(BigUIntView!W unsigned, bool sign = false) + { + this.unsigned = unsigned; + this.sign = sign; + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Returns: false in case of overflow or incorrect string. + Precondition: non-empty coefficients. + +/ + bool fromStringImpl(C)(scope const(C)[] str) + scope @trusted pure @nogc nothrow + if (isSomeChar!C) + { + import mir.utility: _expect; + + if (_expect(str.length == 0, false)) + return false; + + if (str[0] == '-') + { + sign = true; + str = str[1 .. $]; + } + else + if (_expect(str[0] == '+', false)) + { + str = str[1 .. $]; + } + + if (!unsigned.fromStringImpl(str)) + return false; + sign &= unsigned.coefficients.length != 0; + return true; + } + + /++ + +/ + static BigIntView fromHexString(C, bool allowUnderscores = false)(scope const(C)[] str) + @trusted pure + if (isSomeChar!C) + { + auto length = str.length / (W.sizeof * 2) + (str.length % (W.sizeof * 2) != 0); + auto ret = BigIntView!(Unqual!W)(new Unqual!W[length]); + if (ret.fromHexStringImpl!(C, allowUnderscores)(str)) + return cast(BigIntView) ret; + version(D_Exceptions) + { import mir.exception : toMutable; throw hexStringException.toMutable; } + else + assert(0, hexStringErrorMsg); + } + + static if (isMutable!W) + /++ + +/ + bool fromHexStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) + @safe pure @nogc nothrow + if (isSomeChar!C) + { + pragma(inline, false); + import mir.utility: _expect; + + assert(unsigned.coefficients.length); + + if (_expect(str.length == 0, false)) + return false; + + sign = false; + + if (str[0] == '-') + { + sign = true; + str = str[1 .. $]; + } + else + if (_expect(str[0] == '+', false)) + { + str = str[1 .. $]; + } + + auto ret = unsigned.fromHexStringImpl!(C, allowUnderscores)(str); + sign = sign && unsigned.coefficients.length; + return ret; + } + + /++ + +/ + static BigIntView fromBinaryString(C, bool allowUnderscores = false)(scope const(C)[] str) + @trusted pure + if (isSomeChar!C) + { + auto length = str.length / (W.sizeof * 8) + (str.length % (W.sizeof * 8) != 0); + auto ret = BigIntView!(Unqual!W)(new Unqual!W[length]); + if (ret.fromBinaryStringImpl!(C, allowUnderscores)(str)) + return cast(BigIntView) ret; + version(D_Exceptions) + { import mir.exception : toMutable; throw binaryStringException.toMutable; } + else + assert(0, binaryStringErrorMsg); + } + + static if (isMutable!W) + /++ + +/ + bool fromBinaryStringImpl(C, bool allowUnderscores = false)(scope const(C)[] str) + @safe pure @nogc nothrow + if (isSomeChar!C) + { + pragma(inline, false); + import mir.utility: _expect; + + assert(unsigned.coefficients.length); + + if (_expect(str.length == 0, false)) + return false; + + sign = false; + + if (str[0] == '-') + { + sign = true; + str = str[1 .. $]; + } + else + if (_expect(str[0] == '+', false)) + { + str = str[1 .. $]; + } + + auto ret = unsigned.fromBinaryStringImpl!(C, allowUnderscores)(str); + sign = sign && unsigned.coefficients.length; + return ret; + } + + /// + T opCast(T)() const scope + if (isFloatingPoint!T && isMutable!T) + { + auto ret = this.unsigned.opCast!T; + if (sign) + ret = -ret; + return ret; + } + + /// + T opCast(T, bool nonZero = false)() const scope + if (is(T == long) || is(T == int)) + { + auto ret = this.unsigned.opCast!(Unsigned!T, nonZero); + if (sign) + ret = -ret; + return ret; + } + + static if (W.sizeof == size_t.sizeof) + /// + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!size_t.fromHexString("-afbbfae3cd0aff2714a1de7022b0021d"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + /// + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!size_t.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_021d"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!ushort.fromHexString("-afbbfae3cd0aff2714a1de7022b0021d"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test_llv) + @safe pure + unittest + { + auto view = BigIntView!ushort.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_021d"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!ubyte.fromHexString("-afbbfae3cd0aff2714a1de7022b0021d"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!ubyte.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_021d"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!size_t.fromBinaryString!(char, true)("-10101111101110111111101011100011110011010000101011111111001001110001010010100001110111100111000000100010101100000000001000011101"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!size_t.fromBinaryString!(char, true)("-1010_1111_1011_1011_1111_1010_1110_0011_1100_1101_0000_1010_1111_1111_0010_0111_0001_0100_1010_0001_1101_1110_0111_0000_0010_0010_1011_0000_0000_0010_0001_1101"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!ushort.fromBinaryString!(char, true)("-10101111101110111111101011100011110011010000101011111111001001110001010010100001110111100111000000100010101100000000001000011101"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!ushort.fromBinaryString!(char, true)("-1010_1111_1011_1011_1111_1010_1110_0011_1100_1101_0000_1010_1111_1111_0010_0111_0001_0100_1010_0001_1101_1110_0111_0000_0010_0010_1011_0000_0000_0010_0001_1101"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test) + @safe pure + unittest + { + auto view = BigIntView!ubyte.fromBinaryString!(char, true)("-10101111101110111111101011100011110011010000101011111111001001110001010010100001110111100111000000100010101100000000001000011101"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + static if (W.sizeof == size_t.sizeof) + version(mir_bignum_test_llv) + @safe pure + unittest + { + auto view = BigIntView!ubyte.fromBinaryString!(char, true)("-1010_1111_1011_1011_1111_1010_1110_0011_1100_1101_0000_1010_1111_1111_0010_0111_0001_0100_1010_0001_1101_1110_0111_0000_0010_0010_1011_0000_0000_0010_0001_1101"); + assert(cast(long) view == -0x14a1de7022b0021d); + assert(cast(int) view == -0x22b0021d); + } + + /++ + +/ + T opCast(T : Fp!coefficientSize, uint coefficientSize)() const scope + { + auto ret = unsigned.opCast!(Fp!coefficientSize); + ret.sign = sign; + return ret; + } + + /// + BigIntView!V opCast(T : BigIntView!V, V)() return scope + if (V.sizeof <= W.sizeof) + { + return typeof(return)(this.unsigned.opCast!(BigUIntView!V), sign); + } + + /// + BigIntView!V opCast(T : BigIntView!V, V)() const return scope + if (V.sizeof <= W.sizeof) + { + return typeof(return)(this.unsigned.opCast!(BigUIntView!V), sign); + } + + /// + BigIntView!(const W) lightConst()() return scope + const @safe pure nothrow @nogc @property + { + return typeof(return)(unsigned.lightConst, sign); + } + + ///ditto + alias lightConst this; + + /++ + +/ + sizediff_t opCmp(BigIntView!(const W) rhs) + const @safe pure nothrow @nogc scope + { + import mir.algorithm.iteration: cmp; + if (auto s = rhs.sign - this.sign) + { + if (this.unsigned.coefficients.length && rhs.unsigned.coefficients.length) + return s; + } + auto d = this.unsigned.opCmp(rhs.unsigned); + return sign ? -d : d; + } + + /// + bool opEquals(BigIntView!(const W) rhs) + const @safe pure nothrow @nogc scope + { + return (this.sign == rhs.sign || unsigned.coefficients.length == 0) && this.unsigned == rhs.unsigned; + } + + /++ + Returns: true if the integer and equals to `rhs`. + +/ + bool opEquals(long rhs) + @safe pure nothrow @nogc const scope + { + if (rhs == 0 && unsigned.coefficients.length == 0) + return true; + bool sign = rhs < 0; + ulong urhs = sign ? -rhs : rhs; + return sign == this.sign && unsigned == urhs; + } + + /++ + +/ + BigIntView topMostSignificantPart(size_t length) + { + return BigIntView(unsigned.topMostSignificantPart(length), sign); + } + + /++ + +/ + BigIntView topLeastSignificantPart(size_t length) + { + return BigIntView(unsigned.topLeastSignificantPart(length), sign); + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Performs `bool overflow = big +(-)= big` operatrion. + Params: + rhs = value to add with non-empty coefficients + overflow = (overflow) initial iteration overflow + Precondition: non-empty coefficients length of greater or equal to the `rhs` coefficients length. + Returns: + true in case of unsigned overflow + +/ + bool opOpAssign(string op)(scope BigIntView!(const W) rhs, bool overflow = false) + @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + assert(rhs.coefficients.length > 0); + import mir.conv; + debug assert(this.coefficients.length >= rhs.coefficients.length, this.coefficients.length.to!string ~ " " ~ rhs.coefficients.length.to!string); + enum sum = op == "+"; + // pos += pos + // neg += neg + // neg -= pos + // pos -= neg + if ((sign == rhs.sign) == sum) + return unsigned.opOpAssign!"+"(rhs.unsigned, overflow); + // pos -= pos + // pos += neg + // neg += pos + // neg -= neg + if (unsigned.opOpAssign!"-"(rhs.unsigned, overflow)) + { + sign = !sign; + unsigned.twoComplementInPlace; + } + return false; + } + + static if (isMutable!W && W.sizeof >= 4) + /// ditto + bool opOpAssign(string op)(scope BigUIntView!(const W) rhs, bool overflow = false) + @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + return opOpAssign!op(rhs.signed, overflow); + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Performs `bool overflow = big +(-)= scalar` operatrion. + Precondition: non-empty coefficients + Params: + rhs = value to add + Returns: + true in case of unsigned overflow + +/ + bool opOpAssign(string op, T)(const T rhs) + @safe pure nothrow @nogc + if ((op == "+" || op == "-") && is(T == Signed!W)) + { + assert(this.coefficients.length > 0); + enum sum = op == "+"; + // pos += pos + // neg += neg + // neg -= pos + // pos -= neg + auto urhs = cast(W) (rhs < 0 ? -rhs : rhs); + if ((sign == (rhs < 0)) == sum) + return unsigned.opOpAssign!"+"(urhs); + // pos -= pos + // pos += neg + // neg += pos + // neg -= neg + if (unsigned.opOpAssign!"-"(urhs)) + { + sign = !sign; + unsigned.twoComplementInPlace; + } + return false; + } + + static if (isMutable!W && W.sizeof >= 4) + /// ditto + bool opOpAssign(string op, T)(const T rhs) + @safe pure nothrow @nogc + if ((op == "+" || op == "-") && is(T == W)) + { + assert(this.coefficients.length > 0); + enum sum = op == "+"; + // pos += pos + // neg -= pos + if ((sign == false) == sum) + return unsigned.opOpAssign!"+"(rhs); + // pos -= pos + // neg += pos + if (unsigned.opOpAssign!"-"(rhs)) + { + sign = !sign; + unsigned.twoComplementInPlace; + } + return false; + } + + static if (isMutable!W && W.sizeof >= 4) + /++ + Performs `W overflow = (big += overflow) *= scalar` operatrion. + Precondition: non-empty coefficients + Params: + rhs = unsigned value to multiply by + overflow = initial overflow + Returns: + unsigned overflow value + +/ + W opOpAssign(string op : "*")(W rhs, W overflow = 0u) + @safe pure nothrow @nogc + { + return unsigned.opOpAssign!op(rhs, overflow); + } + + /++ + Returns: the same intger view with inversed sign + +/ + BigIntView opUnary(string op : "-")() + { + return BigIntView(unsigned, !sign); + } + + /++ + Returns: a slice of coefficients starting from the least significant. + +/ + auto coefficients() + @safe pure nothrow @nogc @property + { + return unsigned.coefficients; + } + + /++ + Returns: a slice of coefficients starting from the most significant. + +/ + auto mostSignificantFirst() + @safe pure nothrow @nogc @property + { + return unsigned.mostSignificantFirst; + } + + /++ + Strips zero most significant coefficients. + Strips most significant zero coefficients. + Sets sign to zero if no coefficients were left. + +/ + BigIntView normalized() return scope + { + auto number = this; + number.unsigned = number.unsigned.normalized; + number.sign = number.coefficients.length == 0 ? false : number.sign; + return number; + } + + ///ditto + BigIntView!(const W) normalized() const return scope + { + return lightConst.normalized; + } +} + +/// +version(mir_bignum_test) +@safe pure +unittest +{ + import mir.bignum.fixed: UInt; + import mir.bignum.fp: Fp; + + auto fp = cast(Fp!128) BigIntView!size_t.fromHexString("-afbbfae3cd0aff2714a1de7022b0029d"); + assert(fp.sign); + assert(fp.exponent == 0); + assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); +} + +/// +version(mir_bignum_test) +@safe pure +unittest +{ + auto a = cast(double) BigIntView!size_t.fromHexString("-afbbfae3cd0aff2714a1de7022b0029d"); + assert(a == -0xa.fbbfae3cd0bp+124); +} + +/// +version(mir_bignum_test) +@safe pure +unittest +{ + auto a = cast(double) BigIntView!size_t.fromBinaryString("-10101111101110111111101011100011110011010000101011111111001001110001010010100001110111100111000000100010101100000000001010011101"); + assert(a == -0xa.fbbfae3cd0bp+124); +} + +/// +version(mir_bignum_test) +@safe pure +unittest +{ + auto a = cast(double) BigIntView!size_t.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_029d"); + assert(a == -0xa.fbbfae3cd0bp+124); +} + +/// +version(mir_bignum_test) +@safe pure +unittest +{ + auto a = cast(double) BigIntView!size_t.fromBinaryString!(char, true)("-1010_1111_1011_1011_1111_1010_1110_0011_1100_1101_0000_1010_1111_1111_0010_0111_0001_0100_1010_0001_1101_1110_0111_0000_0010_0010_1011_0000_0000_0010_1001_1101"); + assert(a == -0xa.fbbfae3cd0bp+124); +} + +version(mir_bignum_test_llv) +@safe pure +unittest +{ + import mir.bignum.fixed: UInt; + import mir.bignum.fp: Fp; + + auto fp = cast(Fp!128) BigIntView!size_t.fromHexString!(char, true)("-afbb_fae3_cd0a_ff27_14a1_de70_22b0_029d"); + assert(fp.sign); + assert(fp.exponent == 0); + assert(fp.coefficient == UInt!128.fromHexString("afbbfae3cd0aff2714a1de7022b0029d")); +} + +/// +version(mir_bignum_test_llv) +@safe pure nothrow +unittest +{ + import std.traits; + alias AliasSeq(T...) = T; + + foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) + { + T[3] lhsData = [1, T.max-1, 0]; + T[3] rhsData = [T.max, T.max, 0]; + + auto lhs = BigIntView!T(lhsData).normalized; + + /// bool overflow = bigUInt op= scalar + assert(lhs.coefficients == [1, T.max-1]); + assert(lhs.mostSignificantFirst == [T.max-1, 1]); + + static if (T.sizeof >= 4) + { + + assert((lhs += T.max) == false); + assert(lhs.coefficients == [0, T.max]); + assert((lhs += T.max) == false); + assert((lhs += T.max) == true); // overflow bit + assert(lhs.coefficients == [T.max-1, 0]); + assert((lhs -= T(1)) == false); + assert(lhs.coefficients == [T.max-2, 0]); + assert((lhs -= T.max) == false); + assert(lhs.coefficients == [2, 0]); + assert(lhs.sign); + assert((lhs -= Signed!T(-4)) == false); + assert(lhs.coefficients == [2, 0]); + assert(lhs.sign == false); + assert((lhs += Signed!T.max) == false); + assert(lhs.coefficients == [Signed!T.max + 2, 0]); + + /// bool overflow = bigUInt op= bigUInt/bigInt + lhs = BigIntView!T(lhsData); + auto rhs = BigUIntView!T(rhsData).normalized; + assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); + assert(rhs.coefficients == [T.max, T.max]); + assert((lhs += rhs) == false); + assert(lhs.coefficients == [Signed!T.max + 1, 0, 1]); + assert((lhs -= rhs) == false); + assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); + assert((lhs += -rhs) == false); + assert(lhs.sign); + assert(lhs.coefficients == [T.max - (Signed!T.max + 2), T.max, 0]); + assert(lhs.sign); + assert((lhs -= -rhs) == false); + assert(lhs.coefficients == [Signed!T.max + 2, 0, 0]); + assert(lhs.sign == false); + } + } +} + +/// +version(mir_bignum_test_llv) +unittest +{ + import mir.bignum.fixed: UInt; + import mir.bignum.low_level_view: BigUIntView; + auto bigView = BigUIntView!size_t.fromHexString("55a325ad18b2a77120d870d987d5237473790532acab45da44bc07c92c92babf0b5e2e2c7771cd472ae5d7acdb159a56fbf74f851a058ae341f69d1eb750d7e3"); + auto fixed = UInt!256.fromHexString("55e5669576d31726f4a9b58a90159de5923adc6c762ebd3c4ba518d495229072"); + auto overflow = bigView *= fixed; + assert(overflow == UInt!256.fromHexString("1cbbe8c42dc21f936e4ce5b2f52ac404439857f174084012fcd1b71fdec2a398")); + assert(bigView == BigUIntView!size_t.fromHexString("c73fd2b26f2514c103c324943b6c90a05d2732118d5f0099c36a69a8051bb0573adc825b5c9295896c70280faa4c4d369df8e92f82bfffafe078b52ae695d316")); + +} + +/// +version(mir_bignum_test_llv) +unittest +{ + import mir.bignum.fixed: UInt; + import mir.bignum.low_level_view: BigUIntView; + auto bigView2 = BigUIntView!size_t.fromHexString("55a325ad18b2a77120d870d987d5237473790532acab45da44bc07c92c92babf0b5e2e2c7771cd472ae5d7acdb159a56fbf74f851a058ae341f69d1eb750d7e3"); + auto bigView = BigUIntView!size_t.fromHexString!(char, true)("55a3_25ad_18b2_a771_20d8_70d9_87d5_2374_7379_0532_acab_45da_44bc_07c9_2c92_babf_0b5e_2e2c_7771_cd47_2ae5_d7ac_db15_9a56_fbf7_4f85_1a05_8ae3_41f6_9d1e_b750_d7e3"); + auto fixed = UInt!256.fromHexString!(true)("55e5_6695_76d3_1726_f4a9_b58a_9015_9de5_923a_dc6c_762e_bd3c_4ba5_18d4_9522_9072"); + auto overflow = bigView *= fixed; + assert(overflow == UInt!256.fromHexString("1cbbe8c42dc21f936e4ce5b2f52ac404439857f174084012fcd1b71fdec2a398")); + assert(bigView == BigUIntView!size_t.fromHexString("c73fd2b26f2514c103c324943b6c90a05d2732118d5f0099c36a69a8051bb0573adc825b5c9295896c70280faa4c4d369df8e92f82bfffafe078b52ae695d316")); +} + +/++ ++/ +struct DecimalView(W) + if (is(Unqual!W == size_t)) +{ + import mir.parse: DecimalExponentKey; + + /// + bool sign; + /// + long exponent; + /// + BigUIntView!W coefficient; + +@safe: + + static if (is(W == size_t)) + /++ + Returns: false in case of overflow or incorrect string. + Precondition: non-empty coefficients + +/ + bool fromStringImpl(C, + bool allowSpecialValues = true, + bool allowDotOnBounds = true, + bool allowDExponent = true, + bool allowStartingPlus = true, + bool allowUnderscores = true, + bool allowLeadingZeros = true, + bool allowExponent = true, + bool checkEmpty = true, + ) + (scope const(C)[] str, out DecimalExponentKey key, int exponentShift = 0) + scope @trusted pure @nogc nothrow + if (isSomeChar!C) + in (coefficient.length) + { + pragma(inline, false); + + import mir.utility: _expect; + + BigUIntView!W work; + + bool mullAdd(W rhs, W overflow) + { + import mir.stdio; + // debug dump(rhs, overflow, work); + if ((overflow = work.opOpAssign!"*"(rhs, overflow)) != 0) + { + if (_expect(work.coefficients.length < coefficient.coefficients.length, true)) + { + work = coefficient.topLeastSignificantPart(work.coefficients.length + 1); + work.coefficients[$ - 1] = overflow; + // debug dump(overflow, work); + return false; + } + return true; + } + // debug dump(overflow, work); + return false; + } + + import mir.bignum.internal.parse: decimalFromStringImpl; + alias impl = decimalFromStringImpl!(mullAdd, W); + alias specification = impl!(C, + allowSpecialValues, + allowDotOnBounds, + allowDExponent, + allowStartingPlus, + allowUnderscores, + allowLeadingZeros, + allowExponent, + checkEmpty, + ); + exponent += exponentShift; + auto ret = specification(str, key, exponent, sign); + switch (key) + { + case DecimalExponentKey.infinity: + exponent = exponent.max; + coefficient = coefficient.topLeastSignificantPart(0); + return ret; + case DecimalExponentKey.nan: + exponent = exponent.max; + coefficient = coefficient.topLeastSignificantPart(1); + coefficient.coefficients[0] = 1; + return ret; + default: + coefficient = work; + return ret; + } + } + + /// + DecimalView!(const W) lightConst()() return scope + const @safe pure nothrow @nogc @property + { + return typeof(return)(sign, exponent, coefficient.lightConst); + } + ///ditto + alias lightConst this; + + /++ + +/ + BigIntView!W signedCoefficient() + { + return typeof(return)(coefficient, sign); + } + + static if (W.sizeof >= size_t.sizeof) + /++ + Mir parsing supports up-to quadruple precision. The conversion error is 0 ULP for normal numbers. + Subnormal numbers with an exponent greater than or equal to -512 have upper error bound equal to 1 ULP. + +/ + T opCast(T, bool wordNormalized = false)() const scope + if (isFloatingPoint!T && isMutable!T) + { + import mir.bignum.internal.dec2float; + + auto coeff = coefficient.lightConst; + + static if (!wordNormalized) + coeff = coeff.normalized; + auto ret = exponent != exponent.max ? + coeff.coefficients.decimalTo!T(exponent) : + coeff.length ? T.nan : T.infinity; + if (sign) + ret = -ret; + return ret; + } +} + +version(none) +/// +unittest +{ + { + auto view = DecimalView!ulong(false, -8, BigUIntView!ulong.fromHexString("BEBC2000000011E1A3")); + auto coeff = (cast(BigUIntView!uint)view.coefficient).lightConst; + assert (algoM!double(0.0, coeff, cast(int)view.exponent) == 3.518437208883201171875E+013); + } + + // TBD: triggers underflow + // { + // auto view = DecimalView!ulong(false, 0, BigUIntView!ulong.fromHexString("88BF4748507FB9900ADB624CCFF8D78897DC900FB0460327D4D86D327219")); + // auto coeff = (cast(BigUIntView!uint)view.coefficient).lightConst; + // debug { + // import std.stdio; + // writefln("%s", algoM!float(0.0, coeff, cast(int)view.exponent)); + // writefln("%s", algoM!double(0.0, coeff, cast(int)view.exponent)); + // } + // assert (algoM!float(0.0, coeff, cast(int)view.exponent) == float.infinity); + // assert (algoM!double(0.0, coeff, cast(int)view.exponent) == 0x1.117e8e90a0ff7p+239); + // } + + { + auto view = DecimalView!ulong(false, -324, BigUIntView!ulong.fromHexString("4F0CEDC95A718E")); + auto coeff = (cast(BigUIntView!uint)view.coefficient).lightConst; + assert (algoM!float(0.0, coeff, cast(int)view.exponent) == 0); + assert (algoM!double(0.0, coeff, cast(int)view.exponent) == 2.2250738585072014e-308); + } +} + +/// +version(mir_bignum_test_llv) +unittest +{ + import mir.test; + + auto view = DecimalView!size_t(false, -8, BigUIntView!size_t.fromHexString("BEBC2000000011E1A3")); + + should(cast(float)view) == 3.518437208883201171875E+013f; + should(cast(double)view) == 3.518437208883201171875E+013; + static if (real.mant_dig >= 64) + should(cast(real)view) == 3.518437208883201171875E+013L; + + view = DecimalView!size_t(true, -169, BigUIntView!size_t.fromHexString("5A174AEDA65CC")); + should(cast(float)view) == -0; + should(cast(double)view) == -0x1.1p-511; + static if (real.mant_dig >= 64) + should(cast(real)view) == -0x8.80000000000019fp-514L; + + view = DecimalView!size_t(true, 293, BigUIntView!size_t.fromHexString("36496F6C4ED38")); + should(cast(float)view) == -float.infinity; + should(cast(double)view) == -9.55024478104888e+307; + static if (real.mant_dig >= 64) + should(cast(real)view) == -9.55024478104888e+307L; + + view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 1; + should(cast(double)view) == 1; + static if (real.mant_dig >= 64) + should(cast(real)view) == 1L; + + view = DecimalView!size_t(false, -5, BigUIntView!size_t.fromHexString("3")); + should(cast(float)view) == 3e-5f; + should(cast(double)view) == 3e-5; + static if (real.mant_dig >= 64) + should(cast(real)view) == 3e-5L; + + view = DecimalView!size_t(false, -1, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0.1f; + should(cast(double)view) == 0.1; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0.1L; + + view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("3039")); + should(cast(float)view) == 12345.0f; + should(cast(double)view) == 12345.0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 12345.0L; + + view = DecimalView!size_t(false, -7, BigUIntView!size_t.fromHexString("98967F")); + should(cast(float)view) == 0.9999999f; + should(cast(double)view) == 0.9999999; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0.9999999L; + + view = DecimalView!size_t(false, -324, BigUIntView!size_t.fromHexString("4F0CEDC95A718E")); + should(cast(float)view) == 0; + should(cast(double)view) == 2.2250738585072014e-308; + static if (real.mant_dig >= 64) + should(cast(real)view) == 2.2250738585072014e-308L; + + view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("1FFFFFFFFFFFFFFFD")); + should(cast(float)view) == 36893488147419103229f; + should(cast(double)view) == 36893488147419103229.0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x1FFFFFFFFFFFFFFFDp0L; + + view = DecimalView!size_t(false, -33, BigUIntView!size_t.fromHexString("65")); + should(cast(float)view) == 101e-33f; + should(cast(double)view) == 101e-33; + static if (real.mant_dig >= 64) + should(cast(real)view) == 101e-33L; + + view = DecimalView!size_t(false, 23, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 1e23f; + should(cast(double)view) == 1e23; + static if (real.mant_dig >= 64) + should(cast(real)view) == 1e23L; + + view = DecimalView!size_t(false, 23, BigUIntView!size_t.fromHexString("81B")); + should(cast(float)view) == 2075e23f; + should(cast(double)view) == 0xaba3d58a1f1a98p+32; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xaba3d58a1f1a9cp+32L; + + view = DecimalView!size_t(false, -23, BigUIntView!size_t.fromHexString("2209")); + should(cast(float)view) == 8713e-23f; + should(cast(double)view) == 0x1.9b75b4e7de2b9p-64; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xc.dbada73ef15c401p-67L; + + view = DecimalView!size_t(false, 300, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == float.infinity; + should(cast(double)view) == 0x1.7e43c8800759cp+996; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xb.f21e44003acdd2dp+993L; + + view = DecimalView!size_t(false, 245, BigUIntView!size_t.fromHexString("B3A73CEB227")); + should(cast(float)view) == float.infinity; + should(cast(double)view) == 0x1.48e3735333cb6p+857; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xa.471b9a999e5b01ep+854L; + + view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("88BF4748507FB9900ADB624CCFF8D78897DC900FB0460327D4D86D327219")); + should(cast(float)view) == float.infinity; + should(cast(double)view) == 0x1.117e8e90a0ff7p+239; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.8bf4748507fb99p+236L; + + view = DecimalView!size_t(false, -324, BigUIntView!size_t.fromHexString("5")); + should(cast(float)view) == 0; + should(cast(double)view) == 0x0.0000000000001p-1022; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.18995ce7aa0e1b2p-1077L; + + view = DecimalView!size_t(false, -324, BigUIntView!size_t.fromHexString("5B")); + should(cast(float)view) == 0; + should(cast(double)view) == 0x0.0000000000012p-1022; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x9.3594d9adeb09a55p-1073L; + + view = DecimalView!size_t(false, -322, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0x0.0000000000014p-1022; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xa.1ebfb4219491a1fp-1073L; + + view = DecimalView!size_t(false, -320, BigUIntView!size_t.fromHexString("CA1CCB")); + should(cast(float)view) == 0; + should(cast(double)view) == 0x0.000063df832d9p-1022; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xc.7bf065b215888c7p-1043L; + + view = DecimalView!size_t(false, -319, BigUIntView!size_t.fromHexString("33CE7943FB")); + should(cast(float)view) == 0; + should(cast(double)view) == 0x1.000000000162p-1022; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.000000000b103b6p-1025L; + + view = DecimalView!size_t(false, -309, BigUIntView!size_t.fromHexString("15")); + should(cast(float)view) == 0; + should(cast(double)view) == 0x0.f19c2629ccf53p-1022; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xf.19c2629ccf52fc4p-1026L; + + view = DecimalView!size_t(false, -340, BigUIntView!size_t.fromHexString("AF87023B9BF0EE")); + should(cast(float)view) == 0; + should(cast(double)view) == 0x0.0000000000001p-1022; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xf.fffffffffffff64p-1078L; + + view = DecimalView!size_t(false, 400, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == float.infinity; + should(cast(double)view) == double.infinity; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xd.a763fc8cb9ff9e6p+1325L; + + view = DecimalView!size_t(false, 309, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == float.infinity; + should(cast(double)view) == double.infinity; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xb.201833b35d63f73p+1023L; + + view = DecimalView!size_t(false, 308, BigUIntView!size_t.fromHexString("2")); + should(cast(float)view) == float.infinity; + should(cast(double)view) == double.infinity; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.e679c2f5e44ff8fp+1021L; + + view = DecimalView!size_t(false, 308, BigUIntView!size_t.fromHexString("2")); + should(cast(float)view) == float.infinity; + should(cast(double)view) == double.infinity; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.e679c2f5e44ff8fp+1021L; + + view = DecimalView!size_t(false, 295, BigUIntView!size_t.fromHexString("1059949B7090")); + should(cast(float)view) == float.infinity; + should(cast(double)view) == double.infinity; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.00000000006955ap+1021L; + + view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("0")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0L; + + view = view; + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0L; + + view = DecimalView!size_t(false, -325, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xa.5ced43b7e3e9188p-1083L; + + view = DecimalView!size_t(false, -326, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.4a57695fe98746dp-1086L; + + view = DecimalView!size_t(false, -500, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.33ada2003db9a8p-1664L; + + view = DecimalView!size_t(false, -1000, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.68a9188a89e1467p-3325L; + + view = DecimalView!size_t(false, -4999, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0L; + + view = DecimalView!size_t(false, -10000, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0L; + + view = DecimalView!size_t(false, -4969, BigUIntView!size_t.fromHexString("329659A941466C6B")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == real.min_normal * real.epsilon; + + view = DecimalView!size_t(false, -15, BigUIntView!size_t.fromHexString("525DB0200FFAB")); + should(cast(float)view) == 1.448997445238699f; + should(cast(double)view) == 0x1.72f17f1f49aadp+0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xb.978bf8fa4d56cp-3L; + + view = DecimalView!size_t(false, -15, BigUIntView!size_t.fromHexString("525DB0200FFAB")); + should(cast(float)view) == 1.448997445238699f; + should(cast(double)view) == 0x1.72f17f1f49aadp+0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xb.978bf8fa4d56cp-3L; + + view = DecimalView!size_t(false, -325, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xa.5ced43b7e3e9188p-1083L; + + view = DecimalView!size_t(false, -326, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0; + should(cast(double)view) == 0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8.4a57695fe98746dp-1086L; + + view = DecimalView!size_t(false, 0, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 1; + should(cast(double)view) == 0x1p+0; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0x8p-3L; + + view = DecimalView!size_t(false, -5, BigUIntView!size_t.fromHexString("3")); + should(cast(float)view) == 3e-5f; + should(cast(double)view) == 0x1.f75104d551d69p-16; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xf.ba8826aa8eb4635p-19L; + + view = DecimalView!size_t(false, -1, BigUIntView!size_t.fromHexString("1")); + should(cast(float)view) == 0.1f; + should(cast(double)view) == 0x1.999999999999ap-4; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xc.ccccccccccccccdp-7L; + + view = DecimalView!size_t(false, -7, BigUIntView!size_t.fromHexString("98967F")); + should(cast(float)view) == 0.9999999f; + should(cast(double)view) == 0x1.fffffca501acbp-1; + static if (real.mant_dig >= 64) + should(cast(real)view) == 0xf.ffffe5280d65435p-4L; +} + +/++ ++/ +struct BinaryView(W) +{ + /// + bool sign; + /// + long exponent; + /// + BigUIntView!W coefficient; + +@safe: + + /// + DecimalView!(const W) lightConst()() return scope + const @safe pure nothrow @nogc @property + { + return typeof(return)(sign, exponent, coefficient.lightConst); + } + ///ditto + alias lightConst this; + + /++ + +/ + BigIntView!W signedCoefficient() + { + return typeof(return)(sign, coefficients); + } +} diff --git a/source/mir/combinatorics/package.d b/source/mir/combinatorics/package.d index 87425e9e..226cbc70 100644 --- a/source/mir/combinatorics/package.d +++ b/source/mir/combinatorics/package.d @@ -1,9 +1,9 @@ /** This module contains various combinatorics algorithms. -Authors: Sebastian Wilzbach, Ilya Yaroshenko +Authors: Sebastian Wilzbach, Ilia Ki -License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) */ module mir.combinatorics; @@ -15,11 +15,12 @@ import std.traits; version(mir_test) unittest { import mir.ndslice.fuse; + import std.string: representation; - assert(['a', 'b'].permutations.fuse == [['a', 'b'], ['b', 'a']]); - assert(['a', 'b'].cartesianPower(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'a'], ['b', 'b']]); - assert(['a', 'b'].combinations(2).fuse == [['a', 'b']]); - assert(['a', 'b'].combinationsRepeat(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'b']]); + assert(['a', 'b'].representation.permutations.fuse == [['a', 'b'], ['b', 'a']]); + assert(['a', 'b'].representation.cartesianPower(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'a'], ['b', 'b']]); + assert(['a', 'b'].representation.combinations(2).fuse == [['a', 'b']]); + assert(['a', 'b'].representation.combinationsRepeat(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'b']]); assert(permutations!ushort(2).fuse == [[0, 1], [1, 0]]); assert(cartesianPower!ushort(2, 2).fuse == [[0, 0], [0, 1], [1, 0], [1, 1]]); @@ -85,45 +86,49 @@ Params: Returns: Binomial coefficient */ -R binomial(R = ulong, T)(T n, T k) +R binomial(R = ulong, T)(const T n, const T k) if (isArithmetic!(R, T) && ((is(typeof(T.min < 0)) && is(typeof(T.init & 1))) || !is(typeof(T.min < 0))) ) { R result = 1; enum hasMinProperty = is(typeof(T.min < 0)); + + T n2 = n; + T k2 = k; + // only add negative support if possible static if ((hasMinProperty && T.min < 0) || !hasMinProperty) { - if (n < 0) + if (n2 < 0) { - if (k >= 0) + if (k2 >= 0) { - return (k & 1 ? -1 : 1) * binomial!(R, T)(-n + k-1, k); + return (k2 & 1 ? -1 : 1) * binomial!(R, T)(-n2 + k2 - 1, k2); } - else if (k <= n) + else if (k2 <= n2) { - return ((n-k) & 1 ? -1 : 1) * binomial!(R, T)(-k-1, n-k); + return ((n2 - k2) & 1 ? -1 : 1) * binomial!(R, T)(-k2 - 1, n2 - k2); } } - if (k < 0) + if (k2 < 0) { result = 0; return result; } } - if (k > n) + if (k2 > n2) { result = 0; return result; } - if (k > n - k) + if (k2 > n2 - k2) { - k = n - k; + k2 = n2 - k2; } // make a copy of n (could be a custom type) - for (T i = 1, m = n; i <= k; i++, m--) + for (T i = 1, m = n2; i <= k2; i++, m--) { // check whether an overflow can happen // hasMember!(Result, "max") doesn't work with dmd2.068 and ldc 0.17 @@ -189,6 +194,15 @@ version(mir_test) unittest assert(binomial!BigInt(-5, -7) == 15); } +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + const size_t n = 5; + const size_t k = 2; + assert(binomial(n, k) == 10); +} + /** Creates a projection of a generalized `Collection` range for the numeric case case starting from `0` onto a custom `range` of any type. @@ -217,6 +231,9 @@ struct IndexedRoR(Collection, Range) private Collection c; private Range r; + /// + alias DeepElement = ForeachType!Range; + /// this()(Collection collection, Range range) { @@ -225,25 +242,25 @@ struct IndexedRoR(Collection, Range) } /// - auto lightScope()() + auto lightScope()() return scope { return IndexedRoR!(LightScopeOf!Collection, LightScopeOf!Range)(.lightScope(c), .lightScope(r)); } /// - auto lightScope()() const + auto lightScope()() return scope const { return IndexedRoR!(LightConstOf!(LightScopeOf!Collection), LightConstOf!(LightScopeOf!Range))(.lightScope(c), .lightScope(r)); } /// - auto lightConst()() const + auto lightConst()() return scope const { return IndexedRoR!(LightConstOf!Collection, LightConstOf!Range)(.lightConst(c), .lightConst(r)); } /// Input range primitives - auto ref front()() @property + auto front()() @property { import mir.ndslice.slice: isSlice, sliced; import mir.ndslice.topology: indexed; @@ -307,9 +324,10 @@ struct IndexedRoR(Collection, Range) version(mir_test) unittest { import mir.ndslice.fuse; + import std.string: representation; // import mir.ndslice.topology: only; - auto projectionD = 2.permutations.indexedRoR("ab"d); + auto projectionD = 2.permutations.indexedRoR("ab"d.representation); assert(projectionD.fuse == [['a', 'b'], ['b', 'a']]); // auto projectionC = 2.permutations.indexedRoR(only('a', 'b')); @@ -409,6 +427,9 @@ struct Permutations(T) private bool _empty; private size_t _max_states = 1, _pos; + /// + alias DeepElement = const T; + /** state should have the length of `n - 1`, whereas the length of indices should be `n` @@ -642,12 +663,12 @@ struct CartesianPower(T) { import mir.ndslice.slice: Slice; -private: - T[] _state; - size_t n; - size_t _max_states, _pos; + private T[] _state; + private size_t n; + private size_t _max_states, _pos; -public: + /// + alias DeepElement = const T; /// state should have the length of `repeat` this()(size_t n, T[] state) @safe pure nothrow @nogc @@ -726,13 +747,15 @@ pure nothrow @safe version(mir_test) unittest { import mir.ndslice.fuse; import mir.ndslice.topology: iota; + assert(iota(2).cartesianPower.fuse == [[0], [1]]); assert(iota(2).cartesianPower(2).fuse == [[0, 0], [0, 1], [1, 0], [1, 1]]); auto three_nums_two_bins = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]; assert(iota(3).cartesianPower(2).fuse == three_nums_two_bins); - assert("AB"d.cartesianPower(2).fuse == ["AA"d, "AB"d, "BA"d, "BB"d]); + import std.string: representation; + assert("AB"d.representation.cartesianPower(2).fuse == ["AA"d, "AB"d, "BA"d, "BB"d]); } /// @@ -762,16 +785,17 @@ pure nothrow @safe version(mir_test) unittest import mir.array.allocation: array; import mir.ndslice.topology: iota; import std.range: dropOne; + import std.string: representation; assert(iota(0).cartesianPower.length == 0); - assert("AB"d.cartesianPower(3).fuse == ["AAA"d, "AAB"d, "ABA"d, "ABB"d, "BAA"d, "BAB"d, "BBA"d, "BBB"d]); + assert("AB"d.representation.cartesianPower(3).fuse == ["AAA"d, "AAB"d, "ABA"d, "ABB"d, "BAA"d, "BAB"d, "BBA"d, "BBB"d]); auto expected = ["AA"d, "AB"d, "AC"d, "AD"d, "BA"d, "BB"d, "BC"d, "BD"d, "CA"d, "CB"d, "CC"d, "CD"d, "DA"d, "DB"d, "DC"d, "DD"d]; - assert("ABCD"d.cartesianPower(2).fuse == expected); + assert("ABCD"d.representation.cartesianPower(2).fuse == expected); // verify with array too - assert("ABCD"d.cartesianPower(2).fuse == expected); + assert("ABCD"d.representation.cartesianPower(2).fuse == expected); assert(iota(2).cartesianPower.front == [0]); @@ -893,12 +917,12 @@ struct Combinations(T) { import mir.ndslice.slice: Slice; -private: - T[] state; - size_t n; - size_t max_states, pos; + private T[] state; + private size_t n; + private size_t max_states, pos; -public: + /// + alias DeepElement = const T; /// state should have the length of `repeat` this()(size_t n, T[] state) @safe pure nothrow @nogc @@ -1004,9 +1028,10 @@ pure nothrow @safe version(mir_test) unittest { import mir.ndslice.fuse; import mir.ndslice.topology: iota; + import std.string: representation; assert(iota(3).combinations(2).fuse == [[0, 1], [0, 2], [1, 2]]); - assert("AB"d.combinations(2).fuse == ["AB"d]); - assert("ABC"d.combinations(2).fuse == ["AB"d, "AC"d, "BC"d]); + assert("AB"d.representation.combinations(2).fuse == ["AB"d]); + assert("ABC"d.representation.combinations(2).fuse == ["AB"d, "AC"d, "BC"d]); } /// @@ -1035,14 +1060,15 @@ pure nothrow @safe version(mir_test) unittest import mir.array.allocation: array; import mir.ndslice.topology: iota; import std.range: dropOne; + import std.string: representation; assert(iota(0).combinations.length == 0); assert(iota(2).combinations.fuse == [[0], [1]]); auto expected = ["AB"d, "AC"d, "AD"d, "BC"d, "BD"d, "CD"d]; - assert("ABCD"d.combinations(2).fuse == expected); + assert("ABCD"d.representation.combinations(2).fuse == expected); // verify with array too - assert("ABCD"d.combinations(2).fuse == expected); + assert("ABCD"d.representation.combinations(2).fuse == expected); assert(iota(2).combinations.front == [0]); // is copyable? @@ -1221,12 +1247,12 @@ struct CombinationsRepeat(T) { import mir.ndslice.slice: Slice; -private: - T[] state; - size_t n; - size_t max_states, pos; + private T[] state; + private size_t n; + private size_t max_states, pos; -public: + /// + alias DeepElement = const T; /// state should have the length of `repeat` this()(size_t n, T[] state) @safe pure nothrow @nogc @@ -1310,11 +1336,12 @@ pure nothrow @safe version(mir_test) unittest { import mir.ndslice.fuse; import mir.ndslice.topology: iota; + import std.string: representation; assert(iota(2).combinationsRepeat.fuse == [[0], [1]]); assert(iota(2).combinationsRepeat(2).fuse == [[0, 0], [0, 1], [1, 1]]); assert(iota(3).combinationsRepeat(2).fuse == [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]]); - assert("AB"d.combinationsRepeat(2).fuse == ["AA"d, "AB"d, "BB"d]); + assert("AB"d.representation.combinationsRepeat(2).fuse == ["AA"d, "AB"d, "BB"d]); } /// @@ -1343,14 +1370,15 @@ version(mir_test) unittest import mir.array.allocation: array; import mir.ndslice.topology: iota; import std.range: dropOne; + import std.string: representation; assert(iota(0).combinationsRepeat.length == 0); - assert("AB"d.combinationsRepeat(3).fuse == ["AAA"d, "AAB"d, "ABB"d,"BBB"d]); + assert("AB"d.representation.combinationsRepeat(3).fuse == ["AAA"d, "AAB"d, "ABB"d,"BBB"d]); auto expected = ["AA"d, "AB"d, "AC"d, "AD"d, "BB"d, "BC"d, "BD"d, "CC"d, "CD"d, "DD"d]; - assert("ABCD"d.combinationsRepeat(2).fuse == expected); + assert("ABCD"d.representation.combinationsRepeat(2).fuse == expected); // verify with array too - assert("ABCD"d.combinationsRepeat(2).fuse == expected); + assert("ABCD"d.representation.combinationsRepeat(2).fuse == expected); assert(iota(2).combinationsRepeat.front == [0]); diff --git a/source/mir/container/binaryheap.d b/source/mir/container/binaryheap.d index 38902afd..9dbc7a0f 100644 --- a/source/mir/container/binaryheap.d +++ b/source/mir/container/binaryheap.d @@ -6,18 +6,16 @@ Current implementation is suitable for Mir/BetterC concepts. This module is a submodule of $(MREF mir, container). -Copyright: 2010- Andrei Alexandrescu. All rights reserved by the respective holders. +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments -License: Distributed under the Boost Software License, Version 1.0. +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) -Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilya Yaroshenko (Mir & BetterC rework). +Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilia Ki (Mir & BetterC rework). */ module mir.container.binaryheap; import mir.primitives; -import mir.primitives; - -import std.range.primitives: isRandomAccessRange, hasSwappableElements, ElementType; +import std.range.primitives: isRandomAccessRange, hasSwappableElements; import std.traits; /// @@ -63,11 +61,11 @@ insertBack), the $(D BinaryHeap) may grow by adding elements to the container. +/ struct BinaryHeap(alias less = "a < b", Store) -if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[]))) +if (isRandomAccessRange!Store || isRandomAccessRange!(typeof(Store.init[]))) { import mir.utility : min; import mir.functional : naryFun; - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; + import core.lifetime: move; import std.algorithm.mutation : swapAt; static if (isRandomAccessRange!Store) @@ -182,7 +180,7 @@ public: Returns a _front of the heap, which is the largest element according to `less`. +/ - @property auto ref ElementType!Store front() scope return + @property auto ref ElementType!Store front() return scope { assert(!empty, "Cannot call front on an empty heap."); return _store.front; @@ -384,7 +382,7 @@ BinaryHeap!(less, Store) heapify(alias less = "a < b", Store)(Store s, @system nothrow version(mir_test) unittest { // Test range interface. - import std.range.primitives: isInputRange; + import mir.primitives: isInputRange; import mir.algorithm.iteration : equal; int[] a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]; auto h = heapify(a); @@ -399,6 +397,20 @@ BinaryHeap!(less, Store) heapify(alias less = "a < b", Store)(Store s, Array!int elements = [1, 2, 10, 12]; auto heap = heapify(elements); assert(heap.front == 12); + heap.insert(100); + assert(heap.front == 100); + heap.insert(1); + assert(heap.front == 100); +} + +@system version(mir_test) unittest +{ + import std.container.array : Array; + + Array!Object elements; + auto heap = heapify(elements); + Object obj; + heap.insert(obj); } @system nothrow version(mir_test) unittest @@ -426,6 +438,7 @@ BinaryHeap!(less, Store) heapify(alias less = "a < b", Store)(Store s, /// Heap operations for random-access ranges template HeapOps(alias less, Range) { + import std.range.primitives : hasSwappableElements, hasAssignableElements; import mir.functional; import std.algorithm.mutation : swapAt; diff --git a/source/mir/date.d b/source/mir/date.d new file mode 100644 index 00000000..ea6f8950 --- /dev/null +++ b/source/mir/date.d @@ -0,0 +1,4492 @@ +/++ +Fast BetterC Date type with Boost ABI and mangling compatability. + +$(SCRIPT inhibitQuickIndex = 1;) +$(DIVC quickindex, +$(BOOKTABLE, +$(TR $(TH Category) $(TH Functions)) +$(TR $(TD Main date types) $(TD + $(LREF Date) + $(LREF YearMonth) + $(LREF YearQuarter) +)) +$(TR $(TD Other date types) $(TD + $(LREF Month) + $(LREF Quarter) + $(LREF DayOfWeek) +)) +$(TR $(TD Date checking) $(TD + $(LREF valid) + $(LREF yearIsLeapYear) +)) +$(TR $(TD Date conversion) $(TD + $(LREF daysToDayOfWeek) + $(LREF quarter) +)) +$(TR $(TD Other) $(TD + $(LREF AllowDayOverflow) + $(LREF DateTimeException) + $(LREF AssumePeriod) +)) +)) +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: $(HTTP jmdavisprog.com, Jonathan M Davis), Ilia Ki (boost-like and BetterC rework) ++/ +module mir.date; + +import mir.primitives: isOutputRange; +import mir.serde: serdeProxy; +import mir.timestamp: Timestamp; +import std.traits: isSomeChar, Unqual; + +version(mir_test) +version(D_Exceptions) +version(unittest) import std.exception : assertThrown; + +version(test_with_asdf) +unittest +{ + import asdf.serialization; + + assert(Date(2020, 3, 19).serializeToJson == `"2020-03-19"`); + assert(`"2020-03-19"`.deserialize!Date == Date(2020, 3, 19)); + assert(`"20200319"`.deserialize!Date == Date(2020, 3, 19)); + assert(`"2020-Mar-19"`.deserialize!Date == Date(2020, 3, 19)); +} + +/++ +Returns whether the given value is valid for the given unit type when in a +time point. Naturally, a duration is not held to a particular range, but +the values in a time point are (e.g. a month must be in the range of +1 - 12 inclusive). +Params: + units = The units of time to validate. + value = The number to validate. ++/ +bool valid(string units)(int value) @safe pure nothrow @nogc +if (units == "months" || + units == "hours" || + units == "minutes" || + units == "seconds") +{ + static if (units == "months") + return value >= Month.jan && value <= Month.dec; + else static if (units == "hours") + return value >= 0 && value <= 23; + else static if (units == "minutes") + return value >= 0 && value <= 59; + else static if (units == "seconds") + return value >= 0 && value <= 59; +} + +/// +version (mir_test) +@safe unittest +{ + assert(valid!"hours"(12)); + assert(!valid!"hours"(32)); + assert(valid!"months"(12)); + assert(!valid!"months"(13)); +} + +/++ +Returns whether the given day is valid for the given year and month. +Params: + units = The units of time to validate. + year = The year of the day to validate. + month = The month of the day to validate (January is 1). + day = The day to validate. ++/ +bool valid(string units)(int year, int month, int day) @safe pure nothrow @nogc + if (units == "days") +{ + return day > 0 && day <= maxDay(year, month); +} + +/// +version (mir_test) +@safe pure nothrow @nogc unittest +{ + assert(valid!"days"(2016, 2, 29)); + assert(!valid!"days"(2016, 2, 30)); + assert(valid!"days"(2017, 2, 20)); + assert(!valid!"days"(2017, 2, 29)); +} + +/// +enum AllowDayOverflow : bool +{ + /// + no, + /// + yes +} + +/++ +Whether the given Gregorian Year is a leap year. +Params: + year = The year to to be tested. + +/ +bool yearIsLeapYear(int year) @safe pure nothrow @nogc +{ + if (year % 400 == 0) + return true; + if (year % 100 == 0) + return false; + return year % 4 == 0; +} + +/// +version (mir_test) +@safe unittest +{ + foreach (year; [1, 2, 100, 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010]) + { + assert(!yearIsLeapYear(year)); + assert(!yearIsLeapYear(-year)); + } + + foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012]) + { + assert(yearIsLeapYear(year)); + assert(yearIsLeapYear(-year)); + } +} + +version (mir_test) +@safe unittest +{ + import std.format : format; + foreach (year; [1, 2, 3, 5, 6, 7, 100, 200, 300, 500, 600, 700, 1998, 1999, + 2001, 2002, 2003, 2005, 2006, 2007, 2009, 2010, 2011]) + { + assert(!yearIsLeapYear(year), format("year: %s.", year)); + assert(!yearIsLeapYear(-year), format("year: %s.", year)); + } + + foreach (year; [0, 4, 8, 400, 800, 1600, 1996, 2000, 2004, 2008, 2012]) + { + assert(yearIsLeapYear(year), format("year: %s.", year)); + assert(yearIsLeapYear(-year), format("year: %s.", year)); + } +} + +/// +enum Month : short +{ + /// + jan = 1, + /// + feb, + /// + mar, + /// + apr, + /// + may, + /// + jun, + /// + jul, + /// + aug, + /// + sep, + /// + oct, + /// + nov, + /// + dec, +} + +version(D_Exceptions) +/// +class DateTimeException : Exception +{ + /// + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + /// ditto + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} + +version(D_Exceptions) +{ + private static immutable InvalidMonth = new DateTimeException("Date: Invalid Month"); + private static immutable InvalidDay = new DateTimeException("Date: Invalid Day"); + private static immutable InvalidISOString = new DateTimeException("Date: Invalid ISO String"); + private static immutable InvalidISOExtendedString = new DateTimeException("Date: Invalid ISO Extended String"); + private static immutable InvalidSimpleString = new DateTimeException("Date: Invalid Simple String"); + private static immutable InvalidString = new DateTimeException("Date: Invalid String"); +} + +version (mir_test) +@safe unittest +{ + initializeTests(); +} + +/++ + Represents the 7 days of the Gregorian week (Monday is 0). + +/ +extern(C++, "mir") +enum DayOfWeek +{ + mon = 0, /// + tue, /// + wed, /// + thu, /// + fri, /// + sat, /// + sun, /// +} + +/// +@serdeProxy!Timestamp +struct YearMonthDay +{ + short year = 1; + Month month = Month.jan; + ubyte day = 1; + + /// + Quarter quarter() @safe pure nothrow @nogc @property + { + return month.quarter; + } + + /// + Timestamp timestamp() @safe pure nothrow @nogc const @property + { + return Timestamp(year, cast(ubyte)month, day); + } + + /// + alias opCast(T : Timestamp) = timestamp; + +@safe pure @nogc: + + /// + YearQuarter yearQuarter() @safe pure nothrow @nogc @property + { + return YearQuarter(year, this.quarter); + } + + /// + alias opCast(T : YearQuarter) = yearQuarter; + + /// + version(mir_test) + unittest + { + import mir.timestamp; + auto timestamp = cast(Timestamp) YearMonthDay(2020, Month.may, 12); + } + + /// + this(short year, Month month, ubyte day) @safe pure nothrow @nogc + { + this.year = year; + this.month = month; + this.day = day; + } + + /// + this(Date date) @safe pure nothrow @nogc + { + this = date.yearMonthDay; + } + + /// + version(mir_test) + @safe unittest + { + auto d = YearMonthDay(2020, Month.may, 31); + auto ym = d.YearMonth; + assert(ym.year == 2020); + assert(ym.month == Month.may); + } + + // + version(mir_test) + @safe unittest + { + auto d = YearMonthDay(2050, Month.dec, 31); + auto ym = d.YearMonth; + assert(ym.year == 2050); + assert(ym.month == Month.dec); + } + + /// + this(YearMonth yearMonth, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure nothrow @nogc + { + with(yearMonth) this(year, month, day(assumePeriod)); + } + + /// + this(YearQuarter yearQuarter, AssumePeriod assumePeriodMonth = AssumePeriod.begin, AssumePeriod assumePeriodDay = AssumePeriod.begin) @safe pure nothrow @nogc + { + with(yearQuarter) this(year, month(assumePeriodMonth), day(assumePeriodDay)); + } + + version(D_Exceptions) + /// + this(Timestamp timestamp) @safe pure @nogc + { + if (timestamp.precision != Timestamp.Precision.day) + { + static immutable exc = new Exception("YearMonthDay: invalid timestamp precision"); + { import mir.exception : toMutable; throw exc.toMutable; } + } + with(timestamp) this(year, cast(Month)month, day); + } + + /// + @safe pure nothrow @nogc + ref YearMonthDay add(string units : "months")(long months, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + { + auto years = months / 12; + months %= 12; + auto newMonth = month + months; + + if (months < 0) + { + if (newMonth < 1) + { + newMonth += 12; + --years; + } + } + else if (newMonth > 12) + { + newMonth -= 12; + ++years; + } + + year += years; + month = cast(Month) newMonth; + + immutable currMaxDay = maxDay(year, month); + immutable overflow = day - currMaxDay; + + if (overflow > 0) + { + if (allowOverflow == AllowDayOverflow.yes) + { + ++month; + day = cast(ubyte) overflow; + } + else + day = cast(ubyte) currMaxDay; + } + + return this; + } + + /// + @safe pure nothrow @nogc + ref YearMonthDay add(string units : "quarters")(long quarters) + { + return add!"months"(quarters * 4); + } + + // Shares documentation with "years" version. + @safe pure nothrow @nogc + ref YearMonthDay add(string units : "years")(long years, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + { + year += years; + + immutable currMaxDay = maxDay(year, month); + immutable overflow = day - currMaxDay; + + if (overflow > 0) + { + if (allowOverflow == AllowDayOverflow.yes) + { + ++month; + day = cast(ubyte) overflow; + } + else + day = cast(ubyte) currMaxDay; + } + return this; + } + + /++ + Day of the year this $(LREF Date) is on. + +/ + @property int dayOfYear() const @safe pure nothrow @nogc + { + if (month >= Month.jan && month <= Month.dec) + { + immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; + auto monthIndex = month - Month.jan; + + return lastDay[monthIndex] + day; + } + assert(0, "Invalid month."); + } + + /// + version (mir_test) + @safe unittest + { + assert(YearMonthDay(1999, cast(Month) 1, 1).dayOfYear == 1); + assert(YearMonthDay(1999, cast(Month) 12, 31).dayOfYear == 365); + assert(YearMonthDay(2000, cast(Month) 12, 31).dayOfYear == 366); + } + + /++ + Whether this $(LREF Date) is in a leap year. + +/ + @property bool isLeapYear() const @safe pure nothrow @nogc + { + return yearIsLeapYear(year); + } + + private void setDayOfYear(bool useExceptions = false)(int days) + { + immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; + + bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear); + + static if (useExceptions) + { + if (dayOutOfRange) { import mir.exception : toMutable; throw InvalidDay.toMutable; } + } + else + { + assert(!dayOutOfRange, "Invalid Day"); + } + + foreach (i; 1 .. lastDay.length) + { + if (days <= lastDay[i]) + { + month = cast(Month)(cast(int) Month.jan + i - 1); + day = cast(ubyte)(days - lastDay[i - 1]); + return; + } + } + assert(0, "Invalid day of the year."); + } + + /++ + The last day in the month that this $(LREF Date) is in. + +/ + @property ubyte daysInMonth() const @safe pure nothrow @nogc + { + return maxDay(year, month); + } + + /++ + Whether the current year is a date in A.D. + +/ + @property bool isAD() const @safe pure nothrow @nogc + { + return year > 0; + } +} + +/++ +Controls the assumed start period of days for `YearMonth` or days and quarters +for `YearQuarter` ++/ +enum AssumePeriod { + /// + begin, + /// + end +} + +/// Represents a date as a pair of years and months. +@serdeProxy!Timestamp +struct YearMonth +{ + short year = 1; + Month month = Month.jan; + + version(D_BetterC){} else + { + private string toStringImpl(alias fun)() const @safe pure nothrow + { + import mir.small_string : SmallString; + SmallString!16 w; + try + fun(w); + catch (Exception e) + assert(0, __traits(identifier, fun) ~ " threw."); + return w[].idup; + } + + string toISOExtString() const @safe pure nothrow + { + return toStringImpl!toISOExtString; + } + + alias toString = toISOExtString; + } + + /// + void toISOExtString(W)(scope ref W w) const scope + if (isOutputRange!(W, char)) + { + import mir.format: printZeroPad; + if (year >= 10_000) + w.put('+'); + w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6); + w.put('-'); + w.printZeroPad(cast(uint)month, 2); + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth(1999, Month.jan); + assert(ym.toISOExtString == "1999-01"); + } + + // + version (mir_test) + @safe unittest + { + auto ym = YearMonth(10_001, Month.jan); + assert(ym.toISOExtString == "+10001-01"); + } + + @property ubyte day(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc + { + final switch (assumePeriod) + { + case AssumePeriod.begin: + return 1; + case AssumePeriod.end: + return daysInMonth; + } + } + + /// + version (mir_test) + @safe unittest + { + assert(YearMonth(1999, cast(Month) 1).day(AssumePeriod.begin) == 1); + assert(YearMonth(1999, cast(Month) 12).day(AssumePeriod.end) == 31); + } + + /// + Quarter quarter() @safe pure nothrow @nogc @property + { + return month.quarter; + } + + /// + version (mir_test) + @safe unittest + { + assert(YearMonth(1999, Month.jan).quarter == 1); + } + + /// + Timestamp timestamp() @safe pure nothrow @nogc const @property + { + return Timestamp(year, cast(ubyte)month); + } + + /// + alias opCast(T : Timestamp) = timestamp; + + /// + version(mir_test) + unittest + { + import mir.timestamp; + auto ym0 = YearMonth(2020, Month.may); + auto timestamp1 = cast(Timestamp) ym0; + auto ym1 = YearMonth(timestamp1); + } + + /// + this(short year, Month month) @safe pure nothrow @nogc + { + this.year = year; + this.month = month; + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth(2000, Month.dec); + } + + /// + this(short year, ushort month) @safe pure @nogc + { + static immutable exc = new Exception("Month out of bounds [1, 12]"); + if (1 > month || month > 12) + { import mir.exception : toMutable; throw exc.toMutable; } + this.year = year; + this.month = cast(Month)month; + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth(2000, 12); + } + + /// + this(Date date) @safe pure nothrow @nogc + { + this(date.YearMonthDay); + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth(Date(2000, Month.dec, 31)); + } + + /// + version(mir_test) + @safe unittest + { + auto d = Date(2020, Month.may, 31); + auto ym = d.YearMonth; + assert(ym.year == 2020); + assert(ym.month == Month.may); + } + + // + version(mir_test) + @safe unittest + { + auto d = Date(2050, Month.dec, 31); + auto ym = d.YearMonth; + assert(ym.year == 2050); + assert(ym.month == Month.dec); + } + + /// + this(YearMonthDay yearMonthDay) @safe pure nothrow @nogc + { + with(yearMonthDay) this(year, month); + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth(YearMonthDay(2000, Month.dec, 31)); + } + + /// + this(YearQuarter yearQuarter, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure nothrow @nogc + { + with(yearQuarter) this(year, month(assumePeriod)); + } + + /// + version (mir_test) + @safe unittest + { + auto ym1 = YearMonth(YearQuarter(2000, Quarter.q1)); + auto ym2 = YearMonth(YearQuarter(2000, Quarter.q1), AssumePeriod.end); + } + + version(D_Exceptions) + /// + this(Timestamp timestamp) @safe pure @nogc + { + if (timestamp.precision != Timestamp.Precision.month) + { + static immutable exc = new Exception("YearMonth: invalid timestamp precision"); + { import mir.exception : toMutable; throw exc.toMutable; } + } + with(timestamp) this(year, cast(Month)month); + } + + Date nthWeekday(int n, DayOfWeek dow) const @safe pure nothrow @nogc + { + auto d = trustedWithDayOfMonth(1); + auto dc = d.dayOfWeek.daysToDayOfWeek(dow) + (n - 1) * 7; + d = d + dc; + return d; + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth(2000, Month.nov); + assert(ym.nthWeekday(1, DayOfWeek.mon) == Date(2000, 11, 6)); + assert(ym.nthWeekday(5, DayOfWeek.mon) == Date(2000, 12, 4)); + } + + /// + Date trustedWithDayOfMonth(int days) const @safe pure nothrow @nogc + { + assert(days <= lengthOfMonth); + return Date.trustedCreate(year, month, days); + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth(2000, Month.nov); + assert(ym.trustedWithDayOfMonth(6) == Date(2000, 11, 6)); + } + + /// + int opCmp(YearMonth rhs) const pure nothrow @nogc @safe + { + if (auto d = this.year - rhs.year) + return d; + return this.month - rhs.month; + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth(2000, Month.nov); + assert(ym.opCmp(YearMonth(2000, Month.nov)) == 0); + assert(ym.opCmp(YearMonth(2000, Month.oct)) == 1); + assert(ym.opCmp(YearMonth(2000, Month.dec)) == -1); + assert(ym.opCmp(YearMonth(2001, Month.nov)) == -1); + } + + /// + size_t toHash() const pure nothrow @nogc @safe + { + return year * 16 + month; + } + + /// + version (mir_test) + @safe unittest + { + assert(YearMonth(2000, Month.dec).toHash == 32012); + } + + /// + Date endOfMonth() const nothrow @property @nogc @safe pure + { + return Date.trustedCreate(year, month, lengthOfMonth); + } + + /// + version (mir_test) + @safe unittest + { + assert(YearMonth(2000, Month.dec).endOfMonth == Date(2000, Month.dec, 31)); + } + + /// + ushort lengthOfMonth() const pure nothrow @property @nogc @safe + { + return maxDay(year, month); + } + + /// + version (mir_test) + @safe unittest + { + assert(YearMonth(2000, Month.dec).lengthOfMonth == 31); + } + + /// + this(scope const(char)[] str) @safe pure @nogc + { + this = fromISOExtString(str); + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearMonth("1999-01"); + assert(ym.year == 1999); + assert(ym.month == 1); + } + + static bool fromISOExtString(C)(scope const(C)[] str, out YearMonth value) @safe pure @nogc + if (isSomeChar!C) + { + import mir.parse: fromString; + if (str.length < 7 || str[$-3] != '-') + return false; + + auto yearStr = str[0 .. $ - 3]; + + if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) + return false; + + short year; + ushort month; + + const ret = + fromString(str[$ - 2 .. $], month) + && fromString(yearStr, year); + + value = YearMonth(year, month); + return ret; + } + + static YearMonth fromISOExtString(C)(scope const(C)[] str) @safe pure + if (isSomeChar!C) + { + YearMonth ret; + if (fromISOExtString(str, ret)) + return ret; + static immutable exc = new Exception("Invalid YearMonth string"); + { import mir.exception : toMutable; throw exc.toMutable; } + } + +nothrow: + + /// + deprecated("please use addMonths instead") + @safe pure nothrow @nogc + ref YearMonth add(string units : "months")(long months) + { + auto years = months / 12; + months %= 12; + auto newMonth = month + months; + + if (months < 0) + { + if (newMonth < 1) + { + newMonth += 12; + --years; + } + } + else if (newMonth > 12) + { + newMonth -= 12; + ++years; + } + + year += years; + month = cast(Month) newMonth; + + return this; + } + + /// + version(mir_test_deprecated) + @safe unittest + { + auto ym0 = YearMonth(2020, Month.jan); + + ym0.add!"months"(1); + assert(ym0.year == 2020); + assert(ym0.month == Month.feb); + + auto ym1 = ym0.add!"months"(1); + assert(ym1.year == 2020); + assert(ym1.month == Month.mar); + + // also changes ym0 + assert(ym0.year == 2020); + assert(ym0.month == Month.mar); + + ym1.add!"months"(10); + assert(ym1.year == 2021); + assert(ym1.month == Month.jan); + + ym1.add!"months"(-13); + assert(ym1.year == 2019); + assert(ym1.month == Month.dec); + } + + /// + deprecated("please use addQuarters instead") + @safe pure nothrow @nogc + ref YearMonth add(string units : "quarters")(long quarters) + { + return add!"months"(quarters * 3); + } + + /// + version(mir_test_deprecated) + @safe unittest + { + auto yq0 = YearMonth(2020, Month.jan); + + yq0.add!"quarters"(1); + assert(yq0.year == 2020); + assert(yq0.month == Month.apr); + + auto yq1 = yq0.add!"quarters"(1); + assert(yq1.year == 2020); + assert(yq1.month == Month.jul); + + // also changes yq0 + assert(yq0.year == 2020); + assert(yq0.month == Month.jul); + + yq1.add!"quarters"(2); + assert(yq1.year == 2021); + assert(yq1.month == Month.jan); + + yq1.add!"quarters"(-5); + assert(yq1.year == 2019); + assert(yq1.month == Month.oct); + } + + /// + deprecated("please use addYears instead") + @safe pure nothrow @nogc + ref YearMonth add(string units : "years")(long years) + { + year += years; + return this; + } + + /// + version(mir_test_deprecated) + @safe unittest + { + auto ym0 = YearMonth(2020, Month.jan); + + ym0.add!"years"(1); + assert(ym0.year == 2021); + assert(ym0.month == Month.jan); + + auto ym1 = ym0.add!"years"(1); + assert(ym1.year == 2022); + assert(ym1.month == Month.jan); + + // also changes ym0 + assert(ym0.year == 2022); + assert(ym0.month == Month.jan); + } + + /// + @safe pure nothrow @nogc + YearMonth addMonths(long months) + { + auto newYear = year; + newYear += months / 12; + months %= 12; + auto newMonth = month; + newMonth += months; + + if (months < 0) + { + if (newMonth < 1) + { + newMonth += 12; + --newYear; + } + } + else if (newMonth > 12) + { + newMonth -= 12; + ++newYear; + } + + return YearMonth(newYear, newMonth); + } + + /// + version(mir_test) + @safe unittest + { + auto ym0 = YearMonth(2020, Month.jan); + + auto ym1 = ym0.addMonths(15); + assert(ym1.year == 2021); + assert(ym1.month == Month.apr); + + auto ym2 = ym1.addMonths(-6); + assert(ym2.year == 2020); + assert(ym2.month == Month.oct); + + auto ym3 = YearMonth(2020, Month.dec).addMonths(3); + assert(ym3.year == 2021); + assert(ym3.month == Month.mar); + + // ym0 is left unchagned + assert(ym0.year == 2020); + assert(ym0.month == Month.jan); + } + + /// + @safe pure nothrow @nogc + YearMonth addQuarters(long quarters) + { + return addMonths(quarters * 3); + } + + /// + version(mir_test) + @safe unittest + { + auto ym0 = YearMonth(2020, Month.jan); + + auto ym1 = ym0.addQuarters(5); + assert(ym1.year == 2021); + assert(ym1.month == Month.apr); + + auto ym2 = ym1.addQuarters(-2); + assert(ym2.year == 2020); + assert(ym2.month == Month.oct); + + auto ym3 = YearMonth(2020, Month.dec).addQuarters(1); + assert(ym3.year == 2021); + assert(ym3.month == Month.mar); + + // ym0 is left unchagned + assert(ym0.year == 2020); + assert(ym0.month == Month.jan); + } + + /// + @safe pure nothrow @nogc + YearMonth addYears(long years) + { + auto newYear = this.year; + newYear += years; + return YearMonth(newYear, month); + } + + /// + version(mir_test) + @safe unittest + { + auto ym0 = YearMonth(2020, Month.jan); + + auto ym1 = ym0.addYears(1); + assert(ym1.year == 2021); + assert(ym1.month == Month.jan); + + // leaves ym0 unchanged + assert(ym0.year == 2020); + assert(ym0.month == Month.jan); + } + + private void setMonthOfYear(bool useExceptions = false)(int days) + { + immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; + + bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear); + + static if (useExceptions) + { + if (dayOutOfRange) { import mir.exception : toMutable; throw InvalidDay.toMutable; } + } + else + { + assert(!dayOutOfRange, "Invalid Day"); + } + + foreach (i; 1 .. lastDay.length) + { + if (days <= lastDay[i]) + { + month = cast(Month)(cast(int) Month.jan + i - 1); + return; + } + } + assert(0, "Invalid day of the year."); + } + + /// + version(mir_test) + @safe unittest + { + auto ym = YearMonth(2020, Month.feb); + ym.setMonthOfYear(10); + assert(ym.year == 2020); + assert(ym.month == Month.jan); + ym.setMonthOfYear(100); + assert(ym.year == 2020); + assert(ym.month == Month.apr); + ym.setMonthOfYear(200); + assert(ym.year == 2020); + assert(ym.month == Month.jul); + ym.setMonthOfYear(300); + assert(ym.year == 2020); + assert(ym.month == Month.oct); + } + + /// + int opBinary(string op : "-")(YearMonth rhs) + { + alias a = this; + alias b = rhs; + return (a.year - b.year) * 12 + a.month - b.month; + } + + /// + YearMonth opBinary(string op)(int rhs) + if (op == "+" || op == "-") + { + static if (op == "+") + return addMonths(rhs); + else + return addMonths(-rhs); + } + + /// + alias opBinaryRight(string op : "+") = opBinary!"+"; + + /// + ref YearMonth opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + static if (op == "+") + this = addMonths(rhs); + else + this = addMonths(-rhs); + return this; + } + + /// + @safe pure @nogc nothrow + version(mir_test) + unittest + { + auto x = YearMonth(2020, Month.mar); + auto x1 = x + 1; + assert(x1 == YearMonth(2020, Month.apr)); + auto x2 = x + 2; + assert(x2 == YearMonth(2020, Month.may)); + auto x3 = x + 3; + assert(x3 == YearMonth(2020, Month.jun)); + } + + /// + @safe pure @nogc nothrow + version(mir_test) + unittest { + auto ym = YearMonth(2020, Month.mar); + ym += 2; + assert(ym == YearMonth(2020, Month.may)); + ym -= 1; + assert(ym == YearMonth(2020, Month.apr)); + } + + /// Get a slice of YearMonths + @safe pure @nogc nothrow + version(mir_test) + unittest { + import mir.ndslice.topology: iota; + + static immutable result1 = [YearMonth(2020, Month.mar), YearMonth(2020, Month.apr), YearMonth(2020, Month.may), YearMonth(2020, Month.jun)]; + static immutable result2 = [YearMonth(2020, Month.mar), YearMonth(2020, Month.may), YearMonth(2020, Month.jul), YearMonth(2020, Month.sep)]; + + auto ym = YearMonth(2020, Month.mar); + + auto x = ym + 4.iota!uint; + assert(x == result1); + + // every other month + auto y = ym + iota!uint([4], 0, 2); + assert(y == result2); + } + + /++ + Day of the year this $(LREF Date) is on. + +/ + @property int dayOfYear(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc + { + if (month >= Month.jan && month <= Month.dec) + { + immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; + auto monthIndex = month - Month.jan; + + return lastDay[monthIndex] + day(assumePeriod); + } + assert(0, "Invalid month."); + } + + /// + version (mir_test) + @safe unittest + { + assert(YearMonth(1999, cast(Month) 1).dayOfYear == 1); + assert(YearMonth(1999, cast(Month) 12).dayOfYear(AssumePeriod.begin) == 335); + assert(YearMonth(1999, cast(Month) 12).dayOfYear(AssumePeriod.end) == 365); + assert(YearMonth(2000, cast(Month) 12).dayOfYear(AssumePeriod.begin) == 336); + assert(YearMonth(2000, cast(Month) 12).dayOfYear(AssumePeriod.end) == 366); + } + + /++ + Whether this $(LREF Date) is in a leap year. + +/ + @property bool isLeapYear() const @safe pure nothrow @nogc + { + return yearIsLeapYear(year); + } + + /// + version (mir_test) + @safe unittest + { + assert(YearMonth(1999, cast(Month) 12).isLeapYear == false); + assert(YearMonth(2000, cast(Month) 12).isLeapYear == true); + } + + /++ + The last day in the month that this $(LREF Date) is in. + +/ + @property ubyte daysInMonth() const @safe pure nothrow @nogc + { + return maxDay(year, month); + } + + /// + version(mir_test) + @safe unittest + { + assert(YearMonth(2020, Month.dec).daysInMonth == 31); + } + + /++ + Whether the current year is a date in A.D. + +/ + @property bool isAD() const @safe pure nothrow @nogc + { + return year > 0; + } + + /// + version(mir_test) + @safe unittest + { + assert(YearMonth(2020, Month.jan).isAD == true); + } +} + +/// +enum Quarter : short +{ + /// + q1 = 1, + /// + q2, + /// + q3, + /// + q4, +} + +/++ +Returns the quarter for a given month. + +Params: + month = month + ++/ +@safe pure @nogc nothrow +Quarter quarter(Month month) +{ + return cast(Quarter)((cast(ubyte)month - 1) / 3 + 1); +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest { + assert(Month.jan.quarter == Quarter.q1); + assert(Month.feb.quarter == Quarter.q1); + assert(Month.mar.quarter == Quarter.q1); + assert(Month.apr.quarter == Quarter.q2); + assert(Month.may.quarter == Quarter.q2); + assert(Month.jun.quarter == Quarter.q2); + assert(Month.jul.quarter == Quarter.q3); + assert(Month.aug.quarter == Quarter.q3); + assert(Month.sep.quarter == Quarter.q3); + assert(Month.oct.quarter == Quarter.q4); + assert(Month.nov.quarter == Quarter.q4); + assert(Month.dec.quarter == Quarter.q4); +} + +private +@safe pure @nogc nothrow +Month monthInQuarter(Quarter quarter, AssumePeriod assumePeriod = AssumePeriod.begin) +{ + assert (assumePeriod == AssumePeriod.begin || assumePeriod == AssumePeriod.end); + return cast(Month) ((cast(byte)quarter - 1) * 3 + 1 + 2 * assumePeriod); +} + +version(mir_test) +@safe pure @nogc nothrow +unittest { + assert(Quarter.q1.monthInQuarter == Month.jan); + assert(Quarter.q1.monthInQuarter(AssumePeriod.end) == Month.mar); + assert(Quarter.q2.monthInQuarter == Month.apr); + assert(Quarter.q2.monthInQuarter(AssumePeriod.end) == Month.jun); + assert(Quarter.q3.monthInQuarter == Month.jul); + assert(Quarter.q3.monthInQuarter(AssumePeriod.end) == Month.sep); + assert(Quarter.q4.monthInQuarter == Month.oct); + assert(Quarter.q4.monthInQuarter(AssumePeriod.end) == Month.dec); +} + +/// Represents a date as a pair of years and quarters. +@serdeProxy!Timestamp +struct YearQuarter +{ + short year = 1; + Quarter quarter = Quarter.q1; + + /// + @property Month month(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc + { + return quarter.monthInQuarter(assumePeriod); + } + + /// + version (mir_test) + @safe unittest + { + auto yq = YearQuarter(2000, Quarter.q4); + assert(yq.month == 10); + assert(yq.month(AssumePeriod.end) == 12); + } + + /// + @property ubyte day(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc + { + final switch (assumePeriod) + { + case AssumePeriod.begin: + return 1; + case AssumePeriod.end: + return daysInMonth; + } + } + + /// + version (mir_test) + @safe unittest + { + auto yq = YearQuarter(2000, Quarter.q4); + assert(yq.day == 1); + assert(yq.day(AssumePeriod.end) == 31); + } + + /// + Timestamp timestamp() @safe pure nothrow @nogc const @property + { + return Timestamp(year, cast(ubyte)month); + } + + /// + version(mir_test) + unittest + { + import mir.timestamp; + auto yq = YearQuarter(2020, Quarter.q2); + auto ts = yq.timestamp; + } + + /// + alias opCast(T : Timestamp) = timestamp; + + /// + version(mir_test) + unittest + { + import mir.timestamp; + auto yq = YearQuarter(2020, Quarter.q2); + auto timestamp = cast(Timestamp) yq; + } + + /// + this(short year, Quarter quarter) @safe pure nothrow @nogc + { + this.year = year; + this.quarter = quarter; + } + + /// + version (mir_test) + @safe unittest + { + auto yq = YearQuarter(2000, Quarter.q4); + } + + /// + this(short year, Month month) @safe pure nothrow @nogc + { + this.year = year; + this.quarter = month.quarter; + } + + /// + version (mir_test) + @safe unittest + { + auto yq = YearQuarter(2000, Month.dec); + } + + /// + this(Date date) @safe pure nothrow @nogc + { + this = date.yearQuarter; + } + + /// + version (mir_test) + @safe unittest + { + auto yq = YearQuarter(Date(2000, Month.dec, 31)); + } + + /// + this(YearMonthDay yearMonthDay) @safe pure nothrow @nogc + { + with(yearMonthDay) this(year, quarter); + } + + /// + version (mir_test) + @safe unittest + { + auto ym = YearQuarter(YearMonthDay(2000, Month.dec, 31)); + } + + /// + this(YearMonth yearMonth) @safe pure nothrow @nogc + { + with(yearMonth) this(year, quarter); + } + + /// + version (mir_test) + @safe unittest + { + auto yq = YearQuarter(YearMonth(2000, Month.dec)); + } + + version(D_Exceptions) + /// + this(Timestamp timestamp) @safe pure @nogc + { + if (timestamp.precision != Timestamp.Precision.month) + { + static immutable exc = new Exception("YearMonth: invalid timestamp precision"); + { import mir.exception : toMutable; throw exc.toMutable; } + } + with(timestamp) this(year, cast(Month)month); + } + + /// + version(mir_test) + @safe unittest + { + import mir.timestamp; + auto ts = Timestamp(2020, 4); + auto yq = YearQuarter(ts); + } + + /// + deprecated("please use addQuarters instead") + @safe pure nothrow @nogc + ref YearQuarter add(string units : "quarters")(long quarters) + { + auto years = quarters / 4; + quarters %= 4; + auto newQuarter = quarter + quarters; + + if (quarters < 0) + { + if (newQuarter < 1) + { + newQuarter += 4; + --years; + } + } + else if (newQuarter > 4) + { + newQuarter -= 4; + ++years; + } + + year += years; + quarter = cast(Quarter) newQuarter; + + return this; + } + + /// + version(mir_test_deprecated) + @safe unittest + { + auto yq0 = YearQuarter(2020, Quarter.q1); + + yq0.add!"quarters"(1); + assert(yq0.year == 2020); + assert(yq0.quarter == Quarter.q2); + + auto yq1 = yq0.add!"quarters"(1); + assert(yq1.year == 2020); + assert(yq1.quarter == Quarter.q3); + + // also changes yq0 + assert(yq0.year == 2020); + assert(yq0.quarter == Quarter.q3); + + yq1.add!"quarters"(2); + assert(yq1.year == 2021); + assert(yq1.quarter == Quarter.q1); + + yq1.add!"quarters"(-5); + assert(yq1.year == 2019); + assert(yq1.quarter == Quarter.q4); + } + + /// + deprecated("please use addYears instead") + @safe pure nothrow @nogc + ref YearQuarter add(string units : "years")(long years) + { + year += years; + return this; + } + + /// + version(mir_test_deprecated) + @safe unittest + { + auto yq0 = YearQuarter(2020, Quarter.q1); + + yq0.add!"years"(1); + assert(yq0.year == 2021); + assert(yq0.quarter == Quarter.q1); + + auto yq1 = yq0.add!"years"(1); + assert(yq1.year == 2022); + assert(yq1.quarter == Quarter.q1); + + // also changes yq0 + assert(yq0.year == 2022); + assert(yq0.quarter == Quarter.q1); + } + + /// + @safe pure nothrow @nogc + YearQuarter addQuarters(long quarters) + { + auto years = quarters / 4; + auto newYear = year; + newYear += years; + quarters %= 4; + auto newQuarter = quarter + quarters; + + if (quarters < 0) + { + if (newQuarter < 1) + { + newQuarter += 4; + --newYear; + } + } + else if (newQuarter > 4) + { + newQuarter -= 4; + ++newYear; + } + + return YearQuarter(newYear, cast(Quarter) newQuarter); + } + + /// + version(mir_test) + @safe unittest + { + auto yq0 = YearQuarter(2020, Quarter.q1); + + auto yq1 = yq0.addQuarters(5); + assert(yq1.year == 2021); + assert(yq1.quarter == Quarter.q2); + + auto yq2 = yq1.addQuarters(-2); + assert(yq2.year == 2020); + assert(yq2.quarter == Quarter.q4); + + auto yq3 = YearQuarter(2020, Quarter.q4).addQuarters(1); + assert(yq3.year == 2021); + assert(yq3.quarter == Quarter.q1); + + // yq0 is left unchagned + assert(yq0.year == 2020); + assert(yq0.quarter == Quarter.q1); + } + + /// + @safe pure nothrow @nogc + YearQuarter addYears(long years) + { + auto newYear = this.year; + newYear += years; + return YearQuarter(newYear, quarter); + } + + /// + version(mir_test) + @safe unittest + { + auto yq0 = YearQuarter(2020, Quarter.q1); + + auto yq1 = yq0.addYears(1); + assert(yq1.year == 2021); + assert(yq1.quarter == Quarter.q1); + + // leaves yq0 unchanged + assert(yq0.year == 2020); + assert(yq0.quarter == Quarter.q1); + } + + private void setQuarterOfYear(bool useExceptions = false)(int days) + { + immutable int[] lastDay = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap; + + bool dayOutOfRange = days <= 0 || days > (isLeapYear ? daysInLeapYear : daysInYear); + + static if (useExceptions) + { + if (dayOutOfRange) { import mir.exception : toMutable; throw InvalidDay.toMutable; } + } + else + { + assert(!dayOutOfRange, "Invalid Day"); + } + + foreach (i; 1 .. lastDay.length) + { + if (days <= lastDay[i]) + { + quarter = cast(Quarter)(cast(int) Quarter.q1 + i - 1); + return; + } + } + assert(0, "Invalid day of the year."); + } + + /// + version(mir_test) + @safe unittest + { + auto yq = YearQuarter(2020, Quarter.q3); + yq.setQuarterOfYear(10); + assert(yq.year == 2020); + assert(yq.quarter == Quarter.q1); + yq.setQuarterOfYear(100); + assert(yq.year == 2020); + assert(yq.quarter == Quarter.q2); + yq.setQuarterOfYear(200); + assert(yq.year == 2020); + assert(yq.quarter == Quarter.q3); + yq.setQuarterOfYear(300); + assert(yq.year == 2020); + assert(yq.quarter == Quarter.q4); + } + + /// + int opBinary(string op : "-")(YearQuarter rhs) + { + alias a = this; + alias b = rhs; + return (a.year - b.year) * 4 + a.quarter - b.quarter; + } + + /// + YearQuarter opBinary(string op)(int rhs) + if (op == "+" || op == "-") + { + static if (op == "+") + return addQuarters(rhs); + else + return addQuarters(-rhs); + } + + /// + alias opBinaryRight(string op : "+") = opBinary!"+"; + + /// + ref YearQuarter opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + static if (op == "+") + this = addQuarters(rhs); + else + this = addQuarters(-rhs); + return this; + } + + /// + @safe pure @nogc nothrow + version(mir_test) + unittest + { + auto x = YearQuarter(2020, Quarter.q1); + auto x1 = x + 1; + assert(x1 == YearQuarter(2020, Quarter.q2)); + auto x2 = x + 2; + assert(x2 == YearQuarter(2020, Quarter.q3)); + auto x3 = x + 3; + assert(x3 == YearQuarter(2020, Quarter.q4)); + } + + /// + @safe pure @nogc nothrow + version(mir_test) + unittest { + auto yq = YearQuarter(2020, Quarter.q1); + yq += 2; + assert(yq == YearQuarter(2020, Quarter.q3)); + yq -= 1; + assert(yq == YearQuarter(2020, Quarter.q2)); + } + + /// Get a slice of YearQuarters + @safe pure @nogc nothrow + version(mir_test) + unittest { + import mir.ndslice.topology: iota; + + static immutable result1 = [YearQuarter(2020, Quarter.q1), YearQuarter(2020, Quarter.q2), YearQuarter(2020, Quarter.q3), YearQuarter(2020, Quarter.q4)]; + static immutable result2 = [YearQuarter(2020, Quarter.q1), YearQuarter(2020, Quarter.q3), YearQuarter(2021, Quarter.q1), YearQuarter(2021, Quarter.q3)]; + + auto yq = YearQuarter(2020, Quarter.q1); + + auto x = yq + 4.iota!uint; + assert(x == result1); + + // every other quarter + auto y = yq + iota!uint([4], 0, 2); + assert(y == result2); + } + + /++ + Day of the quarter this $(LREF Date) is on. + +/ + @property int dayOfQuarter(AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) const @safe pure nothrow @nogc + { + if (quarter >= Quarter.q1 && quarter <= Quarter.q4) + { + immutable int[] lastDayQuarter = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap; + auto quarterIndex = quarter - Quarter.q1; + immutable int[] lastDay = isLeapYear ? lastDayLeap : lastDayNonLeap; + auto monthIndex = month(assumePeriodMonth) - Month.jan; + + return lastDay[monthIndex] - lastDayQuarter[quarterIndex] + day(assumePeriodDay); + } + assert(0, "Invalid quarter."); + } + + /// ditto + @property int dayOfQuarter(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc + { + return dayOfQuarter(assumePeriod, assumePeriod); + } + + /// + version (mir_test) + @safe unittest + { + assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter == 1); + assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31); + assert(YearQuarter(1999, cast(Quarter) 1).dayOfQuarter(AssumePeriod.end) == 90); + + assert(YearQuarter(2000, cast(Quarter) 1).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31); + assert(YearQuarter(2000, cast(Quarter) 1).dayOfQuarter(AssumePeriod.end) == 91); + + assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter == 1); + assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter(AssumePeriod.begin, AssumePeriod.end) == 31); + assert(YearQuarter(2000, cast(Quarter) 4).dayOfQuarter(AssumePeriod.end) == 92); + } + + /++ + Day of the year this $(LREF Date) is on. + +/ + @property int dayOfYear(AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) const @safe pure nothrow @nogc + { + if (quarter >= Quarter.q1 && quarter <= Quarter.q4) + { + immutable int[] lastDayQuarter = isLeapYear ? lastDayQuarterLeap : lastDayQuarterNonLeap; + auto quarterIndex = quarter - Quarter.q1; + + return lastDayQuarter[quarterIndex] + dayOfQuarter(assumePeriodMonth, assumePeriodDay); + } + assert(0, "Invalid quarter."); + } + + /// ditto + @property int dayOfYear(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc + { + return dayOfYear(assumePeriod, assumePeriod); + } + + /// + version (mir_test) + @safe unittest + { + assert(YearQuarter(1999, cast(Quarter) 1).dayOfYear == 1); + assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear == 274); + assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear(AssumePeriod.begin, AssumePeriod.end) == 304); + assert(YearQuarter(1999, cast(Quarter) 4).dayOfYear(AssumePeriod.end) == 365); + assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear == 275); + assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear(AssumePeriod.begin, AssumePeriod.end) == 305); + assert(YearQuarter(2000, cast(Quarter) 4).dayOfYear(AssumePeriod.end) == 366); + } + + /++ + Whether this $(LREF Date) is in a leap year. + +/ + @property bool isLeapYear() const @safe pure nothrow @nogc + { + return yearIsLeapYear(year); + } + + /// + version (mir_test) + @safe unittest + { + assert(YearQuarter(1999, cast(Quarter) 4).isLeapYear == false); + assert(YearQuarter(2000, cast(Quarter) 4).isLeapYear == true); + } + + /++ + The last day in the month that this $(LREF Date) is in. + +/ + @property ubyte daysInMonth(AssumePeriod assumePeriod = AssumePeriod.begin) const @safe pure nothrow @nogc + { + return maxDay(year, month(assumePeriod)); + } + + /// + version(mir_test) + @safe unittest + { + auto yq = YearQuarter(2020, Quarter.q3); + assert(yq.daysInMonth == 31); + assert(yq.daysInMonth(AssumePeriod.end) == 30); + } + + /++ + Whether the current year is a date in A.D. + +/ + @property bool isAD() const @safe pure nothrow @nogc + { + return year > 0; + } + + /// + version(mir_test) + @safe unittest + { + assert(YearQuarter(2020, Quarter.q1).isAD == true); + } +} + +/++ + Represents a date in the + $(HTTP en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, Proleptic + Gregorian Calendar) ranging from 32,768 B.C. to 32,767 A.D. Positive years + are A.D. Non-positive years are B.C. + + Year, month, and day are kept separately internally so that $(D Date) is + optimized for calendar-based operations. + + $(D Date) uses the Proleptic Gregorian Calendar, so it assumes the Gregorian + leap year calculations for its entire length. As per + $(HTTP en.wikipedia.org/wiki/ISO_8601, ISO 8601), it treats 1 B.C. as + year 0, i.e. 1 B.C. is 0, 2 B.C. is -1, etc. Use $(LREF yearBC) to use B.C. + as a positive integer with 1 B.C. being the year prior to 1 A.D. + + Year 0 is a leap year. + +/ +// extern(C++, "boost", "gregorian") +// extern(C++, class) +extern(C++, "mir") +@serdeProxy!YearMonthDay +struct Date +{ +extern(D): +public: + + private enum _julianShift = 1_721_425; + + /// + uint toHash() @safe pure nothrow @nogc const scope + { + return _dayNumber; + } + + /++ + Throws: + $(LREF DateTimeException) if the resulting + $(LREF Date) would not be valid. + + Params: + _year = Year of the Gregorian Calendar. Positive values are A.D. + Non-positive values are B.C. with year 0 being the year + prior to 1 A.D. + _month = Month of the year (January is 1). + _day = Day of the month. + +/ + pragma(inline, false) + static Date trustedCreate(int _year, int _month, int _day) @safe pure @nogc nothrow + { + Date ret; + immutable int[] lastDay = yearIsLeapYear(_year) ? lastDayLeap : lastDayNonLeap; + auto monthIndex = _month - Month.jan; + + const dayOfYear = lastDay[monthIndex] + _day; + + if (_month >= Month.jan && _month <= Month.dec) {} else + assert(0, "Invalid month."); + if (_year > 0) + { + if (_year == 1) + { + ret._dayNumber = dayOfYear; + goto R; + } + + int years = _year - 1; + auto days = (years / 400) * daysIn400Years; + years %= 400; + + days += (years / 100) * daysIn100Years; + years %= 100; + + days += (years / 4) * daysIn4Years; + years %= 4; + + days += years * daysInYear; + + days += dayOfYear; + + ret._dayNumber = days; + } + else if (_year == 0) + { + ret._dayNumber = dayOfYear - daysInLeapYear; + } + else + { + int years = _year; + auto days = (years / 400) * daysIn400Years; + years %= 400; + + days += (years / 100) * daysIn100Years; + years %= 100; + + days += (years / 4) * daysIn4Years; + years %= 4; + + if (years < 0) + { + days -= daysInLeapYear; + ++years; + + days += years * daysInYear; + + days -= daysInYear - dayOfYear; + } + else + days -= daysInLeapYear - dayOfYear; + + ret._dayNumber = days; + } + R: + ret._dayNumber -= 1; + return ret; + } + + /// + Timestamp timestamp() @safe pure nothrow @nogc const @property + { + return yearMonthDay.timestamp; + } + + /// + version(mir_test) + @safe unittest + { + import mir.timestamp; + auto d1 = Date(2020, Month.may, 15); + auto ts2 = d1.timestamp; + } + + version(D_Exceptions) + /// + this(Timestamp timestamp) @safe pure @nogc + { + if (timestamp.precision != Timestamp.Precision.day) + { + static immutable exc = new Exception("Date: invalid timestamp precision"); + { import mir.exception : toMutable; throw exc.toMutable; } + } + with(timestamp) this(year, cast(Month)month, day); + } + + /// + version(mir_test) + @safe unittest + { + import mir.timestamp; + auto ts = Date(2020, Month.may, 15).timestamp; + auto d2 = Date(ts); + } + + version(D_Exceptions) + /// + this(scope const(char)[] str) @safe pure @nogc + { + this = fromString(str); + } + + /// + version(mir_test) + @safe unittest + { + auto d = Date("2020-12-31"); + } + + version(D_Exceptions) + /// + this(YearMonthDay ymd) @safe pure @nogc + { + with(ymd) this(year, month, day); + } + + /// + version(mir_test) + @safe unittest + { + auto d = Date(YearMonthDay(2020, Month.may, 31)); + } + + version(D_Exceptions) + /// + this(YearQuarter yq, AssumePeriod assumePeriodMonth, AssumePeriod assumePeriodDay) @safe pure @nogc + { + with(yq) this(year, month(assumePeriodMonth), day(assumePeriodDay)); + } + + version(D_Exceptions) + /// + this(YearQuarter yq, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure @nogc + { + this(yq, assumePeriod, assumePeriod); + } + + /// + version(mir_test) + @safe unittest + { + auto d1 = Date(YearQuarter(2020, Quarter.q2)); + auto d2 = Date(YearQuarter(2020, Quarter.q2), AssumePeriod.end); + } + + version(D_Exceptions) + /// + this(YearMonth ym, AssumePeriod assumePeriod = AssumePeriod.begin) @safe pure @nogc nothrow + { + with(ym) this = trustedCreate(year, month, day(assumePeriod)); + } + + /// + version(mir_test) + @safe unittest + { + auto d1 = Date(YearMonth(2020, Month.may)); + auto d2 = Date(YearMonth(2020, Month.may), AssumePeriod.end); + } + + version(D_Exceptions) + /// + this(int _year, int _month, int _day) @safe pure @nogc + { + if (!valid!"months"(_month)) + { import mir.exception : toMutable; throw InvalidMonth.toMutable; } + if (!valid!"days"(_year, cast(Month) _month, _day)) + { import mir.exception : toMutable; throw InvalidDay.toMutable; } + this = trustedCreate(_year, _month, _day); + } + + /// + static bool fromYMD(int _year, int _month, int _day, out Date value) @safe pure nothrow @nogc + { + if (valid!"months"(_month) && valid!"days"(_year, cast(Month) _month, _day)) + { + value = trustedCreate(_year, _month, _day); + return true; + } + return false; + } + + version (mir_test) + @safe unittest + { + import std.exception : assertNotThrown; + // assert(Date(0, 12, 31) == Date.init); + + // Test A.D. + assertThrown!DateTimeException(Date(1, 0, 1)); + assertThrown!DateTimeException(Date(1, 1, 0)); + assertThrown!DateTimeException(Date(1999, 13, 1)); + assertThrown!DateTimeException(Date(1999, 1, 32)); + assertThrown!DateTimeException(Date(1999, 2, 29)); + assertThrown!DateTimeException(Date(2000, 2, 30)); + assertThrown!DateTimeException(Date(1999, 3, 32)); + assertThrown!DateTimeException(Date(1999, 4, 31)); + assertThrown!DateTimeException(Date(1999, 5, 32)); + assertThrown!DateTimeException(Date(1999, 6, 31)); + assertThrown!DateTimeException(Date(1999, 7, 32)); + assertThrown!DateTimeException(Date(1999, 8, 32)); + assertThrown!DateTimeException(Date(1999, 9, 31)); + assertThrown!DateTimeException(Date(1999, 10, 32)); + assertThrown!DateTimeException(Date(1999, 11, 31)); + assertThrown!DateTimeException(Date(1999, 12, 32)); + + assertNotThrown!DateTimeException(Date(1999, 1, 31)); + assertNotThrown!DateTimeException(Date(1999, 2, 28)); + assertNotThrown!DateTimeException(Date(2000, 2, 29)); + assertNotThrown!DateTimeException(Date(1999, 3, 31)); + assertNotThrown!DateTimeException(Date(1999, 4, 30)); + assertNotThrown!DateTimeException(Date(1999, 5, 31)); + assertNotThrown!DateTimeException(Date(1999, 6, 30)); + assertNotThrown!DateTimeException(Date(1999, 7, 31)); + assertNotThrown!DateTimeException(Date(1999, 8, 31)); + assertNotThrown!DateTimeException(Date(1999, 9, 30)); + assertNotThrown!DateTimeException(Date(1999, 10, 31)); + assertNotThrown!DateTimeException(Date(1999, 11, 30)); + assertNotThrown!DateTimeException(Date(1999, 12, 31)); + + // Test B.C. + assertNotThrown!DateTimeException(Date(0, 1, 1)); + assertNotThrown!DateTimeException(Date(-1, 1, 1)); + assertNotThrown!DateTimeException(Date(-1, 12, 31)); + assertNotThrown!DateTimeException(Date(-1, 2, 28)); + assertNotThrown!DateTimeException(Date(-4, 2, 29)); + + assertThrown!DateTimeException(Date(-1, 2, 29)); + assertThrown!DateTimeException(Date(-2, 2, 29)); + assertThrown!DateTimeException(Date(-3, 2, 29)); + } + + + /++ + Params: + day = Julian day. + +/ + deprecated("Use `fromDayNumber` adjusted by -1_721_426") + this(int day) @safe pure nothrow @nogc + { + _dayNumber = day - (1 + _julianShift); + } + + version (mir_test) + @safe unittest + { + import std.range : chain; + + // Test A.D. + // foreach (gd; chain(testGregDaysBC, testGregDaysAD)) + // assert(Date(gd.day) == gd.date); + } + + + /++ + Compares this $(LREF Date) with the given $(LREF Date). + + Returns: + $(BOOKTABLE, + $(TR $(TD this < rhs) $(TD < 0)) + $(TR $(TD this == rhs) $(TD 0)) + $(TR $(TD this > rhs) $(TD > 0)) + ) + +/ + int opCmp(Date rhs) const @safe pure nothrow @nogc + { + return this._dayNumber - rhs._dayNumber; + } + + version (mir_test) + @safe unittest + { + // Test A.D. + // assert(Date(0, 12, 31).opCmp(Date.init) == 0); + + assert(Date(1999, 1, 1).opCmp(Date(1999, 1, 1)) == 0); + assert(Date(1, 7, 1).opCmp(Date(1, 7, 1)) == 0); + assert(Date(1, 1, 6).opCmp(Date(1, 1, 6)) == 0); + + assert(Date(1999, 7, 1).opCmp(Date(1999, 7, 1)) == 0); + assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 6)) == 0); + + assert(Date(1, 7, 6).opCmp(Date(1, 7, 6)) == 0); + + assert(Date(1999, 7, 6).opCmp(Date(2000, 7, 6)) < 0); + assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 6)) > 0); + assert(Date(1999, 7, 6).opCmp(Date(1999, 8, 6)) < 0); + assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 6)) > 0); + assert(Date(1999, 7, 6).opCmp(Date(1999, 7, 7)) < 0); + assert(Date(1999, 7, 7).opCmp(Date(1999, 7, 6)) > 0); + + assert(Date(1999, 8, 7).opCmp(Date(2000, 7, 6)) < 0); + assert(Date(2000, 8, 6).opCmp(Date(1999, 7, 7)) > 0); + assert(Date(1999, 7, 7).opCmp(Date(2000, 7, 6)) < 0); + assert(Date(2000, 7, 6).opCmp(Date(1999, 7, 7)) > 0); + assert(Date(1999, 7, 7).opCmp(Date(1999, 8, 6)) < 0); + assert(Date(1999, 8, 6).opCmp(Date(1999, 7, 7)) > 0); + + // Test B.C. + assert(Date(0, 1, 1).opCmp(Date(0, 1, 1)) == 0); + assert(Date(-1, 1, 1).opCmp(Date(-1, 1, 1)) == 0); + assert(Date(-1, 7, 1).opCmp(Date(-1, 7, 1)) == 0); + assert(Date(-1, 1, 6).opCmp(Date(-1, 1, 6)) == 0); + + assert(Date(-1999, 7, 1).opCmp(Date(-1999, 7, 1)) == 0); + assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 6)) == 0); + + assert(Date(-1, 7, 6).opCmp(Date(-1, 7, 6)) == 0); + + assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 6)) < 0); + assert(Date(-1999, 7, 6).opCmp(Date(-2000, 7, 6)) > 0); + assert(Date(-1999, 7, 6).opCmp(Date(-1999, 8, 6)) < 0); + assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 6)) > 0); + assert(Date(-1999, 7, 6).opCmp(Date(-1999, 7, 7)) < 0); + assert(Date(-1999, 7, 7).opCmp(Date(-1999, 7, 6)) > 0); + + assert(Date(-2000, 8, 6).opCmp(Date(-1999, 7, 7)) < 0); + assert(Date(-1999, 8, 7).opCmp(Date(-2000, 7, 6)) > 0); + assert(Date(-2000, 7, 6).opCmp(Date(-1999, 7, 7)) < 0); + assert(Date(-1999, 7, 7).opCmp(Date(-2000, 7, 6)) > 0); + assert(Date(-1999, 7, 7).opCmp(Date(-1999, 8, 6)) < 0); + assert(Date(-1999, 8, 6).opCmp(Date(-1999, 7, 7)) > 0); + + // Test Both + assert(Date(-1999, 7, 6).opCmp(Date(1999, 7, 6)) < 0); + assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 6)) > 0); + + assert(Date(-1999, 8, 6).opCmp(Date(1999, 7, 6)) < 0); + assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 6)) > 0); + + assert(Date(-1999, 7, 7).opCmp(Date(1999, 7, 6)) < 0); + assert(Date(1999, 7, 6).opCmp(Date(-1999, 7, 7)) > 0); + + assert(Date(-1999, 8, 7).opCmp(Date(1999, 7, 6)) < 0); + assert(Date(1999, 7, 6).opCmp(Date(-1999, 8, 7)) > 0); + + assert(Date(-1999, 8, 6).opCmp(Date(1999, 6, 6)) < 0); + assert(Date(1999, 6, 8).opCmp(Date(-1999, 7, 6)) > 0); + + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date.opCmp(date) == 0); + assert(date.opCmp(cdate) == 0); + assert(date.opCmp(idate) == 0); + assert(cdate.opCmp(date) == 0); + assert(cdate.opCmp(cdate) == 0); + assert(cdate.opCmp(idate) == 0); + assert(idate.opCmp(date) == 0); + assert(idate.opCmp(cdate) == 0); + assert(idate.opCmp(idate) == 0); + } + + /++ + Day of the week this $(LREF Date) is on. + +/ + @property DayOfWeek dayOfWeek() const @safe pure nothrow @nogc + { + return getDayOfWeek(_dayNumber); + } + + version (mir_test) + @safe unittest + { + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.dayOfWeek == DayOfWeek.tue); + static assert(!__traits(compiles, cdate.dayOfWeek = DayOfWeek.sun)); + assert(idate.dayOfWeek == DayOfWeek.tue); + static assert(!__traits(compiles, idate.dayOfWeek = DayOfWeek.sun)); + } + + /++ + Params: + dayNumber = Day Of Gregorian Calendar Minus One + +/ + static Date fromDayNumber(int dayNumber) @safe pure nothrow @nogc + { + Date date; + date._dayNumber = dayNumber; + return date; + } + + /++ + Returns; + Day Of Gregorian Calendar Minus One + +/ + int dayNumber() @safe pure nothrow @nogc const @property + { + return _dayNumber; + } + + /++ + The Xth day of the Gregorian Calendar that this $(LREF Date) is on. + +/ + @property int dayOfGregorianCal() const @safe pure nothrow @nogc + { + return _dayNumber + 1; + } + + /// + version (mir_test) + @safe unittest + { + assert(Date(1, 1, 1).dayOfGregorianCal == 1); + assert(Date(1, 12, 31).dayOfGregorianCal == 365); + assert(Date(2, 1, 1).dayOfGregorianCal == 366); + + assert(Date(0, 12, 31).dayOfGregorianCal == 0); + assert(Date(0, 1, 1).dayOfGregorianCal == -365); + assert(Date(-1, 12, 31).dayOfGregorianCal == -366); + + assert(Date(2000, 1, 1).dayOfGregorianCal == 730_120); + assert(Date(2010, 12, 31).dayOfGregorianCal == 734_137); + } + + version (mir_test) + @safe unittest + { + import std.range : chain; + + foreach (gd; chain(testGregDaysBC, testGregDaysAD)) + assert(gd.date.dayOfGregorianCal == gd.day); + + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date.dayOfGregorianCal == 729_941); + assert(cdate.dayOfGregorianCal == 729_941); + assert(idate.dayOfGregorianCal == 729_941); + } + + /++ + The Xth day of the Gregorian Calendar that this $(LREF Date) is on. + + Params: + day = The day of the Gregorian Calendar to set this $(LREF Date) to. + + Note: + Zero value corresponds to + +/ + @property void dayOfGregorianCal(int day) @safe pure nothrow @nogc + { + _dayNumber = day - 1; + } + + /// + version (mir_test) + @safe unittest + { + import mir.test; + auto date = Date.init; + assert(date == Date(1, 1, 1)); + + date.dayOfGregorianCal = 365; + assert(date == Date(1, 12, 31)); + + date.dayOfGregorianCal = 366; + assert(date == Date(2, 1, 1)); + + date.dayOfGregorianCal = 0; + assert(date == Date(0, 12, 31)); + + date.dayOfGregorianCal = -365; + assert(date == Date(-0, 1, 1)); + + date.dayOfGregorianCal = -366; + assert(date == Date(-1, 12, 31)); + + date.dayOfGregorianCal = 730_120; + assert(date == Date(2000, 1, 1)); + + date.dayOfGregorianCal = 734_137; + assert(date == Date(2010, 12, 31)); + } + + version (mir_test) + @safe unittest + { + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + date.dayOfGregorianCal = 187; + assert(date.dayOfGregorianCal == 187); + static assert(!__traits(compiles, cdate.dayOfGregorianCal = 187)); + static assert(!__traits(compiles, idate.dayOfGregorianCal = 187)); + } + + private enum uint _startDict = Date(1900, 1, 1)._dayNumber; // [ + private enum uint _endDict = Date(2040, 1, 1)._dayNumber; // ) + static immutable _dictYMD = () + { + YearMonthDay[Date._endDict - Date._startDict] dict; + foreach (uint i; 0 .. dict.length) + dict[i] = Date.fromDayNumber(i + Date._startDict).yearMonthDayImpl; + return dict; + }(); + + /// + YearMonthDay yearMonthDay() const @safe pure nothrow @nogc @property + { + uint day = _dayNumber; + if (day < _endDict) + { + import mir.checkedint: subu; + bool overflow; + auto index = subu(day, _startDict, overflow); + if (!overflow) + return _dictYMD[index]; + } + return yearMonthDayImpl; + } + + /// + YearQuarter yearQuarter() const @safe pure nothrow @nogc @property + { + uint day = _dayNumber; + if (day < _endDict) + { + return yearMonthDay().YearQuarter; + } + return yearQuarterImpl; + } + + /// + version(mir_test) + @safe unittest + { + auto d = Date(2020, Month.may, 31); + auto yq = d.yearQuarter; + assert(yq.year == 2020); + assert(yq.quarter == Quarter.q2); + } + + // + version(mir_test) + @safe unittest + { + auto d = Date(2050, Month.dec, 31); + auto yq = d.yearQuarter; + assert(yq.year == 2050); + assert(yq.quarter == Quarter.q4); + } + + /// + short year() const @safe pure nothrow @nogc @property + { + return yearQuarter.year; + } + + /// + Quarter quarter() const @safe pure nothrow @nogc @property + { + return yearQuarter.quarter; + } + + /// + Month month() const @safe pure nothrow @nogc @property + { + return yearMonthDay.month; + } + + /// + ubyte day() const @safe pure nothrow @nogc @property + { + return yearMonthDay.day; + } + + /// + version(mir_test) + @safe unittest + { + auto d = Date(2020, Month.may, 31); + assert(d.year == 2020); + assert(d.quarter == Quarter.q2); + assert(d.month == Month.may); + assert(d.day == 31); + } + + pragma(inline, false) + YearMonthDay yearMonthDayImpl() const @safe pure nothrow @nogc @property + { + YearMonthDay ymd; + int days = dayOfGregorianCal; + with(ymd) + if (days > 0) + { + int years = (days / daysIn400Years) * 400 + 1; + days %= daysIn400Years; + + { + immutable tempYears = days / daysIn100Years; + + if (tempYears == 4) + { + years += 300; + days -= daysIn100Years * 3; + } + else + { + years += tempYears * 100; + days %= daysIn100Years; + } + } + + years += (days / daysIn4Years) * 4; + days %= daysIn4Years; + + { + immutable tempYears = days / daysInYear; + + if (tempYears == 4) + { + years += 3; + days -= daysInYear * 3; + } + else + { + years += tempYears; + days %= daysInYear; + } + } + + if (days == 0) + { + year = cast(short)(years - 1); + month = Month.dec; + day = 31; + } + else + { + year = cast(short) years; + + setDayOfYear(days); + } + } + else if (days <= 0 && -days < daysInLeapYear) + { + year = 0; + + setDayOfYear(daysInLeapYear + days); + } + else + { + days += daysInLeapYear - 1; + int years = (days / daysIn400Years) * 400 - 1; + days %= daysIn400Years; + + { + immutable tempYears = days / daysIn100Years; + + if (tempYears == -4) + { + years -= 300; + days += daysIn100Years * 3; + } + else + { + years += tempYears * 100; + days %= daysIn100Years; + } + } + + years += (days / daysIn4Years) * 4; + days %= daysIn4Years; + + { + immutable tempYears = days / daysInYear; + + if (tempYears == -4) + { + years -= 3; + days += daysInYear * 3; + } + else + { + years += tempYears; + days %= daysInYear; + } + } + + if (days == 0) + { + year = cast(short)(years + 1); + month = Month.jan; + day = 1; + } + else + { + year = cast(short) years; + immutable newDoY = (yearIsLeapYear(year) ? daysInLeapYear : daysInYear) + days + 1; + + setDayOfYear(newDoY); + } + } + return ymd; + } + + + + pragma(inline, false) + YearQuarter yearQuarterImpl() const @safe pure nothrow @nogc @property + { + YearQuarter yq; + int days = dayOfGregorianCal; + with(yq) + if (days > 0) + { + int years = (days / daysIn400Years) * 400 + 1; + days %= daysIn400Years; + + { + immutable tempYears = days / daysIn100Years; + + if (tempYears == 4) + { + years += 300; + days -= daysIn100Years * 3; + } + else + { + years += tempYears * 100; + days %= daysIn100Years; + } + } + + years += (days / daysIn4Years) * 4; + days %= daysIn4Years; + + { + immutable tempYears = days / daysInYear; + + if (tempYears == 4) + { + years += 3; + days -= daysInYear * 3; + } + else + { + years += tempYears; + days %= daysInYear; + } + } + + if (days == 0) + { + year = cast(short)(years - 1); + quarter = Quarter.q4; + } + else + { + year = cast(short) years; + setQuarterOfYear(days); + } + } + else if (days <= 0 && -days < daysInLeapYear) + { + year = 0; + + setQuarterOfYear(daysInLeapYear + days); + } + else + { + days += daysInLeapYear - 1; + int years = (days / daysIn400Years) * 400 - 1; + days %= daysIn400Years; + + { + immutable tempYears = days / daysIn100Years; + + if (tempYears == -4) + { + years -= 300; + days += daysIn100Years * 3; + } + else + { + years += tempYears * 100; + days %= daysIn100Years; + } + } + + years += (days / daysIn4Years) * 4; + days %= daysIn4Years; + + { + immutable tempYears = days / daysInYear; + + if (tempYears == -4) + { + years -= 3; + days += daysInYear * 3; + } + else + { + years += tempYears; + days %= daysInYear; + } + } + + if (days == 0) + { + year = cast(short)(years + 1); + quarter = Quarter.q2; + } + else + { + year = cast(short) years; + immutable newDoY = (yearIsLeapYear(year) ? daysInLeapYear : daysInYear) + days + 1; + + setQuarterOfYear(newDoY); + } + } + return yq; + } + + version(mir_test) + @safe unittest + { + auto d = Date(2020, Month.may, 31); + auto yq = d.yearQuarterImpl; + } + + /++ + $(LREF Date) for the last day in the quarter that this $(LREF Date) is in. + +/ + @property Date endOfQuarter() const @safe pure nothrow @nogc + { + with(yearMonthDay) + { + int d = _dayNumber - day; + final switch (month) with(Month) + { + case jan: d += maxDay(year, jan); goto case; + case feb: d += maxDay(year, feb); goto case; + case mar: d += maxDay(year, mar); break; + + case apr: d += maxDay(year, apr); goto case; + case may: d += maxDay(year, may); goto case; + case jun: d += maxDay(year, jun); break; + + case jul: d += maxDay(year, jul); goto case; + case aug: d += maxDay(year, aug); goto case; + case sep: d += maxDay(year, sep); break; + + case oct: d += maxDay(year, oct); goto case; + case nov: d += maxDay(year, nov); goto case; + case dec: d += maxDay(year, dec); break; + } + return Date.fromDayNumber(d); + } + } + + /// + version (mir_test) + @safe unittest + { + assert(Date(1999, 1, 6).endOfQuarter == Date(1999, 3, 31)); + assert(Date(1999, 2, 7).endOfQuarter == Date(1999, 3, 31)); + assert(Date(2000, 2, 7).endOfQuarter == Date(2000, 3, 31)); + assert(Date(2000, 6, 4).endOfQuarter == Date(2000, 6, 30)); + } + + /++ + $(LREF Date) for the last day in the month that this $(LREF Date) is in. + +/ + @property Date endOfMonth() const @safe pure nothrow @nogc + { + with(yearMonthDay) + return Date.fromDayNumber(_dayNumber + maxDay(year, month) - day); + } + + /// + version (mir_test) + @safe unittest + { + assert(Date(1999, 1, 6).endOfMonth == Date(1999, 1, 31)); + assert(Date(1999, 2, 7).endOfMonth == Date(1999, 2, 28)); + assert(Date(2000, 2, 7).endOfMonth == Date(2000, 2, 29)); + assert(Date(2000, 6, 4).endOfMonth == Date(2000, 6, 30)); + } + + version (mir_test) + @safe unittest + { + // Test A.D. + assert(Date(1999, 1, 1).endOfMonth == Date(1999, 1, 31)); + assert(Date(1999, 2, 1).endOfMonth == Date(1999, 2, 28)); + assert(Date(2000, 2, 1).endOfMonth == Date(2000, 2, 29)); + assert(Date(1999, 3, 1).endOfMonth == Date(1999, 3, 31)); + assert(Date(1999, 4, 1).endOfMonth == Date(1999, 4, 30)); + assert(Date(1999, 5, 1).endOfMonth == Date(1999, 5, 31)); + assert(Date(1999, 6, 1).endOfMonth == Date(1999, 6, 30)); + assert(Date(1999, 7, 1).endOfMonth == Date(1999, 7, 31)); + assert(Date(1999, 8, 1).endOfMonth == Date(1999, 8, 31)); + assert(Date(1999, 9, 1).endOfMonth == Date(1999, 9, 30)); + assert(Date(1999, 10, 1).endOfMonth == Date(1999, 10, 31)); + assert(Date(1999, 11, 1).endOfMonth == Date(1999, 11, 30)); + assert(Date(1999, 12, 1).endOfMonth == Date(1999, 12, 31)); + + // Test B.C. + assert(Date(-1999, 1, 1).endOfMonth == Date(-1999, 1, 31)); + assert(Date(-1999, 2, 1).endOfMonth == Date(-1999, 2, 28)); + assert(Date(-2000, 2, 1).endOfMonth == Date(-2000, 2, 29)); + assert(Date(-1999, 3, 1).endOfMonth == Date(-1999, 3, 31)); + assert(Date(-1999, 4, 1).endOfMonth == Date(-1999, 4, 30)); + assert(Date(-1999, 5, 1).endOfMonth == Date(-1999, 5, 31)); + assert(Date(-1999, 6, 1).endOfMonth == Date(-1999, 6, 30)); + assert(Date(-1999, 7, 1).endOfMonth == Date(-1999, 7, 31)); + assert(Date(-1999, 8, 1).endOfMonth == Date(-1999, 8, 31)); + assert(Date(-1999, 9, 1).endOfMonth == Date(-1999, 9, 30)); + assert(Date(-1999, 10, 1).endOfMonth == Date(-1999, 10, 31)); + assert(Date(-1999, 11, 1).endOfMonth == Date(-1999, 11, 30)); + assert(Date(-1999, 12, 1).endOfMonth == Date(-1999, 12, 31)); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate.endOfMonth = Date(1999, 7, 30))); + static assert(!__traits(compiles, idate.endOfMonth = Date(1999, 7, 30))); + } + + /// + int opBinary(string op : "-")(Date rhs) const + { + return _dayNumber - rhs._dayNumber; + } + + /// + Date opBinary(string op : "+")(int rhs) const + { + return Date.fromDayNumber(_dayNumber + rhs); + } + + /// + Date opBinaryRight(string op : "+")(int rhs) const + { + return Date.fromDayNumber(_dayNumber + rhs); + } + + /// + Date opBinary(string op : "-")(int rhs) const + { + return Date.fromDayNumber(_dayNumber - rhs); + } + + /// + ref Date opOpAssign(string op)(int rhs) return @safe pure nothrow @nogc + if (op == "+" || op == "-") + { + static if (op == "+") + this._addDays(rhs); + else + this._addDays(-rhs); + return this; + } + + /// + @safe pure @nogc + version(mir_test) + unittest { + auto d = Date(2020, 1, 1); + d += 2; + assert(d == Date(2020, 1, 3)); + d -= 1; + assert(d == Date(2020, 1, 2)); + } + + + /// Get a slice of Dates + @safe pure @nogc + version(mir_test) + unittest { + import mir.ndslice.topology: iota, map; + + static immutable result1 = [Date(2020, Month.mar, 1), Date(2020, Month.mar, 2), Date(2020, Month.mar, 3), Date(2020, Month.mar, 4)]; + static immutable result2 = [Date(2020, Month.mar, 1), Date(2020, Month.mar, 3), Date(2020, Month.mar, 5), Date(2020, Month.mar, 7)]; + static immutable result3 = [Date(2020, Month.mar, 1), Date(2020, Month.apr, 1), Date(2020, Month.may, 1), Date(2020, Month.jun, 1)]; + static immutable result4 = [Date(2020, Month.mar, 1), Date(2020, Month.jun, 1), Date(2020, Month.sep, 1), Date(2020, Month.dec, 1)]; + static immutable result5 = [Date(2020, Month.mar, 1), Date(2021, Month.mar, 1), Date(2022, Month.mar, 1), Date(2023, Month.mar, 1)]; + + auto d = Date(2020, Month.mar, 1); + + auto x = d + 4.iota!uint; + assert(x == result1); + + // every other date + auto y = d + iota!uint([4], 0, 2); + assert(y == result2); + + // every month + auto z = (d.YearMonth + 4.iota!uint).map!Date; + assert(z == result3); + + // every quarter + auto a = (d.YearQuarter + 4.iota!uint).map!(a => a.Date(AssumePeriod.end, AssumePeriod.begin)); + assert(a == result4); + + // every year + auto b = (d.year + 4.iota!uint).map!(a => YearMonthDay(cast(short) a, Month.mar, 1).Date); + assert(b == result5); + } + + const nothrow @nogc pure @safe + Date add(string units)(long amount, AllowDayOverflow allowOverflow = AllowDayOverflow.yes) + { + with(yearMonthDay.add!units(amount)) return trustedCreate(year, month, day); + } + + /++ + The $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for this + $(LREF Date) at noon (since the Julian day changes at noon). + +/ + @property int julianDay() const @safe pure nothrow @nogc + { + return _dayNumber + (1 + _julianShift); + } + + version (mir_test) + @safe unittest + { + assert(Date(-4713, 11, 24).julianDay == 0); + assert(Date(0, 12, 31).julianDay == _julianShift); + assert(Date(1, 1, 1).julianDay == 1_721_426); + assert(Date(1582, 10, 15).julianDay == 2_299_161); + assert(Date(1858, 11, 17).julianDay == 2_400_001); + assert(Date(1982, 1, 4).julianDay == 2_444_974); + assert(Date(1996, 3, 31).julianDay == 2_450_174); + assert(Date(2010, 8, 24).julianDay == 2_455_433); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.julianDay == 2_451_366); + assert(idate.julianDay == 2_451_366); + } + + + /++ + The modified $(HTTP en.wikipedia.org/wiki/Julian_day, Julian day) for + any time on this date (since, the modified Julian day changes at + midnight). + +/ + @property long modJulianDay() const @safe pure nothrow @nogc + { + return julianDay - 2_400_001; + } + + version (mir_test) + @safe unittest + { + assert(Date(1858, 11, 17).modJulianDay == 0); + assert(Date(2010, 8, 24).modJulianDay == 55_432); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.modJulianDay == 51_365); + assert(idate.modJulianDay == 51_365); + } + + version(D_BetterC){} else + private string toStringImpl(alias fun)() const @safe pure nothrow + { + import mir.appender: UnsafeArrayBuffer; + char[16] buffer = void; + auto w = UnsafeArrayBuffer!char(buffer); + fun(w); + return w.data.idup; + } + + version(D_BetterC){} else + /++ + Converts this $(LREF Date) to a string with the format `YYYYMMDD`. + If `writer` is set, the resulting string will be written directly + to it. + + Returns: + A `string` when not using an output range; `void` otherwise. + +/ + string toISOString() const @safe pure nothrow + { + return toStringImpl!toISOString; + } + + /// + version (mir_test) + @safe unittest + { + assert(Date.init.toISOString == "null"); + assert(Date(2010, 7, 4).toISOString == "20100704"); + assert(Date(1998, 12, 25).toISOString == "19981225"); + assert(Date(0, 1, 5).toISOString == "00000105"); + assert(Date(-4, 1, 5).toISOString == "-00040105", Date(-4, 1, 5).toISOString()); + } + + version (mir_test) + @safe unittest + { + // Test A.D. + assert(Date(9, 12, 4).toISOString == "00091204"); + assert(Date(99, 12, 4).toISOString == "00991204"); + assert(Date(999, 12, 4).toISOString == "09991204"); + assert(Date(9999, 7, 4).toISOString == "99990704"); + assert(Date(10000, 10, 20).toISOString == "+100001020"); + + // Test B.C. + assert(Date(0, 12, 4).toISOString == "00001204"); + assert(Date(-9, 12, 4).toISOString == "-00091204"); + assert(Date(-99, 12, 4).toISOString == "-00991204"); + assert(Date(-999, 12, 4).toISOString == "-09991204"); + assert(Date(-9999, 7, 4).toISOString == "-99990704"); + assert(Date(-10000, 10, 20).toISOString == "-100001020"); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.toISOString == "19990706"); + assert(idate.toISOString == "19990706"); + } + + /// ditto + void toISOString(W)(scope ref W w) const scope + if (isOutputRange!(W, char)) + { + import mir.format: printZeroPad; + if(this == Date.init) + { + w.put("null"); + return; + } + with(yearMonthDay) + { + if (year >= 10_000) + w.put('+'); + w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6); + w.printZeroPad(cast(uint)month, 2); + w.printZeroPad(day, 2); + } + } + + version (mir_test) + @safe unittest + { + auto date = Date(1999, 7, 6); + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(date.toString); + assert(cdate.toString); + assert(idate.toString); + } + + version(D_BetterC){} else + /++ + Converts this $(LREF Date) to a string with the format `YYYY-MM-DD`. + If `writer` is set, the resulting string will be written directly + to it. + + Returns: + A `string` when not using an output range; `void` otherwise. + +/ + string toISOExtString() const @safe pure nothrow + { + return toStringImpl!toISOExtString; + } + + ///ditto + alias toString = toISOExtString; + + /// + version (mir_test) + @safe unittest + { + assert(Date.init.toISOExtString == "null"); + assert(Date(2010, 7, 4).toISOExtString == "2010-07-04"); + assert(Date(1998, 12, 25).toISOExtString == "1998-12-25"); + assert(Date(0, 1, 5).toISOExtString == "0000-01-05"); + assert(Date(-4, 1, 5).toISOExtString == "-0004-01-05"); + } + + version (mir_test) + @safe pure unittest + { + import std.array : appender; + + auto w = appender!(char[])(); + Date(2010, 7, 4).toISOString(w); + assert(w.data == "20100704"); + w.clear(); + Date(1998, 12, 25).toISOString(w); + assert(w.data == "19981225"); + } + + version (mir_test) + @safe unittest + { + // Test A.D. + assert(Date(9, 12, 4).toISOExtString == "0009-12-04"); + assert(Date(99, 12, 4).toISOExtString == "0099-12-04"); + assert(Date(999, 12, 4).toISOExtString == "0999-12-04"); + assert(Date(9999, 7, 4).toISOExtString == "9999-07-04"); + assert(Date(10000, 10, 20).toISOExtString == "+10000-10-20"); + + // Test B.C. + assert(Date(0, 12, 4).toISOExtString == "0000-12-04"); + assert(Date(-9, 12, 4).toISOExtString == "-0009-12-04"); + assert(Date(-99, 12, 4).toISOExtString == "-0099-12-04"); + assert(Date(-999, 12, 4).toISOExtString == "-0999-12-04"); + assert(Date(-9999, 7, 4).toISOExtString == "-9999-07-04"); + assert(Date(-10000, 10, 20).toISOExtString == "-10000-10-20"); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.toISOExtString == "1999-07-06"); + assert(idate.toISOExtString == "1999-07-06"); + } + + /// ditto + void toISOExtString(W)(scope ref W w) const scope + if (isOutputRange!(W, char)) + { + import mir.format: printZeroPad; + if(this == Date.init) + { + w.put("null"); + return; + } + with(yearMonthDay) + { + if (year >= 10_000) + w.put('+'); + w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6); + w.put('-'); + w.printZeroPad(cast(uint)month, 2); + w.put('-'); + w.printZeroPad(day, 2); + } + } + + version (mir_test) + @safe pure unittest + { + import std.array : appender; + + auto w = appender!(char[])(); + Date(2010, 7, 4).toISOExtString(w); + assert(w.data == "2010-07-04"); + w.clear(); + Date(-4, 1, 5).toISOExtString(w); + assert(w.data == "-0004-01-05"); + } + + version(D_BetterC){} else + /++ + Converts this $(LREF Date) to a string with the format `YYYY-Mon-DD`. + If `writer` is set, the resulting string will be written directly + to it. + + Returns: + A `string` when not using an output range; `void` otherwise. + +/ + string toSimpleString() const @safe pure nothrow + { + return toStringImpl!toSimpleString; + } + + /// + version (mir_test) + @safe unittest + { + assert(Date.init.toSimpleString == "null"); + assert(Date(2010, 7, 4).toSimpleString == "2010-Jul-04"); + assert(Date(1998, 12, 25).toSimpleString == "1998-Dec-25"); + assert(Date(0, 1, 5).toSimpleString == "0000-Jan-05"); + assert(Date(-4, 1, 5).toSimpleString == "-0004-Jan-05"); + } + + version (mir_test) + @safe unittest + { + // Test A.D. + assert(Date(9, 12, 4).toSimpleString == "0009-Dec-04"); + assert(Date(99, 12, 4).toSimpleString == "0099-Dec-04"); + assert(Date(999, 12, 4).toSimpleString == "0999-Dec-04"); + assert(Date(9999, 7, 4).toSimpleString == "9999-Jul-04"); + assert(Date(10000, 10, 20).toSimpleString == "+10000-Oct-20"); + + // Test B.C. + assert(Date(0, 12, 4).toSimpleString == "0000-Dec-04"); + assert(Date(-9, 12, 4).toSimpleString == "-0009-Dec-04"); + assert(Date(-99, 12, 4).toSimpleString == "-0099-Dec-04"); + assert(Date(-999, 12, 4).toSimpleString == "-0999-Dec-04"); + assert(Date(-9999, 7, 4).toSimpleString == "-9999-Jul-04"); + assert(Date(-10000, 10, 20).toSimpleString == "-10000-Oct-20"); + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + assert(cdate.toSimpleString == "1999-Jul-06"); + assert(idate.toSimpleString == "1999-Jul-06"); + } + + /// ditto + void toSimpleString(W)(scope ref W w) const scope + if (isOutputRange!(W, char)) + { + import mir.format: printZeroPad; + if(this == Date.init) + { + w.put("null"); + return; + } + with(yearMonthDay) + { + if (year >= 10_000) + w.put('+'); + w.printZeroPad(year, year >= 0 ? year < 10_000 ? 4 : 5 : year > -10_000 ? 5 : 6); + w.put('-'); + w.put(month.monthToString); + w.put('-'); + w.printZeroPad(day, 2); + } + } + + version (mir_test) + @safe pure unittest + { + import std.array : appender; + + auto w = appender!(char[])(); + Date(9, 12, 4).toSimpleString(w); + assert(w.data == "0009-Dec-04"); + w.clear(); + Date(-10000, 10, 20).toSimpleString(w); + assert(w.data == "-10000-Oct-20"); + } + + /++ + Creates a $(LREF Date) from a string with the format YYYYMMDD. + + Params: + str = A string formatted in the way that $(LREF .date.toISOString) formats dates. + value = (optional) result value. + + Throws: + $(LREF DateTimeException) if the given string is + not in the correct format or if the resulting $(LREF Date) would not + be valid. Two arguments overload is `nothrow`. + Returns: + `bool` on success for two arguments overload, and the resulting date for single argument overdload. + +/ + static bool fromISOString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc + if (isSomeChar!C) + { + import mir.parse: fromString; + + if (str.length < 8) + return false; + + auto yearStr = str[0 .. $ - 4]; + + if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) + return false; + + uint day, month; + int year; + + return + fromString(str[$ - 2 .. $], day) + && fromString(str[$ - 4 .. $ - 2], month) + && fromString(yearStr, year) + && fromYMD(year, month, day, value); + } + + /// ditto + static Date fromISOString(C)(scope const(C)[] str) @safe pure + if (isSomeChar!C) + { + Date ret; + if (fromISOString(str, ret)) + return ret; + { import mir.exception : toMutable; throw InvalidISOString.toMutable; } + } + + /// + version (mir_test) + @safe unittest + { + assert(Date.fromISOString("20100704") == Date(2010, 7, 4)); + assert(Date.fromISOString("19981225") == Date(1998, 12, 25)); + assert(Date.fromISOString("00000105") == Date(0, 1, 5)); + assert(Date.fromISOString("-00040105") == Date(-4, 1, 5)); + } + + version (mir_test) + @safe unittest + { + assertThrown!DateTimeException(Date.fromISOString("")); + assertThrown!DateTimeException(Date.fromISOString("990704")); + assertThrown!DateTimeException(Date.fromISOString("0100704")); + assertThrown!DateTimeException(Date.fromISOString("2010070")); + assertThrown!DateTimeException(Date.fromISOString("120100704")); + assertThrown!DateTimeException(Date.fromISOString("-0100704")); + assertThrown!DateTimeException(Date.fromISOString("+0100704")); + assertThrown!DateTimeException(Date.fromISOString("2010070a")); + assertThrown!DateTimeException(Date.fromISOString("20100a04")); + assertThrown!DateTimeException(Date.fromISOString("2010a704")); + + assertThrown!DateTimeException(Date.fromISOString("99-07-04")); + assertThrown!DateTimeException(Date.fromISOString("010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-07-0")); + assertThrown!DateTimeException(Date.fromISOString("12010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("-010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("+010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-07-0a")); + assertThrown!DateTimeException(Date.fromISOString("2010-0a-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-a7-04")); + assertThrown!DateTimeException(Date.fromISOString("2010/07/04")); + assertThrown!DateTimeException(Date.fromISOString("2010/7/04")); + assertThrown!DateTimeException(Date.fromISOString("2010/7/4")); + assertThrown!DateTimeException(Date.fromISOString("2010/07/4")); + assertThrown!DateTimeException(Date.fromISOString("2010-7-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-7-4")); + assertThrown!DateTimeException(Date.fromISOString("2010-07-4")); + + assertThrown!DateTimeException(Date.fromISOString("99Jul04")); + assertThrown!DateTimeException(Date.fromISOString("010Jul04")); + assertThrown!DateTimeException(Date.fromISOString("2010Jul0")); + assertThrown!DateTimeException(Date.fromISOString("12010Jul04")); + assertThrown!DateTimeException(Date.fromISOString("-010Jul04")); + assertThrown!DateTimeException(Date.fromISOString("+010Jul04")); + assertThrown!DateTimeException(Date.fromISOString("2010Jul0a")); + assertThrown!DateTimeException(Date.fromISOString("2010Jua04")); + assertThrown!DateTimeException(Date.fromISOString("2010aul04")); + + assertThrown!DateTimeException(Date.fromISOString("99-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0")); + assertThrown!DateTimeException(Date.fromISOString("12010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("-010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("+010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jul-0a")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jua-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jal-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-aul-04")); + + assertThrown!DateTimeException(Date.fromISOString("2010-07-04")); + assertThrown!DateTimeException(Date.fromISOString("2010-Jul-04")); + + assert(Date.fromISOString("19990706") == Date(1999, 7, 6)); + assert(Date.fromISOString("-19990706") == Date(-1999, 7, 6)); + assert(Date.fromISOString("+019990706") == Date(1999, 7, 6)); + assert(Date.fromISOString("19990706") == Date(1999, 7, 6)); + } + + // bug# 17801 + version (mir_test) + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + static foreach (C; AliasSeq!(char, wchar, dchar)) + { + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(Date.fromISOString(to!S("20121221")) == Date(2012, 12, 21)); + } + } + + /++ + Creates a $(LREF Date) from a string with the format YYYY-MM-DD. + + Params: + str = A string formatted in the way that $(LREF .date.toISOExtString) formats dates. + value = (optional) result value. + + Throws: + $(LREF DateTimeException) if the given string is + not in the correct format or if the resulting $(LREF Date) would not + be valid. Two arguments overload is `nothrow`. + Returns: + `bool` on success for two arguments overload, and the resulting date for single argument overdload. + +/ + static bool fromISOExtString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc + if (isSomeChar!C) + { + import mir.parse: fromString; + + if (str.length < 10 || str[$-3] != '-' || str[$-6] != '-') + return false; + + auto yearStr = str[0 .. $ - 6]; + + if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) + return false; + + uint day, month; + int year; + + return + fromString(str[$ - 2 .. $], day) + && fromString(str[$ - 5 .. $ - 3], month) + && fromString(yearStr, year) + && fromYMD(year, month, day, value); + } + + /// ditto + static Date fromISOExtString(C)(scope const(C)[] str) @safe pure + if (isSomeChar!C) + { + Date ret; + if (fromISOExtString(str, ret)) + return ret; + { import mir.exception : toMutable; throw InvalidISOExtendedString.toMutable; } + } + + /// + version (mir_test) + @safe unittest + { + assert(Date.fromISOExtString("2010-07-04") == Date(2010, 7, 4)); + assert(Date.fromISOExtString("1998-12-25") == Date(1998, 12, 25)); + assert(Date.fromISOExtString("0000-01-05") == Date(0, 1, 5)); + assert(Date.fromISOExtString("-0004-01-05") == Date(-4, 1, 5)); + } + + version (mir_test) + @safe unittest + { + assertThrown!DateTimeException(Date.fromISOExtString("")); + assertThrown!DateTimeException(Date.fromISOExtString("990704")); + assertThrown!DateTimeException(Date.fromISOExtString("0100704")); + assertThrown!DateTimeException(Date.fromISOExtString("120100704")); + assertThrown!DateTimeException(Date.fromISOExtString("-0100704")); + assertThrown!DateTimeException(Date.fromISOExtString("+0100704")); + assertThrown!DateTimeException(Date.fromISOExtString("2010070a")); + assertThrown!DateTimeException(Date.fromISOExtString("20100a04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010a704")); + + assertThrown!DateTimeException(Date.fromISOExtString("99-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("010-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0")); + assertThrown!DateTimeException(Date.fromISOExtString("12010-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("-010-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("+010-07-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-07-0a")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-0a-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-a7-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010/07/04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010/7/04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010/7/4")); + assertThrown!DateTimeException(Date.fromISOExtString("2010/07/4")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-7-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-7-4")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-07-4")); + + assertThrown!DateTimeException(Date.fromISOExtString("99Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("010Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0")); + assertThrown!DateTimeException(Date.fromISOExtString("12010Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("-010Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("+010Jul04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010Jul0a")); + assertThrown!DateTimeException(Date.fromISOExtString("2010Jua04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010aul04")); + + assertThrown!DateTimeException(Date.fromISOExtString("99-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0")); + assertThrown!DateTimeException(Date.fromISOExtString("12010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("-010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("+010-Jul-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-0a")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jua-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jal-04")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-aul-04")); + + assertThrown!DateTimeException(Date.fromISOExtString("20100704")); + assertThrown!DateTimeException(Date.fromISOExtString("2010-Jul-04")); + + assert(Date.fromISOExtString("1999-07-06") == Date(1999, 7, 6)); + assert(Date.fromISOExtString("-1999-07-06") == Date(-1999, 7, 6)); + assert(Date.fromISOExtString("+01999-07-06") == Date(1999, 7, 6)); + } + + // bug# 17801 + version (mir_test) + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + static foreach (C; AliasSeq!(char, wchar, dchar)) + { + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(Date.fromISOExtString(to!S("2012-12-21")) == Date(2012, 12, 21)); + } + } + + + /++ + Creates a $(LREF Date) from a string with the format YYYY-Mon-DD. + + Params: + str = A string formatted in the way that $(LREF .date.toSimpleString) formats dates. The function is case sensetive. + value = (optional) result value. + + Throws: + $(LREF DateTimeException) if the given string is + not in the correct format or if the resulting $(LREF Date) would not + be valid. Two arguments overload is `nothrow`. + Returns: + `bool` on success for two arguments overload, and the resulting date for single argument overdload. + +/ + static bool fromSimpleString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc + if (isSomeChar!C) + { + import mir.parse: fromString; + + if (str.length < 11 || str[$-3] != '-' || str[$-7] != '-') + return false; + + auto yearStr = str[0 .. $ - 7]; + + if ((yearStr[0] == '+' || yearStr[0] == '-') != (yearStr.length > 4)) + return false; + + Month month; + + switch (str[$ - 6 .. $ - 3]) + { + case "Jan": month = Month.jan; break; + case "Feb": month = Month.feb; break; + case "Mar": month = Month.mar; break; + case "Apr": month = Month.apr; break; + case "May": month = Month.may; break; + case "Jun": month = Month.jun; break; + case "Jul": month = Month.jul; break; + case "Aug": month = Month.aug; break; + case "Sep": month = Month.sep; break; + case "Oct": month = Month.oct; break; + case "Nov": month = Month.nov; break; + case "Dec": month = Month.dec; break; + default: return false; + } + + uint day; + int year; + + return + fromString(str[$ - 2 .. $], day) + && fromString(yearStr, year) + && fromYMD(year, month, day, value); + } + + /// ditto + static Date fromSimpleString(C)(scope const(C)[] str) @safe pure + if (isSomeChar!C) + { + Date ret; + if (fromSimpleString(str, ret)) + return ret; + throw new DateTimeException("Invalid Simple String"); + } + + /// + version (mir_test) + @safe unittest + { + assert(Date.fromSimpleString("2010-Jul-04") == Date(2010, 7, 4)); + assert(Date.fromSimpleString("1998-Dec-25") == Date(1998, 12, 25)); + assert(Date.fromSimpleString("0000-Jan-05") == Date(0, 1, 5)); + assert(Date.fromSimpleString("-0004-Jan-05") == Date(-4, 1, 5)); + } + + version (mir_test) + @safe unittest + { + assertThrown!DateTimeException(Date.fromSimpleString("")); + assertThrown!DateTimeException(Date.fromSimpleString("990704")); + assertThrown!DateTimeException(Date.fromSimpleString("0100704")); + assertThrown!DateTimeException(Date.fromSimpleString("2010070")); + assertThrown!DateTimeException(Date.fromSimpleString("120100704")); + assertThrown!DateTimeException(Date.fromSimpleString("-0100704")); + assertThrown!DateTimeException(Date.fromSimpleString("+0100704")); + assertThrown!DateTimeException(Date.fromSimpleString("2010070a")); + assertThrown!DateTimeException(Date.fromSimpleString("20100a04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010a704")); + + assertThrown!DateTimeException(Date.fromSimpleString("99-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("010-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0")); + assertThrown!DateTimeException(Date.fromSimpleString("12010-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("-010-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("+010-07-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-0a")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-0a-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-a7-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010/07/04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010/7/04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010/7/4")); + assertThrown!DateTimeException(Date.fromSimpleString("2010/07/4")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-7-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-7-4")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-4")); + + assertThrown!DateTimeException(Date.fromSimpleString("99Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("010Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0")); + assertThrown!DateTimeException(Date.fromSimpleString("12010Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("-010Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("+010Jul04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010Jul0a")); + assertThrown!DateTimeException(Date.fromSimpleString("2010Jua04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010aul04")); + + assertThrown!DateTimeException(Date.fromSimpleString("99-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("010-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0")); + assertThrown!DateTimeException(Date.fromSimpleString("12010-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("-010-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("+010-Jul-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jul-0a")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jua-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-Jal-04")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-aul-04")); + + assertThrown!DateTimeException(Date.fromSimpleString("20100704")); + assertThrown!DateTimeException(Date.fromSimpleString("2010-07-04")); + + assert(Date.fromSimpleString("1999-Jul-06") == Date(1999, 7, 6)); + assert(Date.fromSimpleString("-1999-Jul-06") == Date(-1999, 7, 6)); + assert(Date.fromSimpleString("+01999-Jul-06") == Date(1999, 7, 6)); + } + + // bug# 17801 + version (mir_test) + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + static foreach (C; AliasSeq!(char, wchar, dchar)) + { + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(Date.fromSimpleString(to!S("2012-Dec-21")) == Date(2012, 12, 21)); + } + } + + /++ + Creates a $(LREF Date) from a string with the format YYYY-MM-DD, YYYYMMDD, or YYYY-Mon-DD. + + Params: + str = A string formatted in the way that $(LREF .date.toISOExtString), $(LREF .date.toISOString), and $(LREF .date.toSimpleString) format dates. The function is case sensetive. + value = (optional) result value. + + Throws: + $(LREF DateTimeException) if the given string is + not in the correct format or if the resulting $(LREF Date) would not + be valid. Two arguments overload is `nothrow`. + Returns: + `bool` on success for two arguments overload, and the resulting date for single argument overdload. + +/ + static bool fromString(C)(scope const(C)[] str, out Date value) @safe pure nothrow @nogc + { + return fromISOExtString(str, value) + || fromISOString(str, value) + || fromSimpleString(str, value); + } + + /// + version (mir_test) + @safe pure @nogc unittest + { + assert(Date.fromString("2010-07-04") == Date(2010, 7, 4)); + assert(Date.fromString("20100704") == Date(2010, 7, 4)); + assert(Date.fromString("2010-Jul-04") == Date(2010, 7, 4)); + } + + /// ditto + static Date fromString(C)(scope const(C)[] str) @safe pure + if (isSomeChar!C) + { + Date ret; + if (fromString(str, ret)) + return ret; + { import mir.exception : toMutable; throw InvalidString.toMutable; } + } + + /++ + Returns the $(LREF Date) farthest in the past which is representable by + $(LREF Date). + +/ + @property static Date min() @safe pure nothrow @nogc + { + return Date.fromDayNumber(int.max); + } + + /++ + Returns the $(LREF Date) farthest in the future which is representable + by $(LREF Date). + +/ + @property static Date max() @safe pure nothrow @nogc + { + return Date.fromDayNumber(int.min); + } + +private: + + /+ + Whether the given values form a valid date. + + Params: + year = The year to test. + month = The month of the Gregorian Calendar to test. + day = The day of the month to test. + +/ + static bool _valid(int year, int month, int day) @safe pure nothrow @nogc + { + if (!valid!"months"(month)) + return false; + return valid!"days"(year, month, day); + } + + +package: + + /+ + Adds the given number of days to this $(LREF Date). A negative number + will subtract. + + The month will be adjusted along with the day if the number of days + added (or subtracted) would overflow (or underflow) the current month. + The year will be adjusted along with the month if the increase (or + decrease) to the month would cause it to overflow (or underflow) the + current year. + + $(D _addDays(numDays)) is effectively equivalent to + $(D date.dayOfGregorianCal = date.dayOfGregorianCal + days). + + Params: + days = The number of days to add to this Date. + +/ + ref Date _addDays(long days) return @safe pure nothrow @nogc + { + _dayNumber = cast(int)(_dayNumber + days); + return this; + } + + version (mir_test) + @safe unittest + { + // Test A.D. + { + auto date = Date(1999, 2, 28); + date._addDays(1); + assert(date == Date(1999, 3, 1)); + date._addDays(-1); + assert(date == Date(1999, 2, 28)); + } + + { + auto date = Date(2000, 2, 28); + date._addDays(1); + assert(date == Date(2000, 2, 29)); + date._addDays(1); + assert(date == Date(2000, 3, 1)); + date._addDays(-1); + assert(date == Date(2000, 2, 29)); + } + + { + auto date = Date(1999, 6, 30); + date._addDays(1); + assert(date == Date(1999, 7, 1)); + date._addDays(-1); + assert(date == Date(1999, 6, 30)); + } + + { + auto date = Date(1999, 7, 31); + date._addDays(1); + assert(date == Date(1999, 8, 1)); + date._addDays(-1); + assert(date == Date(1999, 7, 31)); + } + + { + auto date = Date(1999, 1, 1); + date._addDays(-1); + assert(date == Date(1998, 12, 31)); + date._addDays(1); + assert(date == Date(1999, 1, 1)); + } + + { + auto date = Date(1999, 7, 6); + date._addDays(9); + assert(date == Date(1999, 7, 15)); + date._addDays(-11); + assert(date == Date(1999, 7, 4)); + date._addDays(30); + assert(date == Date(1999, 8, 3)); + date._addDays(-3); + assert(date == Date(1999, 7, 31)); + } + + { + auto date = Date(1999, 7, 6); + date._addDays(365); + assert(date == Date(2000, 7, 5)); + date._addDays(-365); + assert(date == Date(1999, 7, 6)); + date._addDays(366); + assert(date == Date(2000, 7, 6)); + date._addDays(730); + assert(date == Date(2002, 7, 6)); + date._addDays(-1096); + assert(date == Date(1999, 7, 6)); + } + + // Test B.C. + { + auto date = Date(-1999, 2, 28); + date._addDays(1); + assert(date == Date(-1999, 3, 1)); + date._addDays(-1); + assert(date == Date(-1999, 2, 28)); + } + + { + auto date = Date(-2000, 2, 28); + date._addDays(1); + assert(date == Date(-2000, 2, 29)); + date._addDays(1); + assert(date == Date(-2000, 3, 1)); + date._addDays(-1); + assert(date == Date(-2000, 2, 29)); + } + + { + auto date = Date(-1999, 6, 30); + date._addDays(1); + assert(date == Date(-1999, 7, 1)); + date._addDays(-1); + assert(date == Date(-1999, 6, 30)); + } + + { + auto date = Date(-1999, 7, 31); + date._addDays(1); + assert(date == Date(-1999, 8, 1)); + date._addDays(-1); + assert(date == Date(-1999, 7, 31)); + } + + { + auto date = Date(-1999, 1, 1); + date._addDays(-1); + assert(date == Date(-2000, 12, 31)); + date._addDays(1); + assert(date == Date(-1999, 1, 1)); + } + + { + auto date = Date(-1999, 7, 6); + date._addDays(9); + assert(date == Date(-1999, 7, 15)); + date._addDays(-11); + assert(date == Date(-1999, 7, 4)); + date._addDays(30); + assert(date == Date(-1999, 8, 3)); + date._addDays(-3); + } + + { + auto date = Date(-1999, 7, 6); + date._addDays(365); + assert(date == Date(-1998, 7, 6)); + date._addDays(-365); + assert(date == Date(-1999, 7, 6)); + date._addDays(366); + assert(date == Date(-1998, 7, 7)); + date._addDays(730); + assert(date == Date(-1996, 7, 6)); + date._addDays(-1096); + assert(date == Date(-1999, 7, 6)); + } + + // Test Both + { + auto date = Date(1, 7, 6); + date._addDays(-365); + assert(date == Date(0, 7, 6)); + date._addDays(365); + assert(date == Date(1, 7, 6)); + date._addDays(-731); + assert(date == Date(-1, 7, 6)); + date._addDays(730); + assert(date == Date(1, 7, 5)); + } + + const cdate = Date(1999, 7, 6); + immutable idate = Date(1999, 7, 6); + static assert(!__traits(compiles, cdate._addDays(12))); + static assert(!__traits(compiles, idate._addDays(12))); + } + + int _dayNumber; +} + +/// ditto +deprecated("use `Date` instead") +alias date = Date; + +/++ + Returns the number of days from the current day of the week to the given + day of the week. If they are the same, then the result is 0. + Params: + currDoW = The current day of the week. + dow = The day of the week to get the number of days to. + +/ +int daysToDayOfWeek(DayOfWeek currDoW, DayOfWeek dow) @safe pure nothrow @nogc +{ + if (currDoW == dow) + return 0; + if (currDoW < dow) + return dow - currDoW; + return DayOfWeek.sun - currDoW + dow + 1; +} + +/// +version (mir_test) +@safe pure nothrow @nogc unittest +{ + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2); +} + +version (mir_test) +@safe unittest +{ + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sun) == 0); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.mon) == 1); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.tue) == 2); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.wed) == 3); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.thu) == 4); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.fri) == 5); + assert(daysToDayOfWeek(DayOfWeek.sun, DayOfWeek.sat) == 6); + + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sun) == 6); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.mon) == 0); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.tue) == 1); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.wed) == 2); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.thu) == 3); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.fri) == 4); + assert(daysToDayOfWeek(DayOfWeek.mon, DayOfWeek.sat) == 5); + + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sun) == 5); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.mon) == 6); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.tue) == 0); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.wed) == 1); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.thu) == 2); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.fri) == 3); + assert(daysToDayOfWeek(DayOfWeek.tue, DayOfWeek.sat) == 4); + + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sun) == 4); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.mon) == 5); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.tue) == 6); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.wed) == 0); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.thu) == 1); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.fri) == 2); + assert(daysToDayOfWeek(DayOfWeek.wed, DayOfWeek.sat) == 3); + + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sun) == 3); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.mon) == 4); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.tue) == 5); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.wed) == 6); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.thu) == 0); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.fri) == 1); + assert(daysToDayOfWeek(DayOfWeek.thu, DayOfWeek.sat) == 2); + + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sun) == 2); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.mon) == 3); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.tue) == 4); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.wed) == 5); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.thu) == 6); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.fri) == 0); + assert(daysToDayOfWeek(DayOfWeek.fri, DayOfWeek.sat) == 1); + + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sun) == 1); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.mon) == 2); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.tue) == 3); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.wed) == 4); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.thu) == 5); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.fri) == 6); + assert(daysToDayOfWeek(DayOfWeek.sat, DayOfWeek.sat) == 0); +} + +package: + + +/+ + Array of the short (three letter) names of each month. + +/ +immutable string[12] _monthNames = ["Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"]; + +/++ + The maximum valid Day in the given month in the given year. + + Params: + year = The year to get the day for. + month = The month of the Gregorian Calendar to get the day for. + +/ +public ubyte maxDay(int year, int month) @safe pure nothrow @nogc +in +{ + assert(valid!"months"(month)); +} +do +{ + switch (month) + { + case Month.jan, Month.mar, Month.may, Month.jul, Month.aug, Month.oct, Month.dec: + return 31; + case Month.feb: + return yearIsLeapYear(year) ? 29 : 28; + case Month.apr, Month.jun, Month.sep, Month.nov: + return 30; + default: + assert(0, "Invalid month."); + } +} + +version (mir_test) +@safe unittest +{ + // Test A.D. + assert(maxDay(1999, 1) == 31); + assert(maxDay(1999, 2) == 28); + assert(maxDay(1999, 3) == 31); + assert(maxDay(1999, 4) == 30); + assert(maxDay(1999, 5) == 31); + assert(maxDay(1999, 6) == 30); + assert(maxDay(1999, 7) == 31); + assert(maxDay(1999, 8) == 31); + assert(maxDay(1999, 9) == 30); + assert(maxDay(1999, 10) == 31); + assert(maxDay(1999, 11) == 30); + assert(maxDay(1999, 12) == 31); + + assert(maxDay(2000, 1) == 31); + assert(maxDay(2000, 2) == 29); + assert(maxDay(2000, 3) == 31); + assert(maxDay(2000, 4) == 30); + assert(maxDay(2000, 5) == 31); + assert(maxDay(2000, 6) == 30); + assert(maxDay(2000, 7) == 31); + assert(maxDay(2000, 8) == 31); + assert(maxDay(2000, 9) == 30); + assert(maxDay(2000, 10) == 31); + assert(maxDay(2000, 11) == 30); + assert(maxDay(2000, 12) == 31); + + // Test B.C. + assert(maxDay(-1999, 1) == 31); + assert(maxDay(-1999, 2) == 28); + assert(maxDay(-1999, 3) == 31); + assert(maxDay(-1999, 4) == 30); + assert(maxDay(-1999, 5) == 31); + assert(maxDay(-1999, 6) == 30); + assert(maxDay(-1999, 7) == 31); + assert(maxDay(-1999, 8) == 31); + assert(maxDay(-1999, 9) == 30); + assert(maxDay(-1999, 10) == 31); + assert(maxDay(-1999, 11) == 30); + assert(maxDay(-1999, 12) == 31); + + assert(maxDay(-2000, 1) == 31); + assert(maxDay(-2000, 2) == 29); + assert(maxDay(-2000, 3) == 31); + assert(maxDay(-2000, 4) == 30); + assert(maxDay(-2000, 5) == 31); + assert(maxDay(-2000, 6) == 30); + assert(maxDay(-2000, 7) == 31); + assert(maxDay(-2000, 8) == 31); + assert(maxDay(-2000, 9) == 30); + assert(maxDay(-2000, 10) == 31); + assert(maxDay(-2000, 11) == 30); + assert(maxDay(-2000, 12) == 31); +} + +/+ + Returns the day of the week for the given day of the Gregorian/Julian Calendar. + + Params: + day = The day of the Gregorian/Julian Calendar for which to get the day of + the week. + +/ +DayOfWeek getDayOfWeek(int day) @safe pure nothrow @nogc +{ + // January 1st, 1 A.D. was a Monday + if (day >= 0) + return cast(DayOfWeek)(day % 7); + else + { + immutable dow = cast(DayOfWeek)((day % 7) + 7); + + if (dow == 7) + return DayOfWeek.mon; + else + return dow; + } +} + +private: + +enum daysInYear = 365; // The number of days in a non-leap year. +enum daysInLeapYear = 366; // The numbef or days in a leap year. +enum daysIn4Years = daysInYear * 3 + daysInLeapYear; // Number of days in 4 years. +enum daysIn100Years = daysIn4Years * 25 - 1; // The number of days in 100 years. +enum daysIn400Years = daysIn100Years * 4 + 1; // The number of days in 400 years. + +/+ + Array of integers representing the last days of each month in a year. + +/ +immutable int[13] lastDayNonLeap = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + +/+ + Array of integers representing the last days of each month in a leap year. + +/ +immutable int[13] lastDayLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; + +/+ + Array of integers representing the last days of each quarter in a year. + +/ +immutable int[5] lastDayQuarterNonLeap = [0, 90, 181, 273, 365]; + +/+ + Array of integers representing the last days of each quarter in a leap year. + +/ +immutable int[5] lastDayQuarterLeap = [0, 91, 182, 274, 366]; + +/+ + Returns the string representation of the given month. + +/ +string monthToString(Month month) @safe pure @nogc nothrow +{ + assert(month >= Month.jan && month <= Month.dec, "Invalid month"); + return _monthNames[month - Month.jan]; +} + +version (mir_test) +@safe unittest +{ + assert(monthToString(Month.jan) == "Jan"); + assert(monthToString(Month.feb) == "Feb"); + assert(monthToString(Month.mar) == "Mar"); + assert(monthToString(Month.apr) == "Apr"); + assert(monthToString(Month.may) == "May"); + assert(monthToString(Month.jun) == "Jun"); + assert(monthToString(Month.jul) == "Jul"); + assert(monthToString(Month.aug) == "Aug"); + assert(monthToString(Month.sep) == "Sep"); + assert(monthToString(Month.oct) == "Oct"); + assert(monthToString(Month.nov) == "Nov"); + assert(monthToString(Month.dec) == "Dec"); +} + +version (mir_test) +version(unittest) +{ + // All of these helper arrays are sorted in ascending order. + auto testYearsBC = [-1999, -1200, -600, -4, -1, 0]; + auto testYearsAD = [1, 4, 1000, 1999, 2000, 2012]; + + // I'd use a Tuple, but I get forward reference errors if I try. + static struct MonthDay + { + Month month; + short day; + + this(int m, short d) @safe + { + month = cast(Month) m; + day = d; + } + } + + MonthDay[] testMonthDays = [MonthDay(1, 1), + MonthDay(1, 2), + MonthDay(3, 17), + MonthDay(7, 4), + MonthDay(10, 27), + MonthDay(12, 30), + MonthDay(12, 31)]; + + auto testDays = [1, 2, 9, 10, 16, 20, 25, 28, 29, 30, 31]; + + Date[] testDatesBC; + Date[] testDatesAD; + + // I'd use a Tuple, but I get forward reference errors if I try. + struct GregDay { int day; Date date; } + auto testGregDaysBC = [GregDay(-1_373_427, Date(-3760, 9, 7)), // Start of the Hebrew Calendar + GregDay(-735_233, Date(-2012, 1, 1)), + GregDay(-735_202, Date(-2012, 2, 1)), + GregDay(-735_175, Date(-2012, 2, 28)), + GregDay(-735_174, Date(-2012, 2, 29)), + GregDay(-735_173, Date(-2012, 3, 1)), + GregDay(-734_502, Date(-2010, 1, 1)), + GregDay(-734_472, Date(-2010, 1, 31)), + GregDay(-734_471, Date(-2010, 2, 1)), + GregDay(-734_444, Date(-2010, 2, 28)), + GregDay(-734_443, Date(-2010, 3, 1)), + GregDay(-734_413, Date(-2010, 3, 31)), + GregDay(-734_412, Date(-2010, 4, 1)), + GregDay(-734_383, Date(-2010, 4, 30)), + GregDay(-734_382, Date(-2010, 5, 1)), + GregDay(-734_352, Date(-2010, 5, 31)), + GregDay(-734_351, Date(-2010, 6, 1)), + GregDay(-734_322, Date(-2010, 6, 30)), + GregDay(-734_321, Date(-2010, 7, 1)), + GregDay(-734_291, Date(-2010, 7, 31)), + GregDay(-734_290, Date(-2010, 8, 1)), + GregDay(-734_260, Date(-2010, 8, 31)), + GregDay(-734_259, Date(-2010, 9, 1)), + GregDay(-734_230, Date(-2010, 9, 30)), + GregDay(-734_229, Date(-2010, 10, 1)), + GregDay(-734_199, Date(-2010, 10, 31)), + GregDay(-734_198, Date(-2010, 11, 1)), + GregDay(-734_169, Date(-2010, 11, 30)), + GregDay(-734_168, Date(-2010, 12, 1)), + GregDay(-734_139, Date(-2010, 12, 30)), + GregDay(-734_138, Date(-2010, 12, 31)), + GregDay(-731_215, Date(-2001, 1, 1)), + GregDay(-730_850, Date(-2000, 1, 1)), + GregDay(-730_849, Date(-2000, 1, 2)), + GregDay(-730_486, Date(-2000, 12, 30)), + GregDay(-730_485, Date(-2000, 12, 31)), + GregDay(-730_484, Date(-1999, 1, 1)), + GregDay(-694_690, Date(-1901, 1, 1)), + GregDay(-694_325, Date(-1900, 1, 1)), + GregDay(-585_118, Date(-1601, 1, 1)), + GregDay(-584_753, Date(-1600, 1, 1)), + GregDay(-584_388, Date(-1600, 12, 31)), + GregDay(-584_387, Date(-1599, 1, 1)), + GregDay(-365_972, Date(-1001, 1, 1)), + GregDay(-365_607, Date(-1000, 1, 1)), + GregDay(-183_351, Date(-501, 1, 1)), + GregDay(-182_986, Date(-500, 1, 1)), + GregDay(-182_621, Date(-499, 1, 1)), + GregDay(-146_827, Date(-401, 1, 1)), + GregDay(-146_462, Date(-400, 1, 1)), + GregDay(-146_097, Date(-400, 12, 31)), + GregDay(-110_302, Date(-301, 1, 1)), + GregDay(-109_937, Date(-300, 1, 1)), + GregDay(-73_778, Date(-201, 1, 1)), + GregDay(-73_413, Date(-200, 1, 1)), + GregDay(-38_715, Date(-105, 1, 1)), + GregDay(-37_254, Date(-101, 1, 1)), + GregDay(-36_889, Date(-100, 1, 1)), + GregDay(-36_524, Date(-99, 1, 1)), + GregDay(-36_160, Date(-99, 12, 31)), + GregDay(-35_794, Date(-97, 1, 1)), + GregDay(-18_627, Date(-50, 1, 1)), + GregDay(-18_262, Date(-49, 1, 1)), + GregDay(-3652, Date(-9, 1, 1)), + GregDay(-2191, Date(-5, 1, 1)), + GregDay(-1827, Date(-5, 12, 31)), + GregDay(-1826, Date(-4, 1, 1)), + GregDay(-1825, Date(-4, 1, 2)), + GregDay(-1462, Date(-4, 12, 30)), + GregDay(-1461, Date(-4, 12, 31)), + GregDay(-1460, Date(-3, 1, 1)), + GregDay(-1096, Date(-3, 12, 31)), + GregDay(-1095, Date(-2, 1, 1)), + GregDay(-731, Date(-2, 12, 31)), + GregDay(-730, Date(-1, 1, 1)), + GregDay(-367, Date(-1, 12, 30)), + GregDay(-366, Date(-1, 12, 31)), + GregDay(-365, Date(0, 1, 1)), + GregDay(-31, Date(0, 11, 30)), + GregDay(-30, Date(0, 12, 1)), + GregDay(-1, Date(0, 12, 30)), + GregDay(0, Date(0, 12, 31))]; + + auto testGregDaysAD = [GregDay(1, Date(1, 1, 1)), + GregDay(2, Date(1, 1, 2)), + GregDay(32, Date(1, 2, 1)), + GregDay(365, Date(1, 12, 31)), + GregDay(366, Date(2, 1, 1)), + GregDay(731, Date(3, 1, 1)), + GregDay(1096, Date(4, 1, 1)), + GregDay(1097, Date(4, 1, 2)), + GregDay(1460, Date(4, 12, 30)), + GregDay(1461, Date(4, 12, 31)), + GregDay(1462, Date(5, 1, 1)), + GregDay(17_898, Date(50, 1, 1)), + GregDay(35_065, Date(97, 1, 1)), + GregDay(36_160, Date(100, 1, 1)), + GregDay(36_525, Date(101, 1, 1)), + GregDay(37_986, Date(105, 1, 1)), + GregDay(72_684, Date(200, 1, 1)), + GregDay(73_049, Date(201, 1, 1)), + GregDay(109_208, Date(300, 1, 1)), + GregDay(109_573, Date(301, 1, 1)), + GregDay(145_732, Date(400, 1, 1)), + GregDay(146_098, Date(401, 1, 1)), + GregDay(182_257, Date(500, 1, 1)), + GregDay(182_622, Date(501, 1, 1)), + GregDay(364_878, Date(1000, 1, 1)), + GregDay(365_243, Date(1001, 1, 1)), + GregDay(584_023, Date(1600, 1, 1)), + GregDay(584_389, Date(1601, 1, 1)), + GregDay(693_596, Date(1900, 1, 1)), + GregDay(693_961, Date(1901, 1, 1)), + GregDay(729_755, Date(1999, 1, 1)), + GregDay(730_120, Date(2000, 1, 1)), + GregDay(730_121, Date(2000, 1, 2)), + GregDay(730_484, Date(2000, 12, 30)), + GregDay(730_485, Date(2000, 12, 31)), + GregDay(730_486, Date(2001, 1, 1)), + GregDay(733_773, Date(2010, 1, 1)), + GregDay(733_774, Date(2010, 1, 2)), + GregDay(733_803, Date(2010, 1, 31)), + GregDay(733_804, Date(2010, 2, 1)), + GregDay(733_831, Date(2010, 2, 28)), + GregDay(733_832, Date(2010, 3, 1)), + GregDay(733_862, Date(2010, 3, 31)), + GregDay(733_863, Date(2010, 4, 1)), + GregDay(733_892, Date(2010, 4, 30)), + GregDay(733_893, Date(2010, 5, 1)), + GregDay(733_923, Date(2010, 5, 31)), + GregDay(733_924, Date(2010, 6, 1)), + GregDay(733_953, Date(2010, 6, 30)), + GregDay(733_954, Date(2010, 7, 1)), + GregDay(733_984, Date(2010, 7, 31)), + GregDay(733_985, Date(2010, 8, 1)), + GregDay(734_015, Date(2010, 8, 31)), + GregDay(734_016, Date(2010, 9, 1)), + GregDay(734_045, Date(2010, 9, 30)), + GregDay(734_046, Date(2010, 10, 1)), + GregDay(734_076, Date(2010, 10, 31)), + GregDay(734_077, Date(2010, 11, 1)), + GregDay(734_106, Date(2010, 11, 30)), + GregDay(734_107, Date(2010, 12, 1)), + GregDay(734_136, Date(2010, 12, 30)), + GregDay(734_137, Date(2010, 12, 31)), + GregDay(734_503, Date(2012, 1, 1)), + GregDay(734_534, Date(2012, 2, 1)), + GregDay(734_561, Date(2012, 2, 28)), + GregDay(734_562, Date(2012, 2, 29)), + GregDay(734_563, Date(2012, 3, 1)), + GregDay(734_858, Date(2012, 12, 21))]; + + // I'd use a Tuple, but I get forward reference errors if I try. + struct DayOfYear { int day; MonthDay md; } + auto testDaysOfYear = [DayOfYear(1, MonthDay(1, 1)), + DayOfYear(2, MonthDay(1, 2)), + DayOfYear(3, MonthDay(1, 3)), + DayOfYear(31, MonthDay(1, 31)), + DayOfYear(32, MonthDay(2, 1)), + DayOfYear(59, MonthDay(2, 28)), + DayOfYear(60, MonthDay(3, 1)), + DayOfYear(90, MonthDay(3, 31)), + DayOfYear(91, MonthDay(4, 1)), + DayOfYear(120, MonthDay(4, 30)), + DayOfYear(121, MonthDay(5, 1)), + DayOfYear(151, MonthDay(5, 31)), + DayOfYear(152, MonthDay(6, 1)), + DayOfYear(181, MonthDay(6, 30)), + DayOfYear(182, MonthDay(7, 1)), + DayOfYear(212, MonthDay(7, 31)), + DayOfYear(213, MonthDay(8, 1)), + DayOfYear(243, MonthDay(8, 31)), + DayOfYear(244, MonthDay(9, 1)), + DayOfYear(273, MonthDay(9, 30)), + DayOfYear(274, MonthDay(10, 1)), + DayOfYear(304, MonthDay(10, 31)), + DayOfYear(305, MonthDay(11, 1)), + DayOfYear(334, MonthDay(11, 30)), + DayOfYear(335, MonthDay(12, 1)), + DayOfYear(363, MonthDay(12, 29)), + DayOfYear(364, MonthDay(12, 30)), + DayOfYear(365, MonthDay(12, 31))]; + + auto testDaysOfLeapYear = [DayOfYear(1, MonthDay(1, 1)), + DayOfYear(2, MonthDay(1, 2)), + DayOfYear(3, MonthDay(1, 3)), + DayOfYear(31, MonthDay(1, 31)), + DayOfYear(32, MonthDay(2, 1)), + DayOfYear(59, MonthDay(2, 28)), + DayOfYear(60, MonthDay(2, 29)), + DayOfYear(61, MonthDay(3, 1)), + DayOfYear(91, MonthDay(3, 31)), + DayOfYear(92, MonthDay(4, 1)), + DayOfYear(121, MonthDay(4, 30)), + DayOfYear(122, MonthDay(5, 1)), + DayOfYear(152, MonthDay(5, 31)), + DayOfYear(153, MonthDay(6, 1)), + DayOfYear(182, MonthDay(6, 30)), + DayOfYear(183, MonthDay(7, 1)), + DayOfYear(213, MonthDay(7, 31)), + DayOfYear(214, MonthDay(8, 1)), + DayOfYear(244, MonthDay(8, 31)), + DayOfYear(245, MonthDay(9, 1)), + DayOfYear(274, MonthDay(9, 30)), + DayOfYear(275, MonthDay(10, 1)), + DayOfYear(305, MonthDay(10, 31)), + DayOfYear(306, MonthDay(11, 1)), + DayOfYear(335, MonthDay(11, 30)), + DayOfYear(336, MonthDay(12, 1)), + DayOfYear(364, MonthDay(12, 29)), + DayOfYear(365, MonthDay(12, 30)), + DayOfYear(366, MonthDay(12, 31))]; + + void initializeTests() @safe + { + foreach (year; testYearsBC) + { + foreach (md; testMonthDays) + testDatesBC ~= Date(year, md.month, md.day); + } + + foreach (year; testYearsAD) + { + foreach (md; testMonthDays) + testDatesAD ~= Date(year, md.month, md.day); + } + } +} diff --git a/source/mir/ediff.d b/source/mir/ediff.d new file mode 100644 index 00000000..48238691 --- /dev/null +++ b/source/mir/ediff.d @@ -0,0 +1,940 @@ +/++ +$(H1 Expression differentiation) + +The module provides API for rapid evaluation of a user-requested set of partial derivatives of any order. + +The implementation operates with double precision numbers. + +$(BOOKTABLE $(H2 Expression construction), +$(TR $(TH Name) $(TH Description)) +$(T2 $(LREF Const), A double precision constant with an immidiate value) +$(T2 $(LREF Var), A double precision named variable with an immidiate value) +$(T2 `+`, Constructs the sum of two expressions) +$(T2 `-`, Constructs the difference of two expressions) +$(T2 `*`, Constructs the product of two expressions) +$(T2 `/`, Constructs the quotient of two expressions) +$(T2 $(LREF derivativeOf), Constructs a partial derivative of one order. + The function can be applied in any stage any number of times. + The function does NOT evaluate the expression.) +$(T2 $(LREF powi), Constructs of the expression raised to the specified (positive or negative) compile-time integral power.) +$(T2 $(LREF log), Constructs the natural logarithm of the expression.) +$(T2 $(LREF exp), Constructs the natural exponent of the expression.) +$(T2 $(LREF sqrt), Constructs the square root of the expression.) +$(T2 $(LREF normalDistribution), Constructs the cumulative normal distribution function of the expression.) +) + +$(BOOKTABLE $(H2 Expression composition), +$(TR $(TH Name) $(TH Description)) +$(T2 $(LREF composeAt), Given a compiletime variable name `v` and two expressions `F(v, U)` and `G(Y)`, + constructs a composition `F ∘ G (U, Y)` as `v = G`) +) + +$(BOOKTABLE $(H2 Derivative set definition), +$(TR $(TH Name) $(TH Description)) +$(T2 $(LREF Derivative), User defined attribute that should applied on a struct member definition of a user-defined set of derivatives. + The attribute holds an unordered set with duplicates of variables names to reflect which partial derivative this member contains.) +$(T2 $(LREF Minus), User-defined attribute that can be applied on struct member definition along with $(LREF Derivative) to denote + that the member holds a negative value of the partial derivative. + This attribute is useful for financial code where verbal definitions may denote negative partial derivatives.) +) + +$(BOOKTABLE $(H2 Function definition), +$(TR $(TH Name) $(TH Description)) +$(T2 $(LREF DependsOn), User defined attribute that should applied on struct definition of a user-defined expression function) +$(T2 $(LREF Dependencies), Fetchs a set of variables the expression is depends on. + First, it checks if the expression has $(LREF DependsOn) UDA + ; otherwise, iterates over members with $(LREF Derivative) UDA and collects variable names. ) +$(T2 .getDerivative, The generic method, which a user expression function should define. + The method should accept a compile-time array of variable names and evaluate the corresponding partial derivative.) +$(T2 $(LREF ediffOperators, Mixin template that propagates `+`, `-`, `*`, and `/` binary operators for user defined expression functions.)) +) + +$(BOOKTABLE $(H2 Expression evaluation), +$(TR $(TH Name) $(TH Description)) +$(T2 $(LREF getFunctionValue), Evaluates function value. It is shortcut for $(LREF getDerivative) of zero order.) +$(T2 $(LREF getDerivative), Evaluates partial derivative for user-defined compiletime set of variables. + First, it checks if the expression has `.getDerivative` methods and uses it, + otherwise iterates over members with $(LREF Derivative) UDA and tries to find a member that holds the required partial derivative.) +$(T2 $(LREF setDerivatives), Evaluates partial derivatives and function value, if any, for a user-provided set of partial derivatives. + The derivative set can be defined with $(LREF Derivative) and $(LREF Minus) UDAs.) +) + +$(H2 Optimization note) + +During function differentiation, the resulting set of expressions likely contains a lot of +identical calls of elementary functions. LLVM efficiently eliminates equivalent calls of intrinsic +functions such as `powi`, `log`, `exp`, and `sqrt`. +On the other hand, it can't eliminate identical calls of complex functions. +It is highly recommended to evaluate a set of partial derivatives immediately after constructing +a complex expression such as $(LREF normalDistribution). + +Authors: Ilia Ki ++/ +module mir.ediff; + +/// +version(mir_test) +unittest +{ + /// + static struct D + { + @Derivative() + double _; + @Derivative("a") + double a; + @Derivative("b") + double b; + @Minus @Derivative("a", "b") + double mab; + } + + auto d = D(3, 4, 5, -6); + assert(d.powi!2.setDerivatives!D == D(9, 24, 30, -76)); +} +/// +version(mir_test) +unittest +{ + /// + static struct Greeks + { + @Derivative("spot") + double delta; + @Derivative("spot", "spot") + double gamma; + @Minus @Derivative("time", "spot") + double charm; + } + + auto greeks = Greeks(2, 3, 4); + + auto dspot = derivativeOf!"spot"(&greeks); + + assert(greeks.delta is dspot.getFunctionValue); + assert(greeks.gamma is dspot.getDerivative!(["spot"])); + assert(greeks.charm is -dspot.getDerivative!(["time"])); +} + +/// +version(mir_test) +unittest +{ + import mir.test; + import mir.math; + + // Test Const + static assert(Dependencies!Const == [].DependsOn); + auto c = 5.Const; + static assert(c.getDerivative!(["any"]) == 0); + assert(c.getFunctionValue == 5); + + // Test Var + auto spot = 7.Var!"spot"; + static assert(Dependencies!(Var!"spot") == ["spot"].DependsOn); + static assert(spot.getDerivative!(["spot"]) == 1); + static assert(spot.getDerivative!(["other"]) == 0); + assert(spot.getFunctionValue == 7); + + // Test integer power and exponent + auto f1 = exp(3.Const * spot.powi!(-2)); + static assert(Dependencies!(typeof(f1)) == ["spot"].DependsOn); + assert(f1.getFunctionValue == mir.math.exp(3 * 7.0 ^^ -2)); + assert(f1.getDerivative!(["spot"]).approxEqual(3 * -2 * 7.0 ^^ -3 * mir.math.exp(3 * 7.0 ^^ -2))); + // Test DerivativeOf + assert(f1.derivativeOf!"spot".getFunctionValue == f1.getDerivative!(["spot"])); + // Test product and sum + assert(f1.derivativeOf!"spot".derivativeOf!"spot".getFunctionValue.approxEqual((3 * (-2 * 7.0 ^^ -3)) ^^ 2 * mir.math.exp(3 * 7.0 ^^ -2) + 3 * (6 * 7.0 ^^ -4)* mir.math.exp(3 * 7.0 ^^ -2))); + + auto strike = 9.Var!"strike"; + + auto f2 = strike * f1 + strike; + assert(f2.getDerivative!(["strike"]).approxEqual(1 + f1.getFunctionValue)); + // Test log + assert(f2.log.getFunctionValue == mir.math.log(f2.getFunctionValue)); + assert(f2.log.getDerivative!(["strike"]) == getFunctionValue(f2.powi!(-1) * (1.Const + f1))); + assert(f2.sqrt.getFunctionValue == mir.math.sqrt(f2.getFunctionValue)); + assert(f2.sqrt.getDerivative!(["strike"]) == getFunctionValue(f2.sqrt.powi!(-1) * 0.5.Const * (1.Const + f1))); + + // Compose + auto barrier = 13.Var!"barrier"; + auto fc = barrier.powi!2 / strike; + auto f3 = f2.composeAt!"strike"(fc); + assert(f3.getFunctionValue == f2.getFunctionValue); + assert(f3.getDerivative!(["vol"]) == f2.getDerivative!(["vol"])); + assert(f3.getDerivative!(["strike"]) == f2.getDerivative!(["strike"]) * fc.getDerivative!(["strike"])); + f3.getDerivative!(["barrier"]).shouldApprox == f2.getDerivative!(["strike"]) * fc.getDerivative!(["barrier"]); + getDerivative!(["barrier"])(f3 + barrier).shouldApprox == f2.getDerivative!(["strike"]) * fc.getDerivative!(["barrier"]) + 1; + f3.getDerivative!(["strike", "barrier"]).shouldApprox == + f2.getDerivative!(["strike", "strike"]) * fc.getDerivative!(["strike"]) * fc.getDerivative!(["barrier"]) + + f2.getDerivative!(["strike"]) * fc.getDerivative!(["strike", "barrier"]); + + /// normalDistribution + import mir.math.func.normal: constantNormalCDF = normalCDF, normalPDF; + barrier.powi!2.normalCDF.getFunctionValue.shouldApprox == constantNormalCDF(13.0 ^^ 2); + barrier.powi!2.normalCDF.getDerivative!(["barrier"]).shouldApprox == normalPDF(13.0 ^^ 2) * (2 * 13); +} + +import mir.algorithm.iteration: uniq; +import mir.array.allocation; +import mir.math.common; +import mir.ndslice.sorting: sort; +import std.traits: Unqual, isPointer, PointerTarget; +import mir.internal.meta: getUDAs, hasUDA; + +@fmamath: + +/++ ++/ +mixin template ediffOperators() +{ +const: + auto opBinary(string op, R)(const R rhs) + if ((op == "+" || op == "-") && is(R == struct)) + { + return Sum!(Unqual!(typeof(this)), R, op == "-")(this, rhs); + } + + auto opBinary(string op, R)(const R rhs) + if (op == "*" && is(R == struct)) + { + alias L = Unqual!(typeof(this)); + static if (is(R == Const) && is(L == Const)) + { + return Const(this.value * rhs.value); + } + else + static if (is(R == Const) && is(L == Powi!(_power, _LT), size_t _power, _LT)) + { + return L(this.value, this.coefficient * rhs.value); + } + else + static if (is(L == Const) && is(R == Powi!(_power, _RT), size_t _power, _RT)) + { + return R(rhs.value, rhs.coefficient * this.value); + } + else + { + return Product!(L, R)(this, rhs); + } + } + + auto opBinaryRight(string op, L)(const L lhs) + if ((op == "+" || op == "*") && is(L == struct)) + { + return this.opBinary!op(lhs); + } + + auto opBinaryRight(string op, L)(const L lhs) + if ((op == "-") && is(L == struct)) + { + return Sum!(L, Unqual!(typeof(this)), true)(lhs, this); + } + + auto opBinary(string op, R)(const R rhs) + if (op == "/" && is(R == struct)) + { + alias L = Unqual!(typeof(this)); + alias A = L; + alias B = R; + alias a = this; + alias b = rhs; + mixin _div; + return result; + } + + auto opBinaryRight(string op, L)(const L lhs) + if ((op == "/") && is(L == struct)) + { + alias R = Unqual!(typeof(this)); + alias A = L; + alias B = R; + alias a = lhs; + alias b = this; + mixin _div; + return result; + } +} + +private mixin template _div() +{ + // A / B + static if (is(A == Const) && is(B == Const)) + auto result = Const(a.value / b.value); + else + static if (is(A == Const)) + auto result = Powi!(-1, B)(b, a.value); + else + static if (is(B == Const)) + auto result = Const(1.0 / b.value) * a; + else + auto result = b.powi!(-1) * a; +} + +private template Sum(A, B, bool diff = false) +{ + + @(Dependencies!A ~ Dependencies!B) + struct Sum + { + A a; + B b; + + @fmamath: + + template getDerivative(string[] variables, bool strict = true) + { + static if (Dependencies!(typeof(this)).containsAll(variables)) + auto getDerivative() const @property + { + double ret = 0; + static if (Dependencies!A.containsAll(variables)) + ret = a.getDerivative!(variables, strict); + static if (Dependencies!B.containsAll(variables)) + { + static if (diff) + ret -= b.getDerivative!(variables, strict); + else + ret += b.getDerivative!(variables, strict); + } + return ret; + } + else + enum double getDerivative = 0; + } + + mixin ediffOperators; + } +} + +private template Product(A, B) +{ + @(Dependencies!A ~ Dependencies!B) + struct Product + { + A a; + B b; + + template getDerivative(string[] variables, bool strict = true) + { + @fmamath: + static if (Dependencies!(typeof(this)).containsAll(variables)) + auto getDerivative() const @property + { + static if (variables.length == 0) + { + return a.getFunctionValue!strict * b.getFunctionValue!strict; + } + else + { + enum variable = variables[0]; + static if (!Dependencies!A.contains(variable)) + return (a * b.derivativeOf!variable).getDerivative!(variables[1 .. $], strict); + else + static if (!Dependencies!B.contains(variable)) + return (a.derivativeOf!variable * b).getDerivative!(variables[1 .. $], strict); + else + return (a * b.derivativeOf!variable + a.derivativeOf!variable * b).getDerivative!(variables[1 .. $], strict); + } + } + else + enum double getDerivative = 0; + } + + mixin ediffOperators; + } +} + +/++ +User defined attribute that should applied on struct definition of a user-defined expression function. ++/ +struct DependsOn +{ + /// + string[] variables; + +@safe pure nothrow: + /// + auto contains(string variable) const @nogc + { + foreach (v; variables) + if (v == variable) + return true; + return false; + } + + /// + auto containsAll(string[] variables) const @nogc + { + foreach (v; variables) + if (!contains(v)) + return false; + return true; + } + + /// + auto containsAny(string[] variables) const @nogc + { + foreach (v; variables) + if (contains(v)) + return true; + return false; + } + + /// Set union + DependsOn opBinary(string op : "~")(DependsOn rhs) + { + return (this.variables ~ rhs.variables).sort.uniq.array.DependsOn; + } +} + +/++ +Fetchs a set of variables the expression is depends on. + First, it checks if the expression has $(LREF DependsOn) UDA + ; otherwise, iterates over members with $(LREF Derivative) UDA and collects variable names. ++/ +template Dependencies(T) +{ + static if (isPointer!T) + enum Dependencies = Dependencies!(PointerTarget!T); + else + static if (hasUDA!(T, DependsOn)) + enum DependsOn Dependencies = getUDAs!(T, DependsOn)[0]; + else + { + enum DependsOn Dependencies = () { + string[] variables; + static foreach (member; __traits(allMembers, T)) + { + static if (hasUDA!(T, member, Derivative)) + { + variables ~= getUDAs!(T, member, Derivative)[0].variables; + } + } + return variables.sort.uniq.array.DependsOn; + } (); + } +} + +/++ +User defined attribute that should applied on a struct member definition of a user-defined set of derivatives. + The attribute holds an unordered set with duplicates of variables names to reflect which partial derivative this member contains. ++/ +struct Derivative +{ + /// + string[] variables; + +@trusted pure nothrow @nogc: + + /// + this(string[] variables...) + { + this.variables = variables.sort; + } +} + +/++ +User-defined attribute that can be applied on struct member definition along with $(LREF Derivative) to denote + that the member holds a negative value of the partial derivative. + This attribute is useful for financial code where verbal definitions may denote negative partial derivatives. ++/ +enum Minus; + +/++ +Evaluates function value. It is shortcut for $(LREF getDerivative) of zero order. +Params: + strict = The parameter is used when the expression can't evaluate the function. If true, prints error at compile-time; otherwise, `getFunctionValues` returns NaN. ++/ +alias getFunctionValue(bool strict = true) = getDerivative!(string[].init, strict); + +/++ +Evaluates partial derivative for user-defined compiletime set of variables. + First, it checks if the expression has `.getDerivative` methods and uses it, + otherwise iterates over members with $(LREF Derivative) UDA and tries to find a member that holds the required partial derivative. +Params: + variables = array that denotes partial derivative + strict = The parameter is used when the expression can't evaluate the derivative. If true, prints error at compile-time; otherwise, `getDerivative` returns NaN. ++/ +template getDerivative(string[] variables, bool strict = true) +{ + import mir.ndslice.topology: pairwise; + import mir.algorithm.iteration: all; + +@fmamath: + + static if (variables.length == 0 || variables.pairwise!"a <= b".all) + { + auto ref getDerivative(T)(auto ref T value) + { + static if (__traits(hasMember, T, "getDerivative")) + return value.getDerivative!(variables, strict); + else + { + import std.meta: anySatisfy; + + static if (isPointer!T) + alias V = PointerTarget!T; + else + alias V = T; + template hasDerivative(string member) + { + static if (hasUDA!(V, member, Derivative)) + enum hasDerivative = variables == getUDAs!(V, member, Derivative)[0].variables; + else + enum hasDerivative = false; + } + static if (anySatisfy!(hasDerivative, __traits(allMembers, V))) + { + static foreach (member; __traits(allMembers, V)) + { + static if (hasDerivative!member) + { + static if (hasUDA!(V, member, Minus)) + return -__traits(getMember, value, member); + else + return __traits(getMember, value, member); + } + } + } + else + static if (strict) + { + static assert(0, Unqual!V.stringof ~ "'_" ~ variables.stringof ~ " not found"); + } + else + { + return double.nan; + } + } + } + } + else + { + import mir.ndslice.sorting: sort; + alias getDerivative = .getDerivative!(variables.sort, strict); + } +} + +/++ +Evaluates partial derivatives and function value, if any, for a user-provided set of partial derivatives. + The derivative set can be defined with $(LREF Derivative) and $(LREF Minus) UDAs. +Params: + D = type of the requested set of partial derivatives + strict = The parameter is used when the expression can't evaluate the derivative. If true, prints error at compile-time; otherwise, the corresponding member is set to NaN. + derivatives = a structure that holds set of partial derivatives (optional) + expression = expression ++/ +void setDerivatives(bool strict = true, D, E)(scope ref D derivatives, E expression) +{ + static if (__traits(hasMember, D, "setDerivatives")) + return derivatives.setDerivatives!strict(expression); + else + { + import std.traits: isPointer, PointerTarget; + + static foreach (member; __traits(allMembers, D)) + { + static if (hasUDA!(D, member, Derivative)) + { + static if (hasUDA!(D, member, Minus)) + __traits(getMember, derivatives, member) = -expression.getDerivative!(getUDAs!(D, member, Derivative)[0].variables, strict); + else + __traits(getMember, derivatives, member) = expression.getDerivative!(getUDAs!(D, member, Derivative)[0].variables, strict); + } + } + } +} + +/// ditto +template setDerivatives(D, bool strict = true) +{ + /// + D setDerivatives(E)(E expression) + { + D ret; + .setDerivatives!strict(ret, expression); + return ret; + } +} + +private auto removeVariable(DependsOn variables, string variable) +{ + string[] ret; + foreach (v; variables.variables) + if (v != variable) + ret ~= v; + return ret.DependsOn; +} + +private template ComposeAt(F, G, string position) + if (Dependencies!F.contains(position)) +{ + @(Dependencies!F.removeVariable(position) ~ Dependencies!G) + struct ComposeAt + { + /// + F f; + /// + G g; + + /// + template getDerivative(string[] variables, bool strict = true) + { + static if (Dependencies!(typeof(this)).containsAll(variables)) + /// + auto ref getDerivative() const @property + { + static if (!Dependencies!G.containsAny(variables)) + return f.getDerivative!(variables, strict); + else + { + static if (Dependencies!F.contains(variables[0]) && variables[0] != position) + auto a = f.derivativeOf!(variables[0]).composeAt!position(g).getDerivative!(variables[1 .. $], strict); + else + enum a = 0; + static if (Dependencies!G.contains(variables[0])) + auto b = (f.derivativeOf!position.composeAt!position(g) * g.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); + else + enum b = 0; + return a + b; + } + } + else + enum double getDerivative = 0; + } + + mixin ediffOperators; + } +} + +/++ +Given a compiletime variable name `v` and two expressions `F(v, U)` and `G(Y)`, + constructs a composition `F ∘ G (U, Y)` as `v = G`. + +Params: + position = name of the variable to compose functions at. ++/ +template composeAt(string position) +{ + /++ + Params: + f = F expression + g = G expression + +/ + auto composeAt(F, G)(const F f, const G g) + { + return ComposeAt!(F, G, position)(f, g); + } +} + +private template DerivativeOf(T, string variable) +{ + @Dependencies!T + struct DerivativeOf + { + // Underlying expression + T underlying; + + @fmamath: + + /// + auto ref getDerivative(string[] variables, bool strict = true)() const @property + { + return underlying.getDerivative!(variable ~ variables, strict); + } + + mixin ediffOperators; + } +} + +/++ +Constructs a partial derivative of one order. + The function can be applied in any stage any number of times. + The function does NOT evaluate the expression. ++/ +template derivativeOf(string variable) +{ +@fmamath: + + /++ + Params: + value = expression + +/ + auto derivativeOf(T)(const T value) @property + { + static if (isPointer!T) + return DerivativeOf!(const(PointerTarget!T)*, variable)(value); + else + return DerivativeOf!(T, variable)(value); + } +} + +/++ +A double precision constant with an immidiate value ++/ +@DependsOn() +struct Const +{ + /// Immidiate value + double value; + + template getDerivative(string[] variables, bool strict = true) + { + static if (variables.length == 0) + alias getDerivative = value; + else + enum double getDerivative = 0; + } + + mixin ediffOperators; +} + +/++ +A double precision named variable with an immidiate value ++/ +template Var(string name) +{ + /// + @DependsOn([name]) + struct Var + { + /// Immidiate value + double value; + + template getDerivative(string[] variables, bool strict = true) + { + static if (variables.length == 0) + alias getDerivative = value; + else + enum double getDerivative = variables == [name]; + } + + mixin ediffOperators; + } +} + +private template Powi(int power, T) + if (power) +{ + @Dependencies!T + struct Powi + { + T value; + double coefficient = 1; + + template getDerivative(string[] variables, bool strict = true) + { + @fmamath: + static if (Dependencies!(typeof(this)).containsAll(variables)) + auto getDerivative() const @property + { + static if (variables.length == 0) + { + static if (power == 0) + return coefficient; + else + static if (power == 1) + return coefficient * value.getFunctionValue!strict; + else + static if (power == 2) + return coefficient * value.getFunctionValue!strict ^^ 2; + else + static if (power == -1) + return coefficient / value.getFunctionValue!strict; + else + static if (power == -2) + return coefficient / value.getFunctionValue!strict ^^ 2; + else + return coefficient * mir.math.common.powi(value.getFunctionValue!strict, power); + } + else + { + static if (power == 1) + auto v = coefficient.Const; + else + auto v = Powi!(power - 1, T)(value, coefficient * power); + static if (is(T == Var!(variables[0]))) + return v.getDerivative!(variables[1 .. $], strict); + else + return (v * value.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); + } + } + else + enum double getDerivative = 0; + } + + mixin ediffOperators; + } +} + +/++ +Constructs of the expression raised to the specified (positive or negative) compile-time integral power. + +Params: + power = integral power (compile-time) + value = expression ++/ +auto powi(int power, T)(const T value) + if (is(T == struct)) +{ + static if (power == 0) + return 1.Const; + else + return Powi!(power, T)(value); +} + +private template Exp(T) +{ + @Dependencies!T + struct Exp + { + /// Power function + T power; + + template getDerivative(string[] variables, bool strict = true) + { + @fmamath: + static if (Dependencies!(typeof(this)).containsAll(variables)) + auto getDerivative() const @property + { + static if (variables.length == 0) + return mir.math.common.exp(power.getFunctionValue!strict); + else + return (this * power.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); + } + else + enum double getDerivative = 0; + } + + mixin ediffOperators; + } +} + +/++ +Constructs the natural exponent of the expression. + +Params: + power = expression ++/ +auto exp(T)(const T power) + if (is(T == struct)) +{ + return Exp!T(power); +} + +private template Log(T) +{ + @Dependencies!T + struct Log + { + T value; + + template getDerivative(string[] variables, bool strict = true) + { + @fmamath: + static if (Dependencies!(typeof(this)).containsAll(variables)) + auto getDerivative() const @property + { + static if (variables.length == 0) + return mir.math.common.log(value.getFunctionValue!strict); + else + return (value.derivativeOf!(variables[0]) / value).getDerivative!(variables[1 .. $], strict); + } + else + enum double getDerivative = 0; + } + + mixin ediffOperators; + } +} + +/++ +Constructs the natural logarithm of the expression. + +Params: + value = expression ++/ +auto log(T)(const T value) + if (is(T == struct)) +{ + return Log!T(value); +} + +private template Sqrt(T) +{ + @Dependencies!T + struct Sqrt + { + T value; + + template getDerivative(string[] variables, bool strict = true) + { + @fmamath: + static if (Dependencies!(typeof(this)).containsAll(variables)) + auto getDerivative() const @property + { + static if (variables.length == 0) + return mir.math.common.sqrt(value.getFunctionValue!strict); + else + return (Powi!(-1, Sqrt!T)(this, 0.5) * value.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); + } + else + enum double getDerivative = 0; + } + + mixin ediffOperators; + } +} + +/++ +Constructs the square root of the expression. + +Params: + value = expression ++/ +auto sqrt(T)(const T value) + if (is(T == struct)) +{ + return Sqrt!T(value); +} + +private template NormalCDF(T) +{ + @Dependencies!T + struct NormalCDF + { + /// Square root argument function + T value; + + template getDerivative(string[] variables, bool strict = true) + { + @fmamath: + static if (Dependencies!(typeof(this)).containsAll(variables)) + auto getDerivative() const @property + { + import mir.math.func.normal: normalCDF, SQRT2PIINV; + static if (variables.length == 0) + return normalCDF(value.getFunctionValue!strict); + else + return (SQRT2PIINV.Const * Powi!(2, T)(value, -0.5).exp * value.derivativeOf!(variables[0])).getDerivative!(variables[1 .. $], strict); + } + else + enum double getDerivative = 0; + } + + mixin ediffOperators; + } +} + +/++ +Constructs the cumulative normal distribution function of the expression + +Params: + value = expression ++/ +auto normalCDF(T)(const T value) + if (is(T == struct)) +{ + return NormalCDF!T(value); +} diff --git a/source/mir/format.d b/source/mir/format.d new file mode 100644 index 00000000..8be3e4b5 --- /dev/null +++ b/source/mir/format.d @@ -0,0 +1,1366 @@ +/++ +$(H1 @nogc Formatting Utilities) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki ++/ +module mir.format; + +import std.traits; +import mir.primitives: isOutputRange; + +///Scalar styles. +enum StringStyle +{ + /// Uninitialized style + none, + /// Literal block style, `|` + longMultiLine, + /// Folded block style, `>` + longSingleLine, + /// Plain scalar + plain, + /// Single quoted scalar + asSingleQuoted, + /// Double quoted scalar + asEscapedString, +} + +/// Collection styles +enum CollectionStyle +{ + /// Uninitialized style + none, + /// Block style + block, + /// Flow style + flow, +} + +/// `mir.conv: to` extension. +version(mir_test) +@safe pure @nogc +unittest +{ + import mir.conv: to; + import mir.small_string; + alias S = SmallString!32; + + // Floating-point numbers are formatted to + // the shortest precise exponential notation. + assert(123.0.to!S == "123.0"); + assert(123.to!(immutable S) == "123"); + assert(true.to!S == "true"); + assert(true.to!string == "true"); + + assert((cast(S)"str")[] == "str"); +} + +/// `mir.conv: to` extension. +version(mir_test) +@safe pure +unittest +{ + import mir.conv: to; + import mir.small_string; + alias S = SmallString!32; + + auto str = S("str"); + assert(str.to!(const(char)[]) == "str"); // GC allocated result + assert(str.to!(char[]) == "str"); // GC allocated result +} + +/// ditto +version(mir_test) +@safe pure +unittest +{ + import mir.conv: to; + import mir.small_string; + alias S = SmallString!32; + + // Floating-point numbers are formatted to + // the shortest precise exponential notation. + assert(123.0.to!string == "123.0"); + assert(123.to!(char[]) == "123"); + + assert(S("str").to!string == "str"); // GC allocated result +} + +/// Concatenated string results +string text(string separator = "", Args...)(auto ref scope const Args args) + if (Args.length > 0) +{ + import mir.utility: _expect; + + static if (Args.length == 1) + { + static if (is(immutable Args[0] == immutable typeof(null))) + { + return "null"; + } + else + static if (is(Args[0] == enum)) + { + import mir.enums: getEnumIndex, enumStrings; + uint id = void; + if (getEnumIndex(args[0], id)._expect(true)) + return enumStrings!(Args[0])[id]; + assert(0); + } + else + static if (is(Unqual!(Args[0]) == bool)) + { + return args[0] ? "true" : "false"; + } + else + static if (is(args[0].toString : string)) + { + return args[0].toString; + } + else + { + import mir.appender: scopedBuffer; + auto buffer = scopedBuffer!char; + buffer.print(args[0]); + return buffer.data.idup; + } + } + else + { + import mir.appender: scopedBuffer; + auto buffer = scopedBuffer!char; + foreach (i, ref arg; args) + { + buffer.print(arg); + static if (separator.length && i + 1 < args.length) + { + buffer.printStaticString!char(separator); + } + } + return buffer.data.idup; + } +} + +/// +version(mir_test) +@safe pure nothrow unittest +{ + const i = 100; + assert(text("str ", true, " ", i, " ", 124.1) == "str true 100 124.1", text("str ", true, " ", 100, " ", 124.1)); + assert(text!" "("str", true, 100, 124.1, null) == "str true 100 124.1 null"); + assert(text(null) == "null", text(null)); +} + +import mir.format_impl; + +/// +struct GetData {} + +/// +enum getData = GetData(); + +/++ ++/ +struct StringBuf(C, uint scopeSize = 256) + if (is(C == char) || is(C == wchar) || is(C == dchar)) +{ + import mir.appender: ScopedBuffer; + + /// + ScopedBuffer!(C, scopeSize) buffer; + + /// + alias buffer this; + + /// + mixin StreamFormatOp!C; +} + +///ditto +auto stringBuf(C = char, uint scopeSize = 256)() + @trusted pure nothrow @nogc @property + if (is(C == char) || is(C == wchar) || is(C == dchar)) +{ + StringBuf!(C, scopeSize) buffer = void; + buffer.initialize; + return buffer; +} + +/++ ++/ +mixin template StreamFormatOp(C) +{ + /// + ref typeof(this) opBinary(string op : "<<", T)(scope ref const T c) scope + { + print!C(this, c); + return this; + } + + /// + ref typeof(this) opBinary(string op : "<<", T)(scope const T c) scope + { + print!C(this, c); + return this; + } + + /// ditto + const(C)[] opBinary(string op : "<<", T : GetData)(scope const T c) scope + { + return buffer.data; + } +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto name = "D"; + auto ver = 2.0; + assert(stringBuf << "Hi " << name << ver << "!\n" << getData == "Hi D2.0!\n"); +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto name = "D"w; + auto ver = 2; + assert(stringBuf!wchar << "Hi "w << name << ver << "!\n"w << getData == "Hi D2!\n"w); +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + auto name = "D"d; + auto ver = 2UL; + assert(stringBuf!dchar << "Hi "d << name << ver << "!\n"d << getData == "Hi D2!\n"d); +} + +@safe pure nothrow @nogc +version (mir_test) unittest +{ + assert(stringBuf << -1234567890 << getData == "-1234567890"); +} + +/++ +Mir's numeric format specification + +Note: the specification isn't complete an may be extended in the future. ++/ +struct NumericSpec +{ + /// + enum Format + { + /++ + Human-frindly precise output. + Examples: `0.000001`, `600000.0`, but `1e-7` and `6e7`. + +/ + human, + /++ + Precise output with explicit exponent. + Examples: `1e-6`, `6e6`, `1.23456789e-100`. + +/ + exponent, + } + + /// + Format format; + + /// Default valus is '\0' (no separators) + char separatorChar = '\0'; + + /// Defaults to 'e' + char exponentChar = 'e'; + + /// Adds '+' to positive numbers and `+0`. + bool plus; + + /// Separator count + ubyte separatorCount = 3; + + /++ + Precise output with explicit exponent. + Examples: `1e-6`, `6e6`, `1.23456789e-100`. + +/ + enum NumericSpec exponent = NumericSpec(Format.exponent); + + /++ + Human-frindly precise output. + +/ + enum NumericSpec human = NumericSpec(Format.human); +} + +// 16-bytes +/// C's compatible format specifier. +struct FormatSpec +{ + /// + bool dash; + /// + bool plus; + /// + bool space; + /// + bool hash; + /// + bool zero; + /// + char format = 's'; + /// + char separator = '\0'; + /// + ubyte unitSize; + /// + int width; + /// + int precision = -1; +} + +/++ ++/ +enum SwitchLU : bool +{ + /// + lower, + /// + upper, +} + +/++ +Wrapper to format floating point numbers using C's library. ++/ +struct FormattedFloating(T) + if(is(T == float) || is(T == double) || is(T == real)) +{ + /// + T value; + /// + FormatSpec spec; + + /// + void toString(C = char, W)(scope ref W w) scope const + if (isSomeChar!C) + { + C[512] buf = void; + auto n = printFloatingPoint(value, spec, buf); + w.put(buf[0 .. n]); + } +} + +/// ditto +FormattedFloating!T withFormat(T)(const T value, FormatSpec spec) +{ + version(LDC) pragma(inline); + return typeof(return)(value, spec); +} + +/++ ++/ +struct HexAddress(T) + if (isUnsigned!T && !is(T == enum)) +{ + /// + T value; + /// + SwitchLU switchLU = SwitchLU.upper; + + /// + void toString(C = char, W)(scope ref W w) scope const + if (isSomeChar!C) + { + enum N = T.sizeof * 2; + static if(isFastBuffer!W) + { + w.advance(printHexAddress(value, w.getStaticBuf!N, cast(bool) switchLU)); + } + else + { + C[N] buf = void; + printHexAddress(value, buf, cast(bool) switchLU); + w.put(buf[]); + } + } +} + +///ditto +HexAddress!T hexAddress(T)(const T value, SwitchLU switchLU = SwitchLU.upper) + if (isUnsigned!T && !is(T == enum)) +{ + return typeof(return)(value, switchLU); +} + +/++ +Escaped string formats ++/ +enum EscapeFormat +{ + /// JSON escaped string format + json, + /// Amzn Ion CLOB format + ionClob, + /// Amzn Ion symbol format + ionSymbol, + /// Amzn Ion string format + ion, +} + +enum escapeFormatQuote(EscapeFormat escapeFormat) = escapeFormat == EscapeFormat.ionSymbol ? '\'' : '\"'; + +/++ ++/ +void printEscaped(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope ref W w, scope const(C)[] str) + if (isOutputRange!(W, C)) +{ + import mir.utility: _expect; + foreach (C c; str) + { + if (_expect(c == escapeFormatQuote!escapeFormat || c == '\\', false)) + goto E; + if (_expect(c < ' ', false)) + goto C; + static if (escapeFormat == EscapeFormat.ionClob) + { + if (c >= 127) + goto A; + } + P: + w.put(c); + continue; + E: + { + C[2] pair; + pair[0] = '\\'; + pair[1] = c; + w.printStaticString!C(pair); + continue; + } + C: + switch (c) + { + static if (escapeFormat != EscapeFormat.json) + { + case '\0': + c = '0'; + goto E; + case '\a': + c = 'a'; + goto E; + case '\v': + c = 'v'; + goto E; + } + case '\b': + c = 'b'; + goto E; + case '\t': + c = 't'; + goto E; + case '\n': + c = 'n'; + goto E; + case '\f': + c = 'f'; + goto E; + case '\r': + c = 'r'; + goto E; + default: + A: + static if (escapeFormat == EscapeFormat.json) + put_uXXXX!C(w, cast(char)c); + else + put_xXX!C(w, cast(char)c); + } + } + return; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.format: stringBuf; + auto w = stringBuf; + w.printEscaped("Hi \a\v\0\f\t\b \\\r\n" ~ `"@nogc"`); + assert(w.data == `Hi \a\v\0\f\t\b \\\r\n\"@nogc\"`); + w.reset; + w.printEscaped("\x03"); + assert(w.data == `\x03`); +} + +/// +void printReplaced(C, W)(scope ref W w, scope const(C)[] str, C c, scope const(C)[] to) +{ + import mir.string: scanLeftAny; + + while (str.length) + { + auto tailLen = str.scanLeftAny(c).length; + print(w, str[0 .. $ - tailLen]); + if (tailLen == 0) + break; + print(w, to); + str = str[$ - tailLen + 1 .. $]; + } +} + +/// +@safe pure nothrow +unittest +{ + import mir.test: should; + auto csv = stringBuf; + csv.put('"'); + csv.printReplaced(`some string with " double quotes "!`, '"', `""`); + csv.put('"'); + csv.data.should == `"some string with "" double quotes ""!"`; +} + +/++ +Decodes `char` `c` to the form `u00XX`, where `XX` is 2 hexadecimal characters. ++/ +void put_xXX(C = char, W)(scope ref W w, char c) + if (isSomeChar!C) +{ + ubyte[2] spl; + spl[0] = c >> 4; + spl[1] = c & 0xF; + C[4] buffer; + buffer[0] = '\\'; + buffer[1] = 'x'; + buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); + buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); + return w.printStaticString(buffer); +} + +/++ +Decodes `char` `c` to the form `\u00XX`, where `XX` is 2 hexadecimal characters. ++/ +void put_uXXXX(C = char, W)(scope ref W w, char c) + if (isSomeChar!C) +{ + ubyte[2] spl; + spl[0] = c >> 4; + spl[1] = c & 0xF; + C[6] buffer; + buffer[0] = '\\'; + buffer[1] = 'u'; + buffer[2] = '0'; + buffer[3] = '0'; + buffer[4] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); + buffer[5] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); + return w.printStaticString(buffer); +} + +/++ +Decodes ushort `c` to the form `\uXXXX`, where `XXXX` is 2 hexadecimal characters. ++/ +void put_uXXXX(C = char, W)(scope ref W w, ushort c) + if (isSomeChar!C) +{ + ubyte[4] spl; + spl[0] = (c >> 12) & 0xF; + spl[1] = (c >> 8) & 0xF; + spl[2] = (c >> 4) & 0xF; + spl[3] = c & 0xF; + C[6] buffer; + buffer[0] = '\\'; + buffer[1] = 'u'; + buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); + buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); + buffer[4] = cast(ubyte)(spl[2] < 10 ? spl[2] + '0' : spl[2] - 10 + 'A'); + buffer[5] = cast(ubyte)(spl[3] < 10 ? spl[3] + '0' : spl[3] - 10 + 'A'); + return w.printStaticString(buffer); +} + +/++ +Decodes uint `c` to the form `\UXXXXXXXX`, where `XXXXXXXX` is 2 hexadecimal characters. ++/ +void put_UXXXXXXXX(C = char, W)(scope ref W w, uint c) + if (isSomeChar!C) +{ + w.printStaticString!C(`\U`); + w.print!C(HexAddress!uint(cast(uint)c)); +} + +/// +void printElement(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope ref W w, scope const(C)[] c) + if (isSomeChar!C) +{ + static immutable C[1] quote = '\"'; + w.printStaticString!C(quote); + w.printEscaped!(C, escapeFormat)(c); + w.printStaticString!C(quote); +} + +/// +void printElement(C = char, EscapeFormat escapeFormat = EscapeFormat.ion, W, T)(scope ref W w, scope auto ref const T c) + if (!isSomeString!T) +{ + return w.print!C(c); +} + +/++ +Multiargument overload. ++/ +void print(C = char, W, Args...)(scope ref W w, auto ref scope const Args args) + if (isSomeChar!C && Args.length > 1) +{ + foreach(i, ref c; args) + static if (i < Args.length - 1) + w.print!C(c); + else + return w.print!C(c); +} + +/// Prints enums +void print(C = char, W, T)(scope ref W w, scope const T c) @nogc + if (isSomeChar!C && is(T == enum)) +{ + import mir.enums: getEnumIndex, enumStrings; + import mir.utility: _expect; + + static assert(!is(OriginalType!T == enum)); + uint index = void; + if (getEnumIndex(c, index)._expect(true)) + { + w.put(enumStrings!T[index]); + return; + } + static immutable C[] str = T.stringof ~ "("; + w.put(str[]); + print!C(w, cast(OriginalType!T) c); + w.put(')'); + return; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + enum Flag + { + no, + yes, + } + + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + w.print(Flag.yes); + assert(w.data == "yes"); +} + +/// Prints boolean +void print(C = char, W)(scope ref W w, bool c) + if (isSomeChar!C) +{ + enum N = 5; + static if(isFastBuffer!W) + { + w.advance(printBoolean(c, w.getStaticBuf!N)); + } + else + { + C[N] buf = void; + auto n = printBoolean(c, buf); + w.put(buf[0 .. n]); + } + return; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + w.print(true); + assert(w.data == `true`); + w.reset; + w.print(false); + assert(w.data == `false`); +} + +/// Prints associative array +pragma(inline, false) +void print(C = char, W, V, K)(scope ref W w, scope const V[K] c) + if (isSomeChar!C) +{ + enum C left = '['; + enum C right = ']'; + enum C[2] sep = ", "; + enum C[2] mid = ": "; + w.put(left); + bool first = true; + foreach (ref key, ref value; c) + { + if (!first) + w.printStaticString!C(sep); + first = false; + w.printElement!C(key); + w.printStaticString!C(mid); + w.printElement!C(value); + } + w.put(right); + return; +} + +/// +@safe pure +version (mir_test) unittest +{ + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + w.print(["a": 1, "b": 2]); + assert(w.data == `["a": 1, "b": 2]` || w.data == `["b": 2, "a": 1]`); +} + +/// Prints null +void print(C = char, W, V)(scope ref W w, const V c) + if (is(V == typeof(null))) +{ + enum C[4] Null = "null"; + return w.printStaticString!C(Null); +} + +/// +@safe pure @nogc +version (mir_test) unittest +{ + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + w.print(null); + assert(w.data == `null`); +} + +/// Prints array +pragma(inline, false) +void printArray(C = char, W, T)(scope ref W w, + scope const(T)[] c, + scope const(C)[] lb = "[", + scope const(C)[] rb = "]", + scope const(C)[] sep = ", ", +) + if (isSomeChar!C && !isSomeChar!T) +{ + w.put(lb); + bool first = true; + foreach (ref e; c) + { + if (!first) + w.put(sep); + first = false; + printElement!C(w, e); + } + w.put(rb); + return; +} + +/// ditto +pragma(inline, false) +void print(C = char, W, T)(scope ref W w, + scope const(T)[] c, +) + if (isSomeChar!C && !isSomeChar!T) +{ + return printArray(w, c); +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + string[2] array = ["a\na", "b"]; + w.print(array[]); + assert(w.data == `["a\na", "b"]`); +} + +/// Prints array as hex values +pragma(inline, false) +void printHexArray(C = char, W, T)(scope ref W w, + scope const(T)[] c, + scope const(C)[] lb = "", + scope const(C)[] rb = "", + scope const(C)[] sep = " ", +) + if (isSomeChar!C && !isSomeChar!T && isUnsigned!T) +{ + w.put(lb); + bool first = true; + foreach (ref e; c) + { + if (!first) + w.put(sep); + first = false; + printElement!C(w, e.hexAddress); + } + w.put(rb); + return; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.test; + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + ubyte[2] array = [0x34, 0x32]; + w.printHexArray(array[]); + w.data.should == `34 32`; +} + +/// Prints escaped character in the form `'c'`. +pragma(inline, false) +void print(C = char, W)(scope ref W w, char c) + if (isSomeChar!C) +{ + w.put('\''); + if (c >= ubyte.max) + { + w.printStaticString!C(`\u`); + w.print!C(HexAddress!ubyte(cast(ubyte)c)); + } + else + if (c >= 0x20) + { + if (c < 0x7F) + { + if (c == '\'' || c == '\\') + { + L: + w.put('\\'); + } + w.put(c); + } + else + { + M: + w.printStaticString!C(`\x`); + w.print!C(HexAddress!ubyte(cast(ubyte)c)); + } + } + else + { + switch(c) + { + case '\n': c = 'n'; goto L; + case '\r': c = 'r'; goto L; + case '\t': c = 't'; goto L; + case '\a': c = 'a'; goto L; + case '\b': c = 'b'; goto L; + case '\f': c = 'f'; goto L; + case '\v': c = 'v'; goto L; + case '\0': c = '0'; goto L; + default: goto M; + } + } + w.put('\''); + return; +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + w.print('\n'); + w.print('\''); + w.print('a'); + w.print('\xF4'); + assert(w.data == `'\n''\'''a''\xF4'`); +} + +/// Prints escaped character in the form `'c'`. +pragma(inline, false) +void print(C = char, W)(scope ref W w, dchar c) + if (isSomeChar!C) +{ + import std.uni: isGraphical; + if (c <= ubyte.max) + return print(w, cast(char) c); + w.put('\''); + if (c.isGraphical) + { + import std.utf: encode; + C[dchar.sizeof / C.sizeof] buf; + print!C(w, buf[0 .. encode(buf, c)]); + } + else + if (c <= ushort.max) + { + w.put_uXXXX!C(cast(ushort)c); + } + else + { + w.put_UXXXXXXXX!C(c); + } + w.put('\''); +} + +/// +@safe pure +version (mir_test) unittest +{ + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + w.print('щ'); + w.print('\U0010FFFE'); + assert(w.data == `'щ''\U0010FFFE'`); +} + +/// Prints some string +void print(C = char, W)(scope ref W w, scope const(C)[] c) + if (isSomeChar!C) +{ + w.put(c); + return; +} + +/// Prints integers +void print(C = char, W, I)(scope ref W w, const I c) + if (isSomeChar!C && isIntegral!I && !is(I == enum)) +{ + static if (I.sizeof == 16) + enum N = 39; + else + static if (I.sizeof == 8) + enum N = 20; + else + enum N = 10; + C[N + !__traits(isUnsigned, I)] buf = void; + static if (__traits(isUnsigned, I)) + auto n = printUnsignedToTail(c, buf); + else + auto n = printSignedToTail(c, buf); + w.put(buf[$ - n .. $]); + return; +} + +/// Prints floating point numbers +void print(C = char, W, T)(scope ref W w, const T c, NumericSpec spec = NumericSpec.init) + if(isSomeChar!C && is(T == float) || is(T == double) || is(T == real)) +{ + import mir.bignum.decimal; + auto decimal = Decimal!(T.mant_dig < 64 ? 1 : 2)(c); + decimal.toString(w, spec); + return; +} + +/// Human friendly precise output (default) +version(mir_bignum_test) +@safe pure nothrow @nogc +unittest +{ + auto spec = NumericSpec.human; + auto buffer = stringBuf; + + void check(double num, string value) + { + buffer.print(num, spec); + assert(buffer.data == value, value); + buffer.reset; + } + + check(-0.0, "-0.0"); + check(0.0, "0.0"); + check(-0.01, "-0.01"); + check(0.0125, "0.0125"); + check(0.000003, "0.000003"); + check(-3e-7, "-3e-7"); + check(123456.0, "123456.0"); + check(123456.1, "123456.1"); + check(12.3456, "12.3456"); + check(-0.123456, "-0.123456"); + check(0.1234567, "0.1234567"); + check(0.01234567, "0.01234567"); + check(0.001234567, "0.001234567"); + check(1.234567e-4, "1.234567e-4"); + check(-1234567.0, "-1.234567e+6"); + check(123456.7890123, "123456.7890123"); + check(1234567.890123, "1.234567890123e+6"); + check(1234567890123.0, "1.234567890123e+12"); + check(0.30000000000000004, "0.30000000000000004"); + check(0.030000000000000002, "0.030000000000000002"); + check(0.0030000000000000005, "0.0030000000000000005"); + check(3.0000000000000003e-4, "3.0000000000000003e-4"); + check(+double.nan, "nan"); + check(-double.nan, "nan"); + check(+double.infinity, "+inf"); + check(-double.infinity, "-inf"); + + spec.separatorChar = ','; + + check(-0.0, "-0.0"); + check(0.0, "0.0"); + check(-0.01, "-0.01"); + check(0.0125, "0.0125"); + check(0.000003, "0.000003"); + check(-3e-7, "-3e-7"); + check(123456.0, "123,456.0"); + check(123456e5, "12,345,600,000.0"); + check(123456.1, "123,456.1"); + check(12.3456, "12.3456"); + check(-0.123456, "-0.123456"); + check(0.1234567, "0.1234567"); + check(0.01234567, "0.01234567"); + check(0.001234567, "0.001234567"); + check(1.234567e-4, "0.0001234567"); + check(-1234567.0, "-1,234,567.0"); + check(123456.7890123, "123,456.7890123"); + check(1234567.890123, "1,234,567.890123"); + check(123456789012.0, "123,456,789,012.0"); + check(1234567890123.0, "1.234567890123e+12"); + check(0.30000000000000004, "0.30000000000000004"); + check(0.030000000000000002, "0.030000000000000002"); + check(0.0030000000000000005, "0.0030000000000000005"); + check(3.0000000000000003e-4, "0.00030000000000000003"); + check(3.0000000000000005e-6, "0.0000030000000000000005"); + check(3.0000000000000004e-7, "3.0000000000000004e-7"); + check(+double.nan, "nan"); + check(-double.nan, "nan"); + check(+double.infinity, "+inf"); + check(-double.infinity, "-inf"); + + spec.separatorChar = '_'; + spec.separatorCount = 2; + check(123456e5, "1_23_45_60_00_00.0"); + + spec.plus = true; + check(0.0125, "+0.0125"); + check(-0.0125, "-0.0125"); +} + +/// Prints structs and unions +pragma(inline, false) +void print(C = char, W, T)(scope ref W w, scope ref const T c) + if (isSomeChar!C && is(T == struct) || is(T == union) && !is(T : NumericSpec)) +{ + import mir.algebraic: isVariant; + static if (__traits(hasMember, T, "toString")) + { + static if (is(typeof(c.toString!C(w)))) + c.toString!C(w); + else + static if (isVariant!T || is(typeof(c.toString(w)))) + c.toString(w); + else + static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) + c.toString((scope const(C)[] s) { w.put(s); }); + else + static if (is(typeof(w.put(c.toString)))) + w.put(c.toString); + else + { + import std.format: FormatSpec; + FormatSpec!char fmt; + + static if (is(typeof(c.toString(w, fmt)))) + c.toString(w, fmt); + else + static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); }, fmt)))) + c.toString((scope const(C)[] s) { w.put(s); }, fmt); + else + // workaround for types with mutable toString + static if (is(typeof((*cast(T*)&c).toString(w, fmt)))) + (*cast(T*)&c).toString(w, fmt); + else + static if (is(typeof((*cast(T*)&c).toString((scope const(C)[] s) { w.put(s); }, fmt)))) + (*cast(T*)&c).toString((scope const(C)[] s) { w.put(s); }, fmt); + else + static if (is(typeof((*cast(T*)&c).toString(w)))) + (*cast(T*)&c).toString(w); + else + static if (is(typeof((*cast(T*)&c).toString((scope const(C)[] s) { w.put(s); })))) + (*cast(T*)&c).toString((scope const(C)[] s) { w.put(s); }); + else + static if (is(typeof(w.put((*cast(T*)&c).toString)))) + w.put((*cast(T*)&c).toString); + else + c.toString(w); + // static assert(0, T.stringof ~ ".toString definition is wrong: 'const' qualifier may be missing."); + } + + return; + } + else + static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) + { + scope const(C)[] string_of_c = c; + return w.print!C(string_of_c); + } + else + static if (hasIterableLightConst!T) + { + enum C left = '['; + enum C right = ']'; + enum C[2] sep = ", "; + w.put(left); + bool first = true; + foreach (ref e; c.lightConst) + { + if (!first) + printStaticString!C(w, sep); + first = false; + print!C(w, e); + } + w.put(right); + return; + } + else + { + enum C left = '('; + enum C right = ')'; + enum C[2] sep = ", "; + w.put(left); + foreach (i, ref e; c.tupleof) + { + static if (i) + w.printStaticString!C(sep); + print!C(w, e); + } + w.put(right); + return; + } +} + +/// ditto +// FUTURE: remove it +pragma(inline, false) +void print(C = char, W, T)(scope ref W w, scope const T c) + if (isSomeChar!C && is(T == struct) || is(T == union)) +{ + return print!(C, W, T)(w, c); +} + +/// +@safe pure nothrow @nogc +version (mir_test) unittest +{ + static struct A { scope void toString(C, W)(scope ref W w) const { w.put(C('a')); } } + static struct S { scope void toString(W)(scope ref W w) const { w.put("s"); } } + static struct D { scope void toString(Dg)(scope Dg sink) const { sink("d"); } } + static struct F { scope const(char)[] toString()() const return { return "f"; } } + static struct G { const(char)[] s = "g"; alias s this; } + + import mir.appender: scopedBuffer; + auto w = scopedBuffer!char; + assert(stringBuf << A() << S() << D() << F() << G() << getData == "asdfg"); +} + +/// Prints classes and interfaces +pragma(inline, false) +void print(C = char, W, T)(scope ref W w, scope const T c) + if (isSomeChar!C && is(T == class) || is(T == interface)) +{ + static if (__traits(hasMember, T, "toString") || __traits(compiles, { scope const(C)[] string_of_c = c; })) + { + if (c is null) + return w.print(null); + else + static if (is(typeof(c.toString!C(w)))) + { + c.toString!C(w); + return; + } + else + static if (is(typeof(c.toString(w)))) + { + c.toString(w); + return; + } + else + static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) + { + c.toString((scope const(C)[] s) { w.put(s); }); + return; + } + else + static if (is(typeof(w.put(c.toString)))) + { + w.put(c.toString); + return; + } + else + static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) + { + scope const(C)[] string_of_c = c; + return w.print!C(string_of_c); + } + else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing."); + } + else + static if (hasIterableLightConst!T) + { + enum C left = '['; + enum C right = ']'; + enum C[2] sep = ", "; + w.put(left); + bool first = true; + foreach (ref e; c.lightConst) + { + if (!first) + w.printStaticString!C(sep); + first = false; + print!C(w, e); + } + w.put(right); + return; + } + else + { + w.put(T.stringof); + return; + } +} + +/// +@safe pure nothrow +version (mir_test) unittest +{ + static class A { void toString(C, W)(scope ref W w) const { w.put(C('a')); } } + static class S { void toString(W)(scope ref W w) const { w.put("s"); } } + static class D { void toString(Dg)(scope Dg sink) const { sink("d"); } } + static class F { const(char)[] toString()() const return { return "f"; } } + static class G { const(char)[] s = "g"; alias s this; } + + assert(stringBuf << new A() << new S() << new D() << new F() << new G() << getData == "asdfg"); +} + +/// +void printStaticString(C, size_t N, W)(scope ref W w, scope ref const C[N] c) + if (isSomeChar!C && is(C == char) || is(C == wchar) || is(C == dchar)) +{ + static if (isFastBuffer!W) + { + enum immutable(ForeachType!(typeof(w.getBuffer(size_t.init))))[] value = c; + w.getStaticBuf!(value.length) = value; + w.advance(c.length); + } + else + { + w.put(c[]); + } + return; +} + +private template hasIterableLightConst(T) +{ + static if (__traits(hasMember, T, "lightConst")) + { + enum hasIterableLightConst = isIterable!(ReturnType!((const T t) => t.lightConst)); + } + else + { + enum hasIterableLightConst = false; + } +} + +private ref C[N] getStaticBuf(size_t N, C, W)(scope ref W w) + if (isFastBuffer!W) +{ + auto buf = w.getBuffer(N); + assert(buf.length >= N); + return buf.ptr[0 .. N]; +} + +private @trusted ref C[N] getStaticBuf(size_t N, C)(return scope ref C[] buf) +{ + assert(buf.length >= N); + return buf.ptr[0 .. N]; +} + +template isFastBuffer(W) +{ + enum isFastBuffer = __traits(hasMember, W, "getBuffer") && __traits(hasMember, W, "advance"); +} + +/// +void printZeroPad(C = char, W, I)(scope ref W w, const I c, size_t minimalLength) + if (isSomeChar!C && isIntegral!I && !is(I == enum)) +{ + static if (I.sizeof == 16) + enum N = 39; + else + static if (I.sizeof == 8) + enum N = 20; + else + enum N = 10; + C[N + !__traits(isUnsigned, I)] buf = void; + static if (__traits(isUnsigned, I)) + auto n = printUnsignedToTail(c, buf); + else + auto n = printSignedToTail(c, buf); + sizediff_t zeros = minimalLength - n; + + if (zeros > 0) + { + static if (!__traits(isUnsigned, I)) + { + if (c < 0) + { + n--; + w.put(C('-')); + } + } + do w.put(C('0')); + while(--zeros); + } + w.put(buf[$ - n .. $]); + return; +} + +/// +version (mir_test) unittest +{ + import mir.appender; + auto w = scopedBuffer!char; + + w.printZeroPad(-123, 5); + w.put(' '); + w.printZeroPad(123, 5); + + assert(w.data == "-0123 00123"); +} + +/// +size_t printBoolean(C)(bool c, ref C[5] buf) + if(is(C == char) || is(C == wchar) || is(C == dchar)) +{ + version(LDC) pragma(inline, true); + if (c) + { + buf[0] = 't'; + buf[1] = 'r'; + buf[2] = 'u'; + buf[3] = 'e'; + return 4; + } + else + { + buf[0] = 'f'; + buf[1] = 'a'; + buf[2] = 'l'; + buf[3] = 's'; + buf[4] = 'e'; + return 5; + } +} + + +/// Prints pointers +void print(C = char, W, T)(scope ref W w, scope const T* c) +{ + import mir.enums: getEnumIndex, enumStrings; + import mir.utility: _expect; + if (c is null) + return w.print!C(null); + return w.print!C(HexAddress!size_t((()@trusted=>cast(size_t)cast(const void*)c)())); +} diff --git a/source/mir/format_impl.d b/source/mir/format_impl.d new file mode 100644 index 00000000..548df598 --- /dev/null +++ b/source/mir/format_impl.d @@ -0,0 +1,437 @@ +/// +module mir.format_impl; + +import mir.format; + +@safe pure @nogc nothrow: + + +size_t printFloatingPointExtend(T, C)(T c, scope ref const FormatSpec spec, scope ref C[512] buf) @trusted +{ + char[512] cbuf = void; + return extendASCII(cbuf[].ptr, buf[].ptr, printFloatingPoint(cast(double)c, spec, cbuf)); +} + +size_t printFloatingPointGen(T)(T c, scope ref const FormatSpec spec, scope ref char[512] buf) @trusted + if(is(T == float) || is(T == double) || is(T == real)) +{ + import mir.math.common: copysign, fabs; + bool neg = copysign(1, c) < 0; + c = fabs(c); + char specFormat = spec.format; + version (CRuntime_Microsoft) + { + if (c != c || c.fabs == c.infinity) + { + size_t i; + char s = void; + if (copysign(1, c) < 0) + s = '-'; + else + if (spec.plus) + s = '+'; + else + if (spec.space) + s = ' '; + else + goto S; + buf[0] = s; + i = 1; + S: + static immutable char[3][2][2] special = [["inf", "INF"], ["nan", "NAN"]]; + auto p = &special[c != c][(specFormat & 0xDF) == specFormat][0]; + buf[i + 0] = p[0]; + buf[i + 1] = p[1]; + buf[i + 2] = p[2]; + return i + 3; + } + } + alias T = double; + static if (is(T == real)) + align(4) char[12] fmt = "%%%%%%*.*gL\0"; + else + align(4) char[12] fmt = "%%%%%%*.*g\0\0"; + + if (specFormat && specFormat != 's' && specFormat != 'g' && specFormat != 'G') + { + assert ( + specFormat == 'e' + || specFormat == 'E' + || specFormat == 'f' + || specFormat == 'F' + || specFormat == 'a' + || specFormat == 'A', "Wrong floating point format specifier."); + fmt[9] = specFormat; + } + uint fmtRevLen = 5; + if (spec.hash) fmt[fmtRevLen--] = '#'; + if (spec.space) fmt[fmtRevLen--] = ' '; + if (spec.zero) fmt[fmtRevLen--] = '0'; + if (spec.plus) fmt[fmtRevLen--] = '+'; + if (spec.dash) fmt[fmtRevLen--] = '-'; + + import core.stdc.stdio : snprintf; + ptrdiff_t res = assumePureSafe(&snprintf)((()@trusted =>buf.ptr)(), buf.length - 1, &fmt[fmtRevLen], spec.width, spec.precision, c); + assert (res >= 0, "snprintf failed to print a floating point number"); + import mir.utility: min; + return res < 0 ? 0 : min(cast(size_t)res, buf.length - 1); +} + +auto assumePureSafe(T)(T t) @trusted + // if (isFunctionPointer!T || isDelegate!T) +{ + import std.traits; + enum attrs = (functionAttributes!T | FunctionAttribute.pure_ | FunctionAttribute.safe) & ~FunctionAttribute.system; + return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; +} + +////////// FLOATING POINT ////////// + +size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref char[512] buf) +{ + return printFloatingPoint(cast(double)c, spec, buf); +} + +size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref char[512] buf) +{ + return printFloatingPointGen(c, spec, buf); +} + +size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref char[512] buf) +{ + version (CRuntime_Microsoft) + { + return printFloatingPoint(cast(double) c, spec, buf); + } + else + { + return printFloatingPointGen(c, spec, buf); + } +} + +size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref wchar[512] buf) +{ + return printFloatingPoint(cast(double)c, spec, buf); +} + +size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref wchar[512] buf) +{ + return printFloatingPointExtend(c, spec, buf); +} + +size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref wchar[512] buf) +{ + version (CRuntime_Microsoft) + { + return printFloatingPoint(cast(double) c, spec, buf); + } + else + { + return printFloatingPointExtend(c, spec, buf); + } +} + +size_t printFloatingPoint(float c, scope ref const FormatSpec spec, scope ref dchar[512] buf) +{ + return printFloatingPoint(cast(double)c, spec, buf); +} + +size_t printFloatingPoint(double c, scope ref const FormatSpec spec, scope ref dchar[512] buf) +{ + return printFloatingPointExtend(c, spec, buf); +} + +size_t printFloatingPoint(real c, scope ref const FormatSpec spec, scope ref dchar[512] buf) +{ + version (CRuntime_Microsoft) + { + return printFloatingPoint(cast(double) c, spec, buf); + } + else + { + return printFloatingPointExtend(c, spec, buf); + } +} + +nothrow: + +size_t printHexadecimal(uint c, ref char[8] buf, bool upper) { return printHexadecimalGen!(uint, char)(c, buf, upper); } +size_t printHexadecimal(ulong c, ref char[16] buf, bool upper) { return printHexadecimalGen!(ulong, char)(c, buf, upper); } +static if (is(ucent)) +size_t printHexadecimal(ucent c, ref char[32] buf, bool upper) { return printHexadecimalGen!(ucent, char)(c, buf, upper); } + +size_t printHexadecimal(uint c, ref wchar[8] buf, bool upper) { return printHexadecimalGen!(uint, wchar)(c, buf, upper); } +size_t printHexadecimal(ulong c, ref wchar[16] buf, bool upper) { return printHexadecimalGen!(ulong, wchar)(c, buf, upper); } +static if (is(ucent)) +size_t printHexadecimal(ucent c, ref wchar[32] buf, bool upper) { return printHexadecimalGen!(ucent, wchar)(c, buf, upper); } + +size_t printHexadecimal(uint c, ref dchar[8] buf, bool upper) { return printHexadecimalGen!(uint, dchar)(c, buf, upper); } +size_t printHexadecimal(ulong c, ref dchar[16] buf, bool upper) { return printHexadecimalGen!(ulong, dchar)(c, buf, upper); } +static if (is(ucent)) +size_t printHexadecimal(ucent c, ref dchar[32] buf, bool upper) { return printHexadecimalGen!(ucent, dchar)(c, buf, upper); } + +size_t printHexadecimalGen(T, C)(T c, ref C[T.sizeof * 2] buf, bool upper) @trusted +{ + if (c < 10) + { + buf[0] = cast(char)('0' + c); + return 1; + } + import mir.bitop: ctlz; + immutable hexString = upper ? hexStringUpper : hexStringLower; + size_t ret = cast(size_t) ctlz(c); + ret = (ret >> 2) + ((ret & 3) != 0); + size_t i = ret; + do + { + buf.ptr[--i] = hexStringUpper[c & 0xF]; + c >>= 4; + } + while(i); + return ret; +} + + size_t printHexAddress(ubyte c, ref char[2] buf, bool upper) { return printHexAddressGen!(ubyte, char)(c, buf, upper); } + size_t printHexAddress(ushort c, ref char[4] buf, bool upper) { return printHexAddressGen!(ushort, char)(c, buf, upper); } +size_t printHexAddress(uint c, ref char[8] buf, bool upper) { return printHexAddressGen!(uint, char)(c, buf, upper); } +size_t printHexAddress(ulong c, ref char[16] buf, bool upper) { return printHexAddressGen!(ulong, char)(c, buf, upper); } +static if (is(ucent)) +size_t printHexAddress(ucent c, ref char[32] buf, bool upper) { return printHexAddressGen!(ucent, char)(c, buf, upper); } + + size_t printHexAddress(ubyte c, ref wchar[2] buf, bool upper) { return printHexAddressGen!(ubyte, wchar)(c, buf, upper); } + size_t printHexAddress(ushort c, ref wchar[4] buf, bool upper) { return printHexAddressGen!(ushort, wchar)(c, buf, upper); } +size_t printHexAddress(uint c, ref wchar[8] buf, bool upper) { return printHexAddressGen!(uint, wchar)(c, buf, upper); } +size_t printHexAddress(ulong c, ref wchar[16] buf, bool upper) { return printHexAddressGen!(ulong, wchar)(c, buf, upper); } +static if (is(ucent)) +size_t printHexAddress(ucent c, ref wchar[32] buf, bool upper) { return printHexAddressGen!(ucent, wchar)(c, buf, upper); } + + size_t printHexAddress(ubyte c, ref dchar[2] buf, bool upper) { return printHexAddressGen!(ubyte, dchar)(c, buf, upper); } + size_t printHexAddress(ushort c, ref dchar[4] buf, bool upper) { return printHexAddressGen!(ushort, dchar)(c, buf, upper); } +size_t printHexAddress(uint c, ref dchar[8] buf, bool upper) { return printHexAddressGen!(uint, dchar)(c, buf, upper); } +size_t printHexAddress(ulong c, ref dchar[16] buf, bool upper) { return printHexAddressGen!(ulong, dchar)(c, buf, upper); } +static if (is(ucent)) +size_t printHexAddress(ucent c, ref dchar[32] buf, bool upper) { return printHexAddressGen!(ucent, dchar)(c, buf, upper); } + +size_t printHexAddressGen(T, C)(T c, ref C[T.sizeof * 2] buf, bool upper) +{ + static if (T.sizeof == 16) + { + printHexAddress(cast(ulong)(c >> 64), buf[0 .. 16], upper); + printHexAddress(cast(ulong) c, buf[16 .. 32], upper); + } + else + { + immutable hexString = upper ? hexStringUpper : hexStringLower; + foreach_reverse(ref e; buf) + { + e = hexStringUpper[c & 0xF]; + c >>= 4; + } + } + return buf.length; +} + +static immutable hexStringUpper = "0123456789ABCDEF"; +static immutable hexStringLower = "0123456789abcdef"; + +size_t printBufferShift(size_t length, size_t shift, scope char* ptr) { return printBufferShiftGen!char(length, shift, ptr); } +size_t printBufferShift(size_t length, size_t shift, scope wchar* ptr) { return printBufferShiftGen!wchar(length, shift, ptr); } +size_t printBufferShift(size_t length, size_t shift, scope dchar* ptr) { return printBufferShiftGen!dchar(length, shift, ptr); } + +size_t printBufferShiftGen(C)(size_t length, size_t shift, scope C* ptr) @trusted +{ + size_t i; + do ptr[i] = ptr[shift + i]; + while(++i < length); + return length; +} + +size_t printSigned(int c, scope ref char[11] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } +size_t printSigned(long c, scope ref char[21] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } +static if (is(cent)) +size_t printSigned(cent c, scope ref char[40] buf, char sign = '\0') { return printSignedGen(c, buf, sign); } + +size_t printSigned(int c, scope ref wchar[11] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } +size_t printSigned(long c, scope ref wchar[21] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } +static if (is(cent)) +size_t printSigned(cent c, scope ref wchar[40] buf, wchar sign = '\0') { return printSignedGen(c, buf, sign); } + +size_t printSigned(int c, scope ref dchar[11] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } +size_t printSigned(long c, scope ref dchar[21] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } +static if (is(cent)) +size_t printSigned(cent c, scope ref dchar[40] buf, dchar sign = '\0') { return printSignedGen(c, buf, sign); } + + +size_t printSignedToTail(int c, scope ref char[11] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } +size_t printSignedToTail(long c, scope ref char[21] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } +static if (is(cent)) +size_t printSignedToTail(cent c, scope ref char[40] buf, char sign = '\0') { return printSignedToTailGen(c, buf, sign); } + +size_t printSignedToTail(int c, scope ref wchar[11] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } +size_t printSignedToTail(long c, scope ref wchar[21] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } +static if (is(cent)) +size_t printSignedToTail(cent c, scope ref wchar[40] buf, wchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } + +size_t printSignedToTail(int c, scope ref dchar[11] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } +size_t printSignedToTail(long c, scope ref dchar[21] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } +static if (is(cent)) +size_t printSignedToTail(cent c, scope ref dchar[40] buf, dchar sign = '\0') { return printSignedToTailGen(c, buf, sign); } + +size_t printSignedGen(T, C, size_t N)(T c, scope ref C[N] buf, C sign) @trusted +{ + auto ret = printSignedToTail(c, buf, sign); + if (auto shift = buf.length - ret) + { + return printBufferShift(ret, shift, buf[].ptr); + } + return ret; +} + +size_t printSignedToTailGen(T, C, size_t N)(T c, scope ref C[N] buf, C sign) +{ + if (c < 0) + { + sign = '-'; + c = -c; + } + + auto ret = printUnsignedToTail(c, buf[1 .. N]); + + if (sign != '\0') + { + buf[$ - ++ret] = sign; + } + return ret; +} + +size_t printUnsigned(uint c, scope ref char[10] buf) { return printUnsignedGen(c, buf); } +size_t printUnsigned(ulong c, scope ref char[20] buf) { return printUnsignedGen(c, buf); } +static if (is(ucent)) +size_t printUnsigned(ucent c, scope ref char[39] buf) { return printUnsignedGen(c, buf); } + +size_t printUnsigned(uint c, scope ref wchar[10] buf) { return printUnsignedGen(c, buf); } +size_t printUnsigned(ulong c, scope ref wchar[20] buf) { return printUnsignedGen(c, buf); } +static if (is(ucent)) +size_t printUnsigned(ucent c, scope ref wchar[39] buf) { return printUnsignedGen(c, buf); } + +size_t printUnsigned(uint c, scope ref dchar[10] buf) { return printUnsignedGen(c, buf); } +size_t printUnsigned(ulong c, scope ref dchar[20] buf) { return printUnsignedGen(c, buf); } +static if (is(ucent)) +size_t printUnsigned(ucent c, scope ref dchar[39] buf) { return printUnsignedGen(c, buf); } + +size_t printUnsignedToTail(uint c, scope ref char[10] buf) { return printUnsignedToTailGen(c, buf); } +size_t printUnsignedToTail(ulong c, scope ref char[20] buf) { return printUnsignedToTailGen(c, buf); } +static if (is(ucent)) +size_t printUnsignedToTail(ucent c, scope ref char[39] buf) { return printUnsignedToTailGen(c, buf); } + +size_t printUnsignedToTail(uint c, scope ref wchar[10] buf) { return printUnsignedToTailGen(c, buf); } +size_t printUnsignedToTail(ulong c, scope ref wchar[20] buf) { return printUnsignedToTailGen(c, buf); } +static if (is(ucent)) +size_t printUnsignedToTail(ucent c, scope ref wchar[39] buf) { return printUnsignedToTailGen(c, buf); } + +size_t printUnsignedToTail(uint c, scope ref dchar[10] buf) { return printUnsignedToTailGen(c, buf); } +size_t printUnsignedToTail(ulong c, scope ref dchar[20] buf) { return printUnsignedToTailGen(c, buf); } +static if (is(ucent)) +size_t printUnsignedToTail(ucent c, scope ref dchar[39] buf) { return printUnsignedToTailGen(c, buf); } + +size_t printUnsignedToTailGen(T, C, size_t N)(T c, scope ref C[N] buf) @trusted +{ + static if (T.sizeof == 4) + { + if (c < 10) + { + buf[$ - 1] = cast(char)('0' + c); + return 1; + } + static assert(N == 10); + } + else + static if (T.sizeof == 8) + { + if (c <= uint.max) + { + return printUnsignedToTail(cast(uint)c, buf[$ - 10 .. $]); + } + static assert(N == 20); + } + else + static if (T.sizeof == 16) + { + if (c <= ulong.max) + { + return printUnsignedToTail(cast(ulong)c, buf[$ - 20 .. $]); + } + static assert(N == 39); + } + else + static assert(0); + size_t refLen = buf.length; + do { + T nc = c / 10; + buf[].ptr[--refLen] = cast(C)('0' + c - nc * 10); + c = nc; + } + while(c); + return buf.length - refLen; +} + +size_t printUnsignedGen(T, C, size_t N)(T c, scope ref C[N] buf) @trusted +{ + auto ret = printUnsignedToTail(c, buf); + if (auto shift = buf.length - ret) + { + return printBufferShift(ret, shift, buf[].ptr); + } + return ret; +} + +nothrow @trusted +size_t extendASCII(char* from, wchar* to, size_t n) +{ + foreach (i; 0 .. n) + to[i] = from[i]; + return n; +} + +nothrow @trusted +size_t extendASCII(char* from, dchar* to, size_t n) +{ + foreach (i; 0 .. n) + to[i] = from[i]; + return n; +} + +version (mir_test) unittest +{ + import mir.appender; + import mir.format; + + assert (stringBuf() << 123L << getData == "123"); + static assert (stringBuf() << 123 << getData == "123"); +} + +void printIntegralZeroImpl(C, size_t N, W, I)(ref scope W w, I c, size_t zeroLen) +{ + static if (__traits(isUnsigned, I)) + alias impl = printUnsignedToTail; + else + alias impl = printSignedToTail; + C[N] buf = void; + size_t n = impl(c, buf); + static if (!__traits(isUnsigned, I)) + { + if (c < 0) + { + n--; + w.put(C('-')); + } + } + sizediff_t zeros = zeroLen - n; + if (zeros > 0) + { + do w.put(C('0')); + while(--zeros); + } + w.put(buf[$ - n .. $]); + return w; +} diff --git a/source/mir/graph/package.d b/source/mir/graph/package.d index 6bc31a6e..979ae297 100644 --- a/source/mir/graph/package.d +++ b/source/mir/graph/package.d @@ -1,9 +1,9 @@ /++ Basic routines to work with graphs. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2018-, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, graph, $1)$(NBSP) @@ -12,11 +12,10 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) module mir.graph; -import mir.math.common: optmath; - -@optmath: +import mir.math.common: fmamath; import mir.series; +import mir.rc.array; import mir.ndslice.iterator: ChopIterator; /// @@ -26,13 +25,27 @@ alias Graph(I = uint, J = size_t) = Slice!(GraphIterator!(I, J)); /// alias GraphSeries(T, I = uint, J = size_t) = Series!(T*, GraphIterator!(I, J)); +/// +alias RCGraphIterator(I = uint, J = size_t) = ChopIterator!(RCI!size_t, RCI!uint); +/// +alias RCGraph(I = uint, J = size_t) = Slice!(RCGraphIterator!(I, J)); +/// +alias RCGraphSeries(T, I = uint, J = size_t) = Series!(RCI!T, RCGraphIterator!(I, J)); + +private static immutable exc_msg = "graphSeries: graph should contains keys for all vertixes"; +version(D_Exceptions) +{ + private static immutable exception = new Exception(exc_msg); +} + /++ -Param: +Params: aaGraph = graph that is represented as associative array Returns: A graph series composed of keys (sorted `.index`) and arrays of indeces (`.data`) Complexity: `O(log(V) (V + E))` +/ +@fmamath GraphSeries!(T, I, J) graphSeries(I = uint, J = size_t, T, Range)(in Range[T] aaGraph) { import mir.array.allocation: array; @@ -54,7 +67,13 @@ GraphSeries!(T, I, J) graphSeries(I = uint, J = size_t, T, Range)(in Range[T] aa { import mir.ndslice.sorting: transitionIndex; auto index = keys.transitionIndex(elem); - assert(index < keys.length, "graphSeries: aaGraph should contains keys for all vertixes"); + if (index >= keys.length) + { + version(D_Exceptions) + { import mir.exception : toMutable; throw exception.toMutable; } + else + assert(0, exc_msg); + } data[dataIndex++] = cast(I) index; } } @@ -79,3 +98,69 @@ pure version(mir_test) unittest [1], // c ]); } + +/++ +Params: + graph = graph that is represented a series +Returns: + A graph as an arrays of indeces +Complexity: `O(log(V) (V + E))` ++/ +@fmamath +RCGraph!(I, J) rcgraph(I = uint, J = size_t, KeyIterator, RangeIterator)(Series!(KeyIterator, RangeIterator) graph) +{ + import mir.array.allocation: array; + import mir.ndslice.sorting; + import mir.ndslice; + auto scopeGraph = graph.lightScope; + auto keys = scopeGraph.index; + auto graphData = scopeGraph.data; + size_t dataLength; + foreach (ref v; graphData) + dataLength += v.length; + auto data = rcslice!I(dataLength); + auto components = rcslice!J(keys.length + 1); + size_t dataIndex; + + foreach (i; 0 .. keys.length) + { + components[i] = cast(J) dataIndex; + foreach(ref elem; graphData[i]) + { + import mir.ndslice.sorting: transitionIndex; + auto index = keys.transitionIndex(elem); + if (index >= keys.length) + { + version(D_Exceptions) + { import mir.exception : toMutable; throw exception.toMutable; } + else + assert(0, exc_msg); + } + data[dataIndex++] = cast(I) index; + } + } + components[keys.length] = dataIndex; + return data._iterator.chopped(components); +} + +/// +@safe pure @nogc version(mir_test) +unittest +{ + import mir.series: series; + + static immutable keys = ["a", "b", "c"]; + static immutable data = [ + ["b", "c"], + ["a"], + ["b"], + ]; + + static immutable graphValue = [ + [1, 2], // a + [0], // b + [1], // c + ]; + + assert (series(keys, data).rcgraph == graphValue); +} diff --git a/source/mir/graph/tarjan.d b/source/mir/graph/tarjan.d index 78b9ea6b..f8821b31 100644 --- a/source/mir/graph/tarjan.d +++ b/source/mir/graph/tarjan.d @@ -3,9 +3,9 @@ This is a submodule of $(MREF mir,graph). Tarjan's strongly connected components algorithm. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2018-, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -16,9 +16,9 @@ module mir.graph.tarjan; import std.traits; -import mir.math.common: optmath; +import mir.math.common: fmamath; -@optmath: +@fmamath: /++ @@ -32,9 +32,10 @@ The implementation is loop based. It does not use recursion and does not have st Complexity: worst-case `O(|V| + |E|)`. Params: + RC = nogc mode, refcounted output graph = components (ndslice) sorted in the direction of traversal of the graph. Each component is an array of indeces. Returns: - components (ndslice of arrays of indexes) + components (ndslice of arrays of indices) Note: The implementation returns components sorted in the direction of traversal of the graph. @@ -44,7 +45,7 @@ See_also: $(SUBREF utility, graph) +/ pragma(inline, false) -auto tarjan(G, I = Unqual!(ForeachType!(ForeachType!G)))(G graph) +auto tarjan(bool RC = false, G, I = Unqual!(ForeachType!(ForeachType!G)))(G graph) if (isUnsigned!I) { import mir.utility: min; @@ -87,35 +88,49 @@ auto tarjan(G, I = Unqual!(ForeachType!(ForeachType!G)))(G graph) } } - bool[] onStack = new bool[graph.length]; - I[] stack; - IndexNode[] indeces; - LoopNode[] loopStack; - I index; + sizediff_t stackIndex; sizediff_t backStackIndex = graph.length; sizediff_t componentBackStackIndex = graph.length + 1; - if (__ctfe) + static if (RC) { - stack = new I[graph.length]; - indeces = new IndexNode[graph.length]; - loopStack = new LoopNode[componentBackStackIndex]; + import mir.rc.array; + auto onStack = RCArray!bool(graph.length); + auto stack = RCArray!I(graph.length, true); + auto indeces = RCArray!IndexNode(graph.length, true); + auto loopStack = RCArray!LoopNode(componentBackStackIndex, true); } else { - () @trusted { - import std.array: uninitializedArray; + I[] stack; + IndexNode[] indeces; + LoopNode[] loopStack; + + bool[] onStack = new bool[graph.length]; + if (__ctfe) + { + + stack = new I[graph.length]; + indeces = new IndexNode[graph.length]; + loopStack = new LoopNode[componentBackStackIndex]; + } + else + { + () @trusted { + import std.array: uninitializedArray; - stack = uninitializedArray!(I[])(graph.length); - indeces = uninitializedArray!(IndexNode[])(graph.length); - loopStack = uninitializedArray!(LoopNode[])(componentBackStackIndex); - } (); + stack = uninitializedArray!(I[])(graph.length); + indeces = uninitializedArray!(IndexNode[])(graph.length); + loopStack = uninitializedArray!(LoopNode[])(componentBackStackIndex); + } (); + } } foreach(ref node; indeces) node.index = undefined; + I index; foreach(size_t v; 0u .. graph.length) { if (indeces[v].isUndefined) @@ -191,26 +206,42 @@ auto tarjan(G, I = Unqual!(ForeachType!(ForeachType!G)))(G graph) } } - S[] pairwiseIndex; - if (__ctfe) + const indexLength = graph.length + 1 - componentBackStackIndex + 1; + static if (RC) { - pairwiseIndex = new S[graph.length - componentBackStackIndex + 1]; + auto pairwiseIndex = RCArray!S(indexLength, true); } else { - () @trusted { - import std.array: uninitializedArray; - pairwiseIndex = uninitializedArray!(S[])(graph.length + 1 - componentBackStackIndex + 1); - } (); + S[] pairwiseIndex; + if (__ctfe) + { + pairwiseIndex = new S[indexLength]; + } + else + { + () @trusted { + import std.array: uninitializedArray; + pairwiseIndex = uninitializedArray!(S[])(indexLength); + } (); + } } - foreach (i, ref e; loopStack[componentBackStackIndex .. $]) + foreach (i, ref e; loopStack[][componentBackStackIndex .. $]) { pairwiseIndex[i] = e.index; } pairwiseIndex[$ - 1] = cast(I) graph.length; import mir.ndslice.topology: chopped; - return (()@trusted {return stack.ptr; }()).chopped(pairwiseIndex); + static if (RC) + { + import core.lifetime: move; + return chopped(RCI!I(stack.move), pairwiseIndex.asSlice); + } + else + { + return (()@trusted {return stack.ptr; }()).chopped(pairwiseIndex); + } } /++ @@ -241,14 +272,21 @@ pure version(mir_test) unittest "11": [], ].graphSeries; - auto components = gs.data.tarjan; - assert(components == [ + static immutable result = [ [0], [1, 2, 5, 4, 3, 6], [10], [7, 8, 9], - [11]]); + [11]]; + + // chec GC interface + auto components = gs.data.tarjan; + assert(components == result); + // check @nogc interface + // Note: The lambda function is used here to show @nogc mode explicitly. + auto rccomponents = (() @nogc => gs.data.tarjan!true )(); + assert(rccomponents == result); } /++ diff --git a/source/mir/internal/ldc_simd.d b/source/mir/internal/ldc_simd.d new file mode 100644 index 00000000..d7fb9987 --- /dev/null +++ b/source/mir/internal/ldc_simd.d @@ -0,0 +1,308 @@ +// module ldc.simd; +// compilers has permanent issues with .di files +module mir.internal.ldc_simd; + +version(LDC): + +import core.simd; +import ldc.llvmasm; + +pure: +nothrow: +@nogc: +@trusted: + +private template isFloatingPoint(T) +{ + enum isFloatingPoint = + is(T == float) || + is(T == double) || + is(T == real); +} + +private template isIntegral(T) +{ + enum isIntegral = + is(T == byte) || + is(T == ubyte) || + is(T == short) || + is(T == ushort) || + is(T == int) || + is(T == uint) || + is(T == long) || + is(T == ulong); +} + +private template isSigned(T) +{ + enum isSigned = + is(T == byte) || + is(T == short) || + is(T == int) || + is(T == long); +} + +private template IntOf(T) +if(isIntegral!T || isFloatingPoint!T) +{ + enum n = T.sizeof; + static if(n == 1) + alias byte IntOf; + else static if(n == 2) + alias short IntOf; + else static if(n == 4) + alias int IntOf; + else static if(n == 8) + alias long IntOf; + else + static assert(0, "Type not supported"); +} + +private template BaseType(V) +{ + alias typeof(V.array[0]) BaseType; +} + +private template numElements(V) +{ + enum numElements = V.sizeof / BaseType!(V).sizeof; +} + +private template llvmType(T) +{ + static if(is(T == float)) + enum llvmType = "float"; + else static if(is(T == double)) + enum llvmType = "double"; + else static if(is(T == byte) || is(T == ubyte) || is(T == void)) + enum llvmType = "i8"; + else static if(is(T == short) || is(T == ushort)) + enum llvmType = "i16"; + else static if(is(T == int) || is(T == uint)) + enum llvmType = "i32"; + else static if(is(T == long) || is(T == ulong)) + enum llvmType = "i64"; + else + static assert(0, + "Can't determine llvm type for D type " ~ T.stringof); +} + +private template llvmVecType(V) +{ + static if(is(V == void16)) + enum llvmVecType = "<16 x i8>"; + else static if(is(V == void32)) + enum llvmVecType = "<32 x i8>"; + else + { + alias BaseType!V T; + enum int n = numElements!V; + enum llvmT = llvmType!T; + enum llvmVecType = "<"~n.stringof~" x "~llvmT~">"; + } +} + +/** +This template provides access to +$(LINK2 http://llvm.org/docs/LangRef.html#i_shufflevector, +LLVM's shufflevector instruction). + +Example: +--- +int4 a = [0, 10, 20, 30]; +int4 b = [40, 50, 60, 70]; +int4 c = shufflevector!(int4, 0, 2, 4, 6)(a, b); +assert(c.array == [0, 20, 40, 60]); +--- +*/ + +template shufflevector(V, mask...) +if(is(typeof(llvmVecType!V)) && mask.length == numElements!V) +{ + enum int n = mask.length; + enum llvmV = llvmVecType!V; + + template genMaskIr(string ir, m...) + { + static if(m.length == 0) + enum genMaskIr = ir; + else + { + enum int mfront = m[0]; + + enum genMaskIr = + genMaskIr!(ir ~ ", i32 " ~ mfront.stringof, m[1 .. $]); + } + } + enum maskIr = genMaskIr!("", mask)[2 .. $]; + enum ir = ` + %r = shufflevector `~llvmV~` %0, `~llvmV~` %1, <`~n.stringof~` x i32> <`~maskIr~`> + ret `~llvmV~` %r`; + + alias __ir_pure!(ir, V, V, V) shufflevector; +} + +/** +This template provides access to +$(LINK2 http://llvm.org/docs/LangRef.html#i_extractelement, +LLVM's extractelement instruction). + +Example: +--- +int4 a = [0, 10, 20, 30]; +int k = extractelement!(int4, 2)(a); +assert(k == 20); +--- +*/ + +template extractelement(V, int i) +if(is(typeof(llvmVecType!V)) && i < numElements!V) +{ + enum llvmT = llvmType!(BaseType!V); + enum llvmV = llvmVecType!V; + enum ir = ` + %r = extractelement `~llvmV~` %0, i32 `~i.stringof~` + ret `~llvmT~` %r`; + + alias __ir_pure!(ir, BaseType!V, V) extractelement; +} + +/** +This template provides access to +$(LINK2 http://llvm.org/docs/LangRef.html#i_insertelement, +LLVM's insertelement instruction). + +Example: +--- +int4 a = [0, 10, 20, 30]; +int b = insertelement!(int4, 2)(a, 50); +assert(b.array == [0, 10, 50, 30]); +--- +*/ + +template insertelement(V, int i) +if(is(typeof(llvmVecType!V)) && i < numElements!V) +{ + enum llvmT = llvmType!(BaseType!V); + enum llvmV = llvmVecType!V; + enum ir = ` + %r = insertelement `~llvmV~` %0, `~llvmT~` %1, i32 `~i.stringof~` + ret `~llvmV~` %r`; + + alias __ir_pure!(ir, V, V, BaseType!V) insertelement; +} + +/** +loadUnaligned: Loads a vector from an unaligned pointer. +Example: +--- +int[4] a = [0, 10, 20, 30]; +int4 v = loadUnaligned!int4(a.ptr); +assert(v.array == a); +--- +*/ +template loadUnaligned(V) +if(is(typeof(llvmVecType!V))) +{ + enum llvmElementType = llvmType!(BaseType!V); + enum llvmV = llvmVecType!V; + enum ir = ` + %p = bitcast `~llvmElementType~`* %0 to `~llvmV~`* + %r = load `~llvmV~`, `~llvmV~`* %p, align 1 + ret `~llvmV~` %r`; + private alias impl = __ir_pure!(ir, V, const(BaseType!V)*); + + pragma(inline, true): + + V loadUnaligned(const(BaseType!V)* p) + { + return impl(p); + } +} + +/** +storeUnaligned: Stores a vector to an unaligned pointer. +Example: +--- +int[4] a; +int4 v = [0, 10, 20, 30]; +storeUnaligned!int4(v, a.ptr); +assert(v.array == a); +--- +*/ +template storeUnaligned(V) +if(is(typeof(llvmVecType!V))) +{ + enum llvmElementType = llvmType!(BaseType!V); + enum llvmV = llvmVecType!V; + enum ir = ` + %p = bitcast `~llvmElementType~`* %1 to `~llvmV~`* + store `~llvmV~` %0, `~llvmV~`* %p, align 1`; + private alias impl = __ir_pure!(ir, void, V, BaseType!V*); + + pragma(inline, true): + + void storeUnaligned(V value, BaseType!V* p) + { + impl(value, p); + } +} + +private enum Cond{ eq, ne, gt, ge } + +private template cmpMask(Cond cond) +{ + template cmpMask(V) + if(is(IntOf!(BaseType!V))) + { + alias BaseType!V T; + enum llvmT = llvmType!T; + + alias IntOf!T Relem; + + enum int n = numElements!V; + alias __vector(Relem[n]) R; + + enum llvmV = llvmVecType!V; + enum llvmR = llvmVecType!R; + enum sign = + (cond == Cond.eq || cond == Cond.ne) ? "" : + isSigned!T ? "s" : "u"; + enum condStr = + cond == Cond.eq ? "eq" : + cond == Cond.ne ? "ne" : + cond == Cond.ge ? "ge" : "gt"; + enum op = + isFloatingPoint!T ? "fcmp o"~condStr : "icmp "~sign~condStr; + + enum ir = ` + %cmp = `~op~` `~llvmV~` %0, %1 + %r = sext <`~n.stringof~` x i1> %cmp to `~llvmR~` + ret `~llvmR~` %r`; + + alias __ir_pure!(ir, R, V, V) cmpMask; + } +} + +/** +equalMask, notEqualMask, greaterMask and greaterOrEqualMask perform an +element-wise comparison between two vectors and return a vector with +signed integral elements. The number of elements in the returned vector +and their size is the same as in parameter vectors. If the condition in +the name of the function holds for elements of the parameter vectors at +a given index, all bits of the element of the result at that index are +set to 1, otherwise the element of the result is zero. + +Example: +--- +float4 a = [1, 3, 5, 7]; +float4 b = [2, 3, 4, 5]; +int4 c = greaterMask!float4(a, b); +writeln(c.array); +assert(c.array == [0, 0, 0xffff_ffff, 0xffff_ffff]); +--- +*/ +alias cmpMask!(Cond.eq) equalMask; +alias cmpMask!(Cond.ne) notEqualMask; /// Ditto +alias cmpMask!(Cond.gt) greaterMask; /// Ditto +alias cmpMask!(Cond.ge) greaterOrEqualMask; /// Ditto diff --git a/source/mir/interpolate/constant.d b/source/mir/interpolate/constant.d index 83a627a6..a41d85a6 100644 --- a/source/mir/interpolate/constant.d +++ b/source/mir/interpolate/constant.d @@ -3,9 +3,9 @@ $(H2 Constant Interpolation) See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2017, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) @@ -13,19 +13,19 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.interpolate.constant; -@optmath: +@fmamath: /// version(mir_test) -@safe pure unittest +@safe pure @nogc unittest { import mir.ndslice; import mir.math.common: approxEqual; - immutable x = [0, 1, 2, 3]; - immutable y = [10, 20, 30, 40]; + static immutable x = [0, 1, 2, 3]; + static immutable y = [10, 20, 30, 40]; - auto interpolant = constant!int(x.sliced, y.sliced); + auto interpolant = constant!int(x.rcslice, y.rcslice!(const int)); assert(interpolant(-1) == 10); assert(interpolant(0) == 10); @@ -38,15 +38,20 @@ version(mir_test) } - -import std.traits; -import mir.primitives; -import mir.ndslice.slice; +import core.lifetime: move; import mir.internal.utility; -import mir.math.common: optmath; +import mir.functional; +import mir.interpolate; +import mir.math.common: fmamath; +import mir.ndslice.slice; +import mir.primitives; +import mir.rc.array; +import mir.utility: min, max; +import std.meta: AliasSeq, staticMap; +import std.traits; +/// public import mir.interpolate: atInterval; -import mir.interpolate; /++ Constructs multivariate constant interpolant with nodes on rectilinear grid. @@ -60,98 +65,82 @@ Constraints: Returns: $(LREF Constant) +/ -template constant(T, size_t N = 1, FirstGridIterator = immutable(T)*, NextGridIterators = Repeat!(N - 1, FirstGridIterator)) - if (is(T == Unqual!T) && N <= 6) +Constant!(F, N, X) constant(F, size_t N = 1, X = F) + (Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) values) { - static if (N > 1) pragma(msg, "Warning: multivariate constant interpolant was not tested."); - - import std.meta: AliasSeq; - -@optmath: - - private alias GridIterators = AliasSeq!(FirstGridIterator, NextGridIterators); - private alias GridVectors = Constant!(T, N, GridIterators).GridVectors; - - /++ - Params: - grid = immutable `x` values for interpolant - values = `f(x)` values for interpolant - Constraints: - `grid` and `values` must have the same length >= 3 - Returns: $(LREF Spline) - +/ - Constant!(T, N, GridIterators) constant(yIterator, SliceKind ykind)( - GridVectors grid, - scope Slice!(yIterator, 1, ykind) values - ) pure @trusted - { - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - auto ret = typeof(return)(grid); - ret._data[] = values; - return ret.move; - } + return typeof(return)(forward!grid, values.move); } /++ Multivariate constant interpolant with nodes on rectilinear grid. +/ -struct Constant(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterators = Repeat!(N - 1, FirstGridIterator)) - if (N && N <= 6 && NextGridIterators.length == N - 1) +struct Constant(F, size_t N = 1, X = F) + if (N && N <= 6) { - import mir.rc.array; - import std.meta: AliasSeq, staticMap; - - package alias GridIterators = AliasSeq!(FirstGridIterator, NextGridIterators); - package alias GridVectors = staticMap!(GridVector, GridIterators); - -@optmath: +@fmamath: /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) - mir_slice!(mir_rci!F, N) _data; + Slice!(RCI!(const F), N) _data; /// Grid iterators. $(RED For internal use.) - GridIterators _grid; + Repeat!(N, RCI!(immutable X)) _grid; + +extern(D): - import mir.utility: min, max; - package enum alignment = min(64u, F.sizeof).max(size_t.sizeof); + bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc + { + if (rhs._data != this._data) + return false; + static foreach (d; 0 .. N) + if (gridScopeView!d != rhs.gridScopeView!d) + return false; + return true; + } /++ +/ - this(GridVectors grid) @safe @nogc + this(Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) data) @safe @nogc { - size_t length = 1; - size_t[N] shape; - enum msg = "constant interpolant: minimal allowed length for the grid equals 1."; + enum msg_min = "constant interpolant: minimal allowed length for the grid equals 1."; + enum msg_eq = "constant interpolant: X and Y values length should be equal."; version(D_Exceptions) - static immutable exc = new Exception(msg); + { + static immutable exc_min = new Exception(msg_min); + static immutable exc_eq = new Exception(msg_eq); + } foreach(i, ref x; grid) { if (x.length < 1) { - version(D_Exceptions) - throw exc; - else - assert(0, msg); + version(D_Exceptions) { import mir.exception : toMutable; throw exc_min.toMutable; } + else assert(0, msg_min); + } + if (x.length != data._lengths[i]) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exc_eq.toMutable; } + else assert(0, msg_eq); } - length *= shape[i] = x.length; + _grid[i] = x._iterator; } - - auto rca = mir_rcarray!F(length); - this._data = rca.asSlice.sliced(shape); - this._grid = staticMap!(iter, grid); + _data = data; } @trusted: /// Constant lightConst()() const @property { return *cast(Constant*)&this; } + /// - Constant lightImmutable()() immutable @property { return *cast(Constant*)&this; } + Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property + if (dimension < N) + { + return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); + } /// - GridVectors[dimension] grid(size_t dimension = 0)() scope return const @property + immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted if (dimension < N) { - return _grid[dimension].sliced(_data._lengths[dimension]); + return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; } /++ @@ -172,31 +161,199 @@ struct Constant(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIter /// enum uint derivativeOrder = 0; + /// + enum uint dimensionCount = N; + /// template opCall(uint derivative = 0) - if (derivative <= derivativeOrder) + // if (derivative <= derivativeOrder) { - @trusted: /++ - `(x)` and `[x]` operators. + `(x)` operator. Complexity: `O(log(grid.length))` +/ - auto opCall(X...)(in X xs) scope const + auto opCall(X...)(in X xs) scope const @trusted if (X.length == N) - // @FUTURE@ - // X.length == N || derivative == 0 && X.length && X.length <= N { - size_t[N] indexes = void; - + size_t[N] indices; foreach(i; Iota!N) { static if (isInterval!(typeof(xs[i]))) - indexes[i] = xs[i][1]; + indices[i] = xs[i][1]; else - indexes[i] = _data._lengths[i] > 1 ? this.findInterval!i(xs[i]) : 0; + indices[i] = _data._lengths[i] > 1 ? this.findInterval!i(xs[i]) : 0; + } + static if (derivative == 0) + { + return _data[indices]; + } + else + { + F[derivative + 1] ret = 0; + ret[0] = _data[indices]; + return ret; } - return _data[indexes]; } } } + +/++ +Single value interpolation ++/ +SingleConstant!F singleConstant(F)(const F value) +{ + return typeof(return)(value); +} + +/// ditto +struct SingleConstant(F, X = F) +{ + /// + enum uint derivativeOrder = 0; + + /// + enum uint dimensionCount = 1; + + /// + F value; + + /// + this(F value) + { + this.value = value; + } + + + /// + immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted + if (dimension == 0) + { + return null; + } + + /// + template opCall(uint derivative = 0) + { + /++ + `(x)` operator. + Complexity: + `O(1)` + +/ + auto opCall(X...)(in X xs) scope const @trusted + { + static if (derivative == 0) + { + return F(value); + } + else + { + F[derivative + 1] ret = 0; + ret[0] = value; + return ret; + } + } + } +} + +/// +@safe pure nothrow +version (mir_test) +unittest +{ + auto sc = singleConstant(34.1); + assert(sc(100) == 34.1); + assert(sc.opCall!2(100) == [34.1, 0, 0]); +} + +/++ +Interpolator used for non-rectiliner trapezoid-like greeds. +Params: + grid = rc-array of interpolation grid + data = rc-array of interpolator-like structures ++/ +auto metaSingleConstant(T)(T data) +{ + import core.lifetime: move; + return MetaSingleConstant!T(data.move); +} + +/// ditto +struct MetaSingleConstant(T, X = double) + // if (T.derivativeOrder >= 1) +{ + import mir.interpolate.utility: DeepType; + + /// + T data; + + /// + this(T data) + { + import core.lifetime: move; + this.data = data.move; + } + + /// + MetaSingleConstant lightConst()() const @property { return *cast(MetaSingleConstant*)&this; } + + /// + immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted + if (dimension == 0) + { + return null; + } + + /// + enum uint derivativeOrder = 1; + + static if (__traits(compiles, {enum N = T.dimensionCount;})) + /// + enum uint dimensionCount = T.dimensionCount + 1; + + /// + template opCall(uint derivative = 0) + { + /++ + `(x)` operator. + Complexity: + `O(log(grid.length))` + +/ + auto opCall(X...)(const X xs) scope const @trusted + if (xs.length >= 1) + { + static if (derivative == 0) + { + return data(xs[1.. $]); + } + else + { + auto iv = data.opCall!derivative(xs[1.. $]); + typeof(iv)[derivative + 1] ret = 0; + ret[0] = iv; + return ret; + } + } + } +} + +/// Ignores the first dimension parameter +version(mir_test) +unittest +{ + import mir.interpolate.linear; + + auto x = [0.0, 1, 2, 3, 5]; + auto y = [4.0, 0, 9, 23, 40]; + + auto g = [7.0, 10, 15]; + + import mir.ndslice.allocation: rcslice; + + auto d = linear!double(x.rcslice!(immutable double), y.rcslice!(const double)); + + auto ignoresFirstDim = d.metaSingleConstant; + + assert(ignoresFirstDim(9.0, 1.8) == d(1.8)); + assert(ignoresFirstDim.opCall!1(9.0, 1.8) == [d.opCall!1(1.8), [0.0, 0.0]]); +} diff --git a/source/mir/interpolate/extrapolate.d b/source/mir/interpolate/extrapolate.d new file mode 100644 index 00000000..d7c6d442 --- /dev/null +++ b/source/mir/interpolate/extrapolate.d @@ -0,0 +1,287 @@ +/++ +$(H2 Extrapolators) + +See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki + +Macros: +SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) ++/ +module mir.interpolate.extrapolate; + +/++ +Constant extrapolator ++/ +ConstantExtrapolator!T constantExtrapolator(T)(T interpolator) +{ + import core.lifetime: move; + return typeof(return)(interpolator.move); +} + + +/// ditto +struct ConstantExtrapolator(T) + if (__traits(hasMember, T, "gridScopeView")) +{ + /// + T data; + + /// + this(T data) + { + import core.lifetime: move; + this.data = data.move; + } + + /// + ConstantExtrapolator lightConst()() const @property { return *cast(ConstantExtrapolator*)&this; } + + /// + auto gridScopeView(size_t dimension = 0)() return scope const @property @trusted + { + return data.gridScopeView!dimension; + } + + /// + enum uint derivativeOrder = 1; + + static if (__traits(compiles, {enum N = T.dimensionCount;})) + /// + enum uint dimensionCount = T.dimensionCount + 1; + + /// + template opCall(uint derivative = 0) + { + /++ + `(x)` operator. + Complexity: + `O(log(grid.length))` + +/ + auto opCall(X...)(const X xs) scope const @trusted + if (X.length >= 1) + { + import mir.internal.utility: Iota; + import mir.math.common: fmin, fmax; + import std.meta: staticMap; + + static if (derivative) + bool[X.length] extrpolated; + + auto mod(size_t i)() + { + static if (__traits(compiles, gridScopeView!i)) + { + auto grid = gridScopeView!i; + static if (derivative) + extrpolated[i] = grid.length != 0 && (xs[i] < grid[0] || grid[$ - 1] < xs[i]); + return grid.length ? xs[i].fmax(grid[0]).fmin(grid[$ - 1]) : xs[i]; + } + else + { + return xs[i]; + } + } + + alias xm = staticMap!(mod, Iota!(X.length)); + + static if (derivative == 0) + return data(xm); + else + { + static assert (X.length <= 4, "multidimensional constant exrapolation with derivatives isn't implemented"); + auto ret = data.opCall!derivative(xm); + + static if (X.length >= 1) + { + if (extrpolated[0]) + foreach (ref a; ret[1 .. $]) + a = 0; + } + + static if (X.length >= 2) + { + if (extrpolated[1]) + foreach (ref a; ret) + foreach (ref b; a[1 .. $]) + b = 0; + } + + static if (X.length >= 3) + { + if (extrpolated[2]) + foreach (ref a; ret) + foreach (ref b; a) + foreach (ref c; b[1 .. $]) + c = 0; + } + + static if (X.length >= 4) + { + if (extrpolated[2]) + foreach (ref a; ret) + foreach (ref b; a) + foreach (ref c; b) + foreach (ref d; c[1 .. $]) + d = 0; + } + + return ret; + } + } + } +} + + +/// +version(mir_test) +unittest +{ + import mir.interpolate.linear; + + auto x = [0.0, 1, 2, 3, 5]; + auto y = [4.0, 0, 9, 23, 40]; + + auto g = [7.0, 10, 15]; + + import mir.ndslice.allocation: rcslice; + + auto linear = linear!double( + x.rcslice!(immutable double), + y.rcslice!(const double), + ).constantExtrapolator; + + assert(linear(2) == 9); + assert(linear(-1) == 4); + assert(linear(100) == 40); + + assert(linear.opCall!1(-1) == [4, 0]); + +} + +/++ +Linear extrapolator. ++/ +LinearExtrapolator!T linearExtrapolator(T)(T interpolator) +{ + import core.lifetime: move; + return typeof(return)(interpolator.move); +} + + +/// ditto +struct LinearExtrapolator(T) + if (__traits(hasMember, T, "gridScopeView")) +{ + /// + T data; + + /// + this(T data) + { + import core.lifetime: move; + this.data = data.move; + } + + /// + LinearExtrapolator lightConst()() const @property { return *cast(LinearExtrapolator*)&this; } + + /// + auto gridScopeView(size_t dimension = 0)() return scope const @property @trusted + { + return data.gridScopeView!dimension; + } + + /// + enum uint derivativeOrder = 1; + + static if (__traits(compiles, {enum N = T.dimensionCount;})) + /// + enum uint dimensionCount = T.dimensionCount + 1; + + /// + template opCall(uint derivative = 0) + { + /++ + `(x)` operator. + Complexity: + `O(log(grid.length))` + +/ + auto opCall(X...)(const X xs) scope const @trusted + if (X.length >= 1) + { + import mir.internal.utility: Iota; + import mir.math.common: fmin, fmax; + import std.meta: staticMap; + + bool[X.length] extrpolated; + + auto mod(size_t i)() + { + static if (__traits(compiles, gridScopeView!i)) + { + auto grid = gridScopeView!i; + extrpolated[i] = grid.length != 0 && (xs[i] < grid[0] || grid[$ - 1] < xs[i]); + return grid.length ? xs[i].fmax(grid[0]).fmin(grid[$ - 1]) : xs[i]; + } + else + { + return xs[i]; + } + } + + alias xm = staticMap!(mod, Iota!(X.length)); + + import mir.utility: max; + + static assert (X.length <= 2, "multidimensional linear exrapolation with derivatives isn't implemented"); + auto ret = data.opCall!(derivative.max(1u))(xm); + + static if (X.length >= 1) + { + if (extrpolated[0]) + { + ret[0] += ret[1] * (xs[0] - xm[0]); + foreach (ref a; ret[2 .. $]) + a = 0; + } + } + + static if (derivative == 0) + return ret[0]; + else + return ret; + } + } +} + + +/// +version(mir_test) +unittest +{ + import mir.test; + import mir.interpolate.linear; + + auto x = [0.0, 1, 2, 3, 5]; + auto y = [4.0, 0, 9, 23, 40]; + + auto g = [7.0, 10, 15]; + + import mir.ndslice.allocation: rcslice; + + auto linear = linear!double( + x.rcslice!(immutable double), + y.rcslice!(const double), + ); + + auto linearLinear = linear.linearExtrapolator; + + linearLinear(2).should == linear(2); + linearLinear(-1).should == linear(-1); + linearLinear.opCall!1(-1).should == [8, -4]; + linearLinear(100).shouldApprox == linear(100); +} diff --git a/source/mir/interpolate/generic.d b/source/mir/interpolate/generic.d new file mode 100644 index 00000000..70125359 --- /dev/null +++ b/source/mir/interpolate/generic.d @@ -0,0 +1,206 @@ +/++ +$(H2 Generic Piecewise Interpolant) + +See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki + +Macros: +SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) ++/ +module mir.interpolate.generic; + +@fmamath: + +/// +version(mir_test) +@safe pure @nogc unittest +{ + import mir.ndslice; + import mir.math.common: approxEqual; + + struct PieceInterpolant + { + int value; + + this()(int value) + { + this.value = value; + } + + int opCall(uint derivative : 0, X)(int x0, int x1, X x) const + { + return value; + } + + enum uint derivativeOrder = 0; + } + + alias S = PieceInterpolant; + static immutable x = [0, 1, 2, 3]; // can be also an array of floating point numbers + static immutable y = [S(10), S(20), S(30)]; + + auto interpolant = generic(x.rcslice, y.rcslice!(const S)); + + assert(interpolant(-1) == 10); + assert(interpolant(0) == 10); + assert(interpolant(0.5) == 10); + + assert(interpolant(1) == 20); + assert(interpolant(1.5) == 20); + + assert(interpolant(2) == 30); + assert(interpolant(3) == 30); + assert(interpolant(3.4) == 30); + assert(interpolant(3) == 30); + assert(interpolant(4) == 30); +} + + +import core.lifetime: move; +import mir.internal.utility; +import mir.functional; +import mir.interpolate; +import mir.math.common: fmamath; +import mir.ndslice.slice; +import mir.primitives; +import mir.rc.array; +import mir.utility: min, max; +import std.meta: AliasSeq, staticMap; +import std.traits; + +/// +public import mir.interpolate: atInterval; + +/++ +Constructs multivariate generic interpolant with nodes on rectilinear grid. + +Params: + grid = `x` values for interpolant + values = `f(x)` values for interpolant + +Constraints: + `grid`, `values` must have the same length >= 1 + +Returns: $(LREF Generic) ++/ +Generic!(X, F) generic(X, F) + (Slice!(RCI!(immutable X)) grid, Slice!(RCI!(const F)) values) +{ + return typeof(return)(forward!grid, values.move); +} + +/++ +Multivariate generic interpolant with nodes on rectilinear grid. ++/ +struct Generic(X, F) +{ +@fmamath: + + /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) + Slice!(RCI!(const F)) _data; + /// Grid iterators. $(RED For internal use.) + RCI!(immutable X) _grid; + + bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc + { + if (rhs._data != this._data) + return false; + static foreach (d; 0 .. 1) + if (gridScopeView!d != rhs.gridScopeView!d) + return false; + return true; + } + +extern(D): + + /++ + +/ + this(Slice!(RCI!(immutable X)) grid, Slice!(RCI!(const F)) data) @safe @nogc + { + import core.lifetime: move; + enum msg_min = "generic interpolant: minimal allowed length for the grid equals 2."; + enum msg_eq = "generic interpolant: X length and Y values length + 1 should be equal."; + version(D_Exceptions) + { + static immutable exc_min = new Exception(msg_min); + static immutable exc_eq = new Exception(msg_eq); + } + if (grid.length < 2) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exc_min.toMutable; } + else assert(0, msg_min); + } + if (grid.length != data._lengths[0] + 1) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exc_eq.toMutable; } + else assert(0, msg_eq); + } + _grid = move(grid._iterator); + _data = move(data); + } + +@trusted: + + /// + Generic lightConst()() const @property { return *cast(Generic*)&this; } + + /// + Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property + if (dimension == 0) + { + return _grid.lightConst.sliced(_data._lengths[0]); + } + + /// + immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted + if (dimension == 0) + { + return _grid._iterator[0 .. _data._lengths[0]]; + } + + /++ + Returns: intervals count. + +/ + size_t intervalCount(size_t dimension = 0)() scope const @property + if (dimension == 0) + { + assert(_data._lengths[0] > 1); + return _data._lengths[0] - 0; + } + + /// + size_t[1] gridShape()() scope const @property + { + return _data.shape; + } + + /// + enum uint derivativeOrder = F.derivativeOrder; + + /// + template opCall(uint derivative = 0) + if (derivative == 0) + { + /++ + `(x)` operator. + Complexity: + `O(log(grid.length))` + +/ + auto opCall(X)(in X x) const + { + return opCall!(X)(Tuple!(size_t, X)(this.findInterval(x), x)); + } + + /// + auto opCall(X)(Tuple!(size_t, X) tuple) const + { + X x = tuple[1]; + size_t idx = tuple[0]; + return _data[idx].opCall!derivative(_grid[idx], _grid[idx + 1], x); + } + } +} diff --git a/source/mir/interpolate/linear.d b/source/mir/interpolate/linear.d index 452bc6a4..f7c68a93 100644 --- a/source/mir/interpolate/linear.d +++ b/source/mir/interpolate/linear.d @@ -3,9 +3,9 @@ $(H2 Linear Interpolation) See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2017, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) @@ -13,113 +13,111 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.interpolate.linear; -import std.traits; -import mir.primitives; -import mir.ndslice.slice; -import mir.math.common: optmath; +import core.lifetime: move; +import mir.functional; import mir.internal.utility; +import mir.interpolate; +import mir.math.common: fmamath; +import mir.ndslice.slice; +import mir.primitives; +import mir.rc.array; +import mir.utility: min, max; +import std.meta: AliasSeq, staticMap; +import std.traits; +/// public import mir.interpolate: atInterval; -import mir.interpolate; -@optmath: +enum msg_min = "linear interpolant: minimal allowed length for the grid equals 2."; +enum msg_eq = "linear interpolant: X and Y values length should be equal."; +version(D_Exceptions) +{ + static immutable exc_min = new Exception(msg_min); + static immutable exc_eq = new Exception(msg_eq); +} + +@fmamath: /++ Constructs multivariate linear interpolant with nodes on rectilinear grid. Params: - T = element floating point type - N = arity (dimension) number - grid = N `x` values for interpolation - values = `f(x)` values for interpolation + grid = `x` values for interpolant + values = `f(x)` values for interpolant Constraints: `grid`, `values` must have the same length >= 2 Returns: $(LREF Linear) +/ -template linear(T, size_t N = 1, FirstGridIterator = immutable(T)*, NextGridIterators = Repeat!(N - 1, FirstGridIterator)) - if (mir.internal.utility.isFloatingPoint!T && is(T == Unqual!T) && N <= 6) +Linear!(F, N, X) linear(F, size_t N = 1, X = F) + (Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) values) { - import std.meta: AliasSeq; - - private alias GridIterators = AliasSeq!(FirstGridIterator, NextGridIterators); - private alias GridVectors = Linear!(T, N, GridIterators).GridVectors; - -@optmath: - - /++ - Params: - grid = immutable `x` values for interpolant - values = `f(x)` values for interpolant - Constraints: - `grid` and `values` must have the same length >= 2 - Returns: $(LREF Spline) - +/ - Linear!(T, N, GridIterators) linear(yIterator, SliceKind ykind)( - GridVectors grid, - scope Slice!(yIterator, N, ykind) values, - ) pure @trusted - { - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - auto ret = typeof(return)(grid); - ret._data[] = values; - return ret.move; - } + return typeof(return)(forward!grid, values.move); } - /// R -> R: Linear interpolation version(mir_test) -@safe pure unittest +@safe pure @nogc unittest { import mir.algorithm.iteration; import mir.ndslice; import mir.math.common: approxEqual; - immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; - immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; - auto xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; + static immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; + static immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; + static immutable xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; - auto interpolation = linear!double(x.sliced, y.sliced); + auto interpolant = linear!double(x.rcslice!(immutable double), y.rcslice!(const double)); - auto data = [0.0011, 0.0030, 0.0064, 0.0104, 0.0144, 0.0176, 0.0207, 0.0225, 0.0243, 0.0261, 0.0268, 0.0274, 0.0281, 0.0288, 0.0295, 0.0302, 0.0309, 0.0316, 0.0322, 0.0329, 0.0332, 0.0335, 0.0337, 0.0340, 0.0342, 0.0345, 0.0348, 0.0350, 0.0353, 0.0356]; + static immutable data = [0.0011, 0.0030, 0.0064, 0.0104, 0.0144, 0.0176, 0.0207, 0.0225, 0.0243, 0.0261, 0.0268, 0.0274, 0.0281, 0.0288, 0.0295, 0.0302, 0.0309, 0.0316, 0.0322, 0.0329, 0.0332, 0.0335, 0.0337, 0.0340, 0.0342, 0.0345, 0.0348, 0.0350, 0.0353, 0.0356]; - assert(all!((a, b) => approxEqual(a, b, 1e-4, 1e-4))(xs.sliced.map!interpolation, data)); + assert(xs.sliced.vmap(interpolant).all!((a, b) => approxEqual(a, b, 1e-4, 1e-4))(data)); + + auto d = interpolant.withDerivative(9.0); + auto de = interpolant.opCall!2(9.0); + assert(de[0 .. 2] == d); + assert(de[2] == 0); } /// R^2 -> R: Bilinear interpolation version(mir_test) -@safe pure unittest +@safe pure @nogc unittest { import mir.math.common: approxEqual; import mir.ndslice; alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); - ///// set test function //// - const double y_x0 = 2; - const double y_x1 = -7; - const double y_x0x1 = 3; + //// set test function //// + enum y_x0 = 2; + enum y_x1 = -7; + enum y_x0x1 = 3; // this function should be approximated very well alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; ///// set interpolant //// - auto x0 = [-1.0, 2, 8, 15].idup.sliced; - auto x1 = [-4.0, 2, 5, 10, 13].idup.sliced; - auto grid = cartesian(x0, x1); + static immutable x0 = [-1.0, 2, 8, 15]; + static immutable x1 = [-4.0, 2, 5, 10, 13]; - auto interpolant = linear!(double, 2)(x0, x1, grid.map!f); + auto grid = cartesian(x0, x1) + .map!f + .rcslice + .lightConst; + + auto interpolant = + linear!(double, 2)( + x0.rcslice!(immutable double), + x1.rcslice!(immutable double), + grid + ); ///// compute test data //// - auto test_grid = cartesian(x0 + 1.23, x1 + 3.23); + auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); auto real_data = test_grid.map!f; - ()@trusted{ - auto interp_data = test_grid.vmap(interpolant); - - ///// verify result //// - assert(all!appreq(interp_data, real_data)); - }(); + auto interp_data = test_grid.vmap(interpolant); + ///// verify result //// + assert(all!appreq(interp_data, real_data)); //// check derivatives //// auto z0 = 1.23; @@ -141,33 +139,39 @@ version(mir_test) alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); ///// set test function //// - const y_x0 = 2; - const y_x1 = -7; - const y_x2 = 5; - const y_x0x1 = 10; - const y_x0x1x2 = 3; + enum y_x0 = 2; + enum y_x1 = -7; + enum y_x2 = 5; + enum y_x0x1 = 10; + enum y_x0x1x2 = 3; // this function should be approximated very well - alias f = (x0, x1, x2) => y_x0 * x0 + y_x1 * x1 + y_x2 * x2 - + y_x0x1 * x0 * x1 + y_x0x1x2 * x0 * x1 * x2 - 11; + static auto f(double x0, double x1, double x2) + { + return y_x0 * x0 + y_x1 * x1 + y_x2 * x2 + y_x0x1 * x0 * x1 + y_x0x1x2 * x0 * x1 * x2 - 11; + } ///// set interpolant //// - auto x0 = [-1.0, 2, 8, 15].idup.sliced; - auto x1 = [-4.0, 2, 5, 10, 13].idup.sliced; - auto x2 = [3, 3.7, 5].idup.sliced; - auto grid = cartesian(x0, x1, x2); - - auto interpolant = linear!(double, 3)(x0, x1, x2, grid.map!f); + static immutable x0 = [-1.0, 2, 8, 15]; + static immutable x1 = [-4.0, 2, 5, 10, 13]; + static immutable x2 = [3, 3.7, 5]; + auto grid = cartesian(x0, x1, x2) + .map!f + .as!(const double) + .rcslice; + + auto interpolant = linear!(double, 3)( + x0.rcslice!(immutable double), + x1.rcslice!(immutable double), + x2.rcslice!(immutable double), + grid); ///// compute test data //// - auto test_grid = cartesian(x0 + 1.23, x1 + 3.23, x2 - 3); + auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23, x2.sliced - 3); auto real_data = test_grid.map!f; - ()@trusted{ - auto interp_data = test_grid.vmap(interpolant); - - ///// verify result //// - assert(all!appreq(interp_data, real_data)); - }(); + auto interp_data = test_grid.vmap(interpolant); + ///// verify result //// + assert(all!appreq(interp_data, real_data)); //// check derivatives //// auto z0 = 1.23; @@ -185,61 +189,64 @@ version(mir_test) /++ Multivariate linear interpolant with nodes on rectilinear grid. +/ -struct Linear(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterators...) - if (N && N <= 6 && NextGridIterators.length == N - 1) +struct Linear(F, size_t N = 1, X = F) + if (N && N <= 6) { - import mir.rc.array; - import std.meta: AliasSeq, staticMap; - - package alias GridIterators = AliasSeq!(FirstGridIterator, NextGridIterators); - package alias GridVectors = staticMap!(GridVector, GridIterators); - - /// $(RED For internal use.) - mir_slice!(mir_rci!F, N) _data; + /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) + Slice!(RCI!(const F), N) _data; /// Grid iterators. $(RED For internal use.) - GridIterators _grid; + Repeat!(N, RCI!(immutable X)) _grid; - import mir.utility: min, max; - package enum alignment = min(64u, F.sizeof).max(size_t.sizeof); +@fmamath extern(D): + + bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc + { + if (rhs._data != this._data) + return false; + static foreach (d; 0 .. N) + if (gridScopeView!d != rhs.gridScopeView!d) + return false; + return true; + } /++ +/ - this(GridVectors grid) @safe @nogc + this(Repeat!(N, Slice!(RCI!(immutable X))) grid, Slice!(RCI!(const F), N) data) @safe @nogc { - size_t length = 1; - size_t[N] shape; - enum msg = "linear interpolant: minimal allowed length for the grid equals 2."; - version(D_Exceptions) - static immutable exc = new Exception(msg); foreach(i, ref x; grid) { if (x.length < 2) { - version(D_Exceptions) - throw exc; - else - assert(0, msg); + version(D_Exceptions) { import mir.exception : toMutable; throw exc_min.toMutable; } + else assert(0, msg_min); + } + if (x.length != data._lengths[i]) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exc_eq.toMutable; } + else assert(0, msg_eq); } - length *= shape[i] = x.length; + _grid[i] = x._iterator.move; } - - auto rca = mir_rcarray!F(length); - this._data = rca.asSlice.sliced(shape); - this._grid = staticMap!(iter, grid); + _data = data.move; } @trusted: /// Linear lightConst()() const @property { return *cast(Linear*)&this; } + /// - Linear lightImmutable()() immutable @property { return *cast(Linear*)&this; } + Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property + if (dimension < N) + { + return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); + } /// - GridVectors[dimension] grid(size_t dimension = 0)() scope return const @property + immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted if (dimension < N) { - return _grid[dimension].sliced(_data._lengths[dimension]); + return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; } /++ @@ -260,76 +267,86 @@ struct Linear(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterat /// enum uint derivativeOrder = 1; + /// + enum uint dimensionCount = N; + /// template opCall(uint derivative = 0) - if (derivative <= derivativeOrder) { /++ - `(x)` and `[x]` operators. + `(x)` operator. Complexity: `O(log(grid.length))` +/ - auto opCall(X...)(in X xs) scope const @trusted + auto opCall(X...)(const X xs) scope const @trusted if (X.length == N) - // @FUTURE@ - // X.length == N || derivative == 0 && X.length && X.length <= N { - import mir.functional: AliasCall; - import mir.ndslice.topology: iota; - alias Kernel = AliasCall!(LinearKernel!F, "opCall", derivative); + static if (derivative > derivativeOrder) + { + auto res = this.opCall!derivativeOrder(xs); + typeof(res[0])[derivative + 1] ret = 0; + ret[0 .. derivativeOrder + 1] = res; + return ret; + } + else + { + import mir.functional: AliasCall; + import mir.ndslice.topology: iota; + alias Kernel = AliasCall!(LinearKernel!F, "opCall", derivative); - size_t[N] indexes = void; - Kernel[N] kernels = void; + size_t[N] indices; + Kernel[N] kernels; - enum rp2d = derivative; + enum rp2d = derivative; - foreach(i; Iota!N) - { - static if (isInterval!(typeof(xs[i]))) + foreach(i; Iota!N) { - indexes[i] = xs[i][1]; - auto x = xs[i][0]; - } - else - { - alias x = xs[i]; - indexes[i] = this.findInterval!i(x); + static if (isInterval!(typeof(xs[i]))) + { + indices[i] = xs[i][1]; + auto x = xs[i][0]; + } + else + { + alias x = xs[i]; + indices[i] = this.findInterval!i(x); + } + kernels[i] = LinearKernel!F(_grid[i][indices[i]], _grid[i][indices[i] + 1], x); } - kernels[i] = LinearKernel!F(_grid[i][indexes[i]], _grid[i][indexes[i] + 1], x); - } - align(64) F[2 ^^ N][derivative + 1] local = void; - immutable strides = _data._lengths.iota.strides; + align(64) F[2 ^^ N][derivative + 1] local; + immutable strides = _data._lengths.iota.strides; - void load(sizediff_t i)(F* from, F* to) - { - version(LDC) pragma(inline, true); - static if (i == -1) + void load(sizediff_t i)(F* from, F* to) { - *to = *from; + version(LDC) pragma(inline, true); + static if (i == -1) + { + *to = *from; + } + else + { + from += strides[i] * indices[i]; + load!(i - 1)(from, to); + from += strides[i]; + enum s = 2 ^^ (N - 1 - i); + to += s; + load!(i - 1)(from, to); + } } - else - { - from += strides[i] * indexes[i]; - load!(i - 1)(from, to); - from += strides[i]; - enum s = 2 ^^ (N - 1 - i); - to += s; - load!(i - 1)(from, to); - } - } - load!(N - 1)(cast(F*) _data.ptr, cast(F*)local[0].ptr); + load!(N - 1)(cast(F*) _data.ptr, cast(F*)local[0].ptr); - foreach(i; Iota!N) - { - enum P = 2 ^^ (N - 1 - i); - enum L = 2 ^^ (N - i * (1 - rp2d)) / 2; - vectorize(kernels[i], local[0][0 * L .. 1 * L], local[0][1 * L .. 2 * L], *cast(F[L][2 ^^ rp2d]*)local[rp2d].ptr); - static if (rp2d == 1) - shuffle3!1(local[1][0 .. L], local[1][L .. 2 * L], local[0][0 .. L], local[0][L .. 2 * L]); - static if (i + 1 == N) - return *cast(SplineReturnType!(F, N, 2 ^^ rp2d)*) local[0].ptr; + foreach(i; Iota!N) + { + enum P = 2 ^^ (N - 1 - i); + enum L = 2 ^^ (N - i * (1 - rp2d)) / 2; + vectorize(kernels[i], local[0][0 * L .. 1 * L], local[0][1 * L .. 2 * L], *cast(F[L][2 ^^ rp2d]*)local[rp2d].ptr); + static if (rp2d == 1) + shuffle3!1(local[1][0 .. L], local[1][L .. 2 * L], local[0][0 .. L], local[0][L .. 2 * L]); + static if (i + 1 == N) + return *cast(SplineReturnType!(F, N, 2 ^^ rp2d)*) local[0].ptr; + } } } } @@ -345,6 +362,8 @@ struct LinearKernel(X) X w0 = 0; X w1 = 0; +@fmamath: + /// auto lightConst()() const @property { @@ -369,10 +388,11 @@ struct LinearKernel(X) /// template opCall(uint derivative = 0) - if (derivative <= 1) + // if (derivative <= 1) { /// - auto opCall(Y)(in Y y0, in Y y1) + auto opCall(Y)(const Y y0, const Y y1) + if (__traits(isFloating, Y)) { auto r0 = y0 * w1; auto r1 = y1 * w0; @@ -390,8 +410,210 @@ struct LinearKernel(X) return y; } } + + static if (derivative) + auto opCall(Y, size_t N)(scope ref const Y[N] y0, scope ref const Y[N] y1) + { + Y[N][derivative + 1] ret = void; + Y[derivative + 1][N] temp = void; + + static foreach(i; 0 .. N) + temp[i] = this.opCall!derivative(y0[i], y1[i]); + static foreach(i; 0 .. N) + static foreach(d; 0 .. derivative + 1) + ret[d][i] = temp[i][d]; + return ret; + } } /// alias withDerivative = opCall!1; } + +/++ +Interpolator used for non-rectiliner trapezoid-like greeds. +Params: + grid = rc-array of interpolation grid + data = rc-array of interpolator-like structures ++/ +auto metaLinear(X, T)(RCArray!(immutable X) grid, RCArray!(const T) data) +{ + import core.lifetime: move; + return MetaLinear!(T, X)(grid.move, data.move); +} + +/// ditto +struct MetaLinear(T, X) + // if (T.derivativeOrder >= 1) +{ + import mir.interpolate.utility: DeepType; + // alias ElementInterpolator = Linear!(F, N, X); + + /// + RCArray!(immutable X) grid; + /// + RCArray!(const T) data; + + /// + this(RCArray!(immutable X) grid, RCArray!(const T) data) + { + import core.lifetime: move; + + if (grid.length < 2) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exc_min.toMutable; } + else assert(0, msg_min); + } + if (grid.length != data.length) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exc_eq.toMutable; } + else assert(0, msg_eq); + } + + this.grid = grid.move; + this.data = data.move; + } + + /// + MetaLinear lightConst()() const @property { return *cast(MetaLinear*)&this; } + + /// + immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted + if (dimension == 0) + { + return grid[]; + } + + /++ + Returns: intervals count. + +/ + size_t intervalCount(size_t dimension = 0)() scope const @property + if (dimension == 0) + { + assert(data.length > 1); + return data.length - 1; + } + + /// + enum uint derivativeOrder = 1; + + static if (__traits(compiles, {enum N = T.dimensionCount;})) + /// + enum uint dimensionCount = T.dimensionCount + 1; + + /// + template opCall(uint derivative = 0) + { + /++ + `(x)` operator. + Complexity: + `O(log(grid.length))` + +/ + auto opCall(X...)(const X xs) scope const @trusted + // if (X.length == dimensionCount) + { + static if (isInterval!(typeof(xs[0]))) + { + size_t index = xs[0][1]; + auto x = xs[0][0]; + } + else + { + alias x = xs[0]; + size_t index = this.findInterval(x); + } + auto lhs = data[index + 0].opCall!derivative(xs[1 .. $]); + auto rhs = data[index + 1].opCall!derivative(xs[1 .. $]); + alias E = typeof(lhs); + alias F = DeepType!E; + auto kernel = LinearKernel!F(grid[index], grid[index + 1], x); + return kernel.opCall!derivative(lhs, rhs); + } + } + + /// + alias withDerivative = opCall!1; + + /// + alias withTwoDerivatives = opCall!2; +} + +/// 2D trapezoid-like (not rectilinear) linear interpolation +version(mir_test) +unittest +{ + auto x = [ + [0.0, 1, 2, 3, 5], + [-4.0, 3, 4], + [0.0, 10], + ]; + auto y = [ + [4.0, 0, 9, 23, 40], + [9.0, 0, 3], + [4.0, 40], + ]; + + auto g = [7.0, 10, 15]; + + import mir.rc.array: RCArray; + import mir.ndslice.allocation: rcslice; + + auto d = RCArray!(Linear!double)(3); + + foreach (i; 0 .. x.length) + d[i] = linear!double(x[i].rcslice!(immutable double), y[i].rcslice!(const double)); + + auto trapezoidInterpolator = metaLinear(g.rcarray!(immutable double), d.lightConst); + + auto val = trapezoidInterpolator(9.0, 1.8); + auto valWithDerivative = trapezoidInterpolator.withDerivative(9.0, 1.8); +} + +version(mir_test) +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice; + alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); + + //// set test function //// + enum y_x0 = 2; + enum y_x1 = -7; + enum y_x0x1 = 3; + + // this function should be approximated very well + alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; + + ///// set interpolant //// + static immutable x0 = [-1.0, 2, 8, 15]; + static immutable x1 = [-4.0, 2, 5, 10, 13]; + + auto grid = cartesian(x0, x1) + .map!f + .rcslice + .lightConst; + + auto int0 = linear!double(x1.rcslice!(immutable double), grid[0]); + auto int1 = linear!double(x1.rcslice!(immutable double), grid[1]); + auto int2 = linear!double(x1.rcslice!(immutable double), grid[2]); + auto int3 = linear!double(x1.rcslice!(immutable double), grid[3]); + + auto interpolant = metaLinear(x0.rcarray!(immutable double), rcarray(int0, int1, int2, int3).lightConst); + + ///// compute test data //// + auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); + auto real_data = test_grid.map!f; + auto interp_data = test_grid.vmap(interpolant); + ///// verify result //// + assert(all!appreq(interp_data, real_data)); + + //// check derivatives //// + auto z0 = 1.23; + auto z1 = 3.21; + auto d = interpolant.withDerivative(z0, z1); + assert(appreq(interpolant(z0, z1), f(z0, z1))); + assert(appreq(d[0][0], f(z0, z1))); + assert(appreq(d[1][0], y_x0 + y_x0x1 * z1)); + assert(appreq(d[0][1], y_x1 + y_x0x1 * z0)); + assert(appreq(d[1][1], y_x0x1)); +} diff --git a/source/mir/interpolate/mod.d b/source/mir/interpolate/mod.d new file mode 100644 index 00000000..093cb9fe --- /dev/null +++ b/source/mir/interpolate/mod.d @@ -0,0 +1,209 @@ +/++ +$(H2 Interpolation Modifier) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2022 Ilia, Symmetry Investments +Authors: Ilia Ki + +Macros: +SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) ++/ +module mir.interpolate.mod; + +import mir.math.common; + +/++ +Applies function to the interpolated value. + +Params: + fun = two arguments `(x, derivativeOrder)` function ++/ +template interpolationMap(alias fun) +{ + /// + auto interpolationMap(T)(T interpolator) + { + import core.lifetime: move; + alias S = InterpolationMap!fun; + return S!T(interpolator.move); + } +} + +/// ditto +template InterpolationMap(alias fun) +{ + /// + struct InterpolationMap(T) + { + static if (__traits(hasMember, T, "derivativeOrder")) + enum derivativeOrder = T.derivativeOrder; + + static if (__traits(hasMember, T, "dimensionCount")) + enum uint dimensionCount = T.dimensionCount; + + /// + T interpolator; + + /// + this(T interpolator) + { + import core.lifetime: move; + this.interpolator = interpolator.move; + } + + /// + template opCall(uint derivative = 0) + // if (derivative <= derivativeOrder) + { + /++ + `(x)` operator. + Complexity: + `O(log(grid.length))` + +/ + auto opCall(X...)(const X xs) scope const @trusted + // if (X.length == dimensionCount) + { + auto g = interpolator.opCall!derivative(xs); + + static if (derivative == 0) + { + typeof(g)[1] ret; + fun(g, ret); + return ret[0]; + } + else + { + static if (X.length == 1) + auto g0 = g[0]; + else + static if (X.length == 2) + auto g0 = g[0][0]; + else + static if (X.length == 3) + auto g0 = g[0][0][0]; + else + static assert(0, "Not implemented"); + + typeof(g0)[derivative + 1] f; + + fun(g0, f); + + static if (X.length == 1) + { + typeof(g) r; + r[0] = f[0]; + r[1] = f[1] * g[1]; + + static if (derivative >= 2) + { + r[2] = f[2] * (g[1] * g[1]) + f[1] * g[2]; + } + static if (derivative >= 3) + { + r[3] = f[3] * (g[1] * g[1] * g[1]) + f[1] * g[3] + 3 * (f[2] * g[1] * g[2]); + } + static if (derivative >= 4) + { + static assert(0, "Not implemented"); + } + + return r; + } else static assert(0, "Not implemented"); + } + } + } + } +} + +/// +version (mir_test) +unittest +{ + import mir.interpolate.spline; + import mir.math.common: log; + import mir.ndslice.allocation: rcslice; + import mir.test; + + alias g = (double x, uint d = 0) => + d == 0 ? 3 * x ^^ 3 + 5 * x ^^ 2 + 0.23 * x + 2 : + d == 1 ? 9 * x ^^ 2 + 10 * x + 0.23 : + double.nan; + + alias f = (double x, ref scope y) + { + y[0] = log(x); + static if (y.length >= 2) + y[1] = 1 / x; + static if (y.length >= 3) + y[2] = -y[1] * y[1]; + static if (y.length >= 4) + y[3] = -2 * y[1] * y[2]; + static if (y.length >= 5) + static assert(0, "Not implemented"); + }; + + auto s = spline!double( + [0.1, 0.4, 0.5, 1.0].rcslice!(immutable double), + [g(0.1), g(0.4), g(0.5), g(1.0)].rcslice!(const double) + ); + + auto m = s.interpolationMap!f; + + m(0.7).shouldApprox == log(g(0.7)); + auto d = m.opCall!3(0.7); + d[0].shouldApprox == log(g(0.7)); + d[1].shouldApprox == 1 / g(0.7) * g(0.7, 1); + d[2].shouldApprox == -0.252301; + d[3].shouldApprox == -4.03705; +} + +private alias implSqrt = (x, ref scope y) +{ + import mir.math.common: sqrt; + y[0] = sqrt(x); + static if (y.length >= 2) + y[1] = 0.5f / y[0]; + static if (y.length >= 3) + y[2] = -0.5f * y[1] / x; + static if (y.length >= 4) + y[3] = -1.5f * y[2] / x; + static if (y.length >= 5) + static assert(0, "Not implemented"); +}; + +/++ +Applies square root function to the interpolated value. ++/ +alias interpolationSqrt = interpolationMap!implSqrt; +/// ditto +alias InterpolationSqrt = InterpolationMap!implSqrt; + +/// +version (mir_test) +unittest +{ + import mir.interpolate.spline; + import mir.math.common: sqrt; + import mir.ndslice.allocation: rcslice; + import mir.test; + + alias g = (double x, uint d = 0) => + d == 0 ? 3 * x ^^ 3 + 5 * x ^^ 2 + 0.23 * x + 2 : + d == 1 ? 9 * x ^^ 2 + 10 * x + 0.23 : + double.nan; + + auto s = spline!double( + [0.1, 0.4, 0.5, 1.0].rcslice!(immutable double), + [g(0.1), g(0.4), g(0.5), g(1.0)].rcslice!(const double) + ); + + auto m = s.interpolationSqrt; + + m(0.7).shouldApprox == sqrt(g(0.7)); + auto d = m.opCall!3(0.7); + d[0].shouldApprox == sqrt(g(0.7)); + d[1].shouldApprox == 0.5 / sqrt(g(0.7)) * g(0.7, 1); + d[2].shouldApprox == 2.2292836438189414; + d[3].shouldApprox == -3.11161; +} diff --git a/source/mir/interpolate/package.d b/source/mir/interpolate/package.d index dbd6ece8..0311a606 100644 --- a/source/mir/interpolate/package.d +++ b/source/mir/interpolate/package.d @@ -4,14 +4,14 @@ $(H1 Interpolation Algorithms) $(BOOKTABLE $(H2 Interpolation modules), $(TR $(TH Module) $(TH Interpolation kind)) $(T2M constant, Constant Interpolant) +$(T2M generic, Generic Piecewise Interpolant) $(T2M linear, Linear Interpolant) -$(T2M pchip, Piecewise Cubic Hermite Interpolating Polynomial) -$(T2M polynomial, Lagrange Barycentric Interpolation) -$(T2M spline, Cubic Spline Interpolant) +$(T2M polynomial, Lagrange Barycentric Interpolant) +$(T2M spline, Piecewise Cubic Hermite Interpolant Spline: C2 (with contiguous second derivative), cardinal, monotone (aka PCHIP), double-quadratic, Akima) ) - -Copyright: Copyright © 2017, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +] +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir,interpolate, $1)$(NBSP) @@ -20,20 +20,20 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.interpolate; -import mir.functional: RefTuple; -import mir.math.common: optmath; +import mir.functional: Tuple; +import mir.math.common: fmamath; import mir.ndslice.slice: Slice, Contiguous; import mir.primitives; import mir.qualifier; import std.traits: isInstanceOf; -@optmath: +@fmamath: package ref iter(alias s)() { return s._iterator; }; package alias GridVector(It) = Slice!It; -package enum bool isInterval(T) = isInstanceOf!(RefTuple, T); -package enum bool isInterval(alias T) = isInstanceOf!(RefTuple, T); +package enum bool isInterval(T) = isInstanceOf!(Tuple, T); +package enum bool isInterval(alias T) = isInstanceOf!(Tuple, T); package template Repeat(ulong n, L...) { @@ -64,12 +64,12 @@ template findInterval(size_t dimension = 0) static if (dimension) { immutable sizediff_t len = interpolant.intervalCount!dimension - 1; - auto grid = interpolant.grid!dimension[1 .. $][0 .. len]; + auto grid = interpolant.gridScopeView!dimension[1 .. $][0 .. len]; } else { immutable sizediff_t len = interpolant.intervalCount - 1; - auto grid = interpolant.grid[][1 .. $][0 .. len]; + auto grid = interpolant.gridScopeView[1 .. $][0 .. len]; } assert(len >= 0); return grid.transitionIndex!"a <= b"(x); @@ -79,12 +79,14 @@ template findInterval(size_t dimension = 0) /// version(mir_test) unittest { + import mir.ndslice.allocation: rcslice; + import mir.ndslice.topology: as; import mir.ndslice.slice: sliced; import mir.interpolate.linear; - auto x = [0.0, 1, 2].idup.sliced; - auto y = [10.0, 2, 4].idup.sliced; - auto interpolation = linear!double(x, y); + static immutable x = [0.0, 1, 2]; + static immutable y = [10.0, 2, 4]; + auto interpolation = linear!double(x.rcslice, y.as!(const double).rcslice); assert(interpolation.findInterval(1.0) == 1); } @@ -93,14 +95,14 @@ Lazy interpolation shell with linear complexity. Params: range = sorted range - interpolant = interpolant structure with `.grid` method. + interpolant = interpolant structure with `.gridScopeView` method. Complexity: - `O(range.length + interpolant.grid.length)` to evaluate all elements. + `O(range.length + interpolant.gridScopeView.length)` to evaluate all elements. Returns: Lazy input range. See_also: $(SUBREF linear, linear), - $(SUBREF pchip, pchip). + $(SUBREF spline, spline). +/ auto interp1(Range, Interpolant)(Range range, Interpolant interpolant, size_t interval = 0) { @@ -118,7 +120,7 @@ struct Interp1(Range, Interpolant) private size_t _interval; /// - auto lightScope() + auto lightScope() scope { return Interp1!(LightScopeOf!Range, LightScopeOf!Interpolant)(.lightScope(_range), .lightScope(_interpolant), _interval); } @@ -144,7 +146,7 @@ struct Interp1(Range, Interpolant) assert(!empty); auto x = _range.front; return (x) @trusted { - auto points = _interpolant.grid; + auto points = _interpolant.gridScopeView; sizediff_t len = _interpolant.intervalCount - 1; assert(len >= 0); while (x > points[_interval + 1] && _interval < len) @@ -162,44 +164,32 @@ version(mir_test) { import mir.math.common: approxEqual; import mir.ndslice.slice: sliced; - import mir.ndslice.allocation: slice; + import mir.ndslice.allocation: rcslice; import mir.interpolate: interp1; - import mir.interpolate.pchip; + import mir.interpolate.spline; - auto x = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22].idup.sliced; - auto y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6].idup.sliced; - auto interpolation = pchip!double(x, y); + static immutable x = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; + static immutable y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; + auto interpolation = spline!double(x.rcslice, y.sliced, SplineType.monotone); - auto xs = slice(x[0 .. $ - 1] + 0.5); + auto xs = x[0 .. $ - 1].sliced + 0.5; auto ys = xs.interp1(interpolation); - - // assert(ys.approxEqual([ - // 5.333333333333334, - // 2.500000000000000, - // 10.000000000000000, - // 4.288971807628524, - // 11.202580845771145, - // 16.250000000000000, - // 17.962962962962962, - // 5.558593750000000, - // 17.604662698412699, - // ])); } -@safe version(mir_test) unittest +@safe pure @nogc version(mir_test) unittest { import mir.interpolate.linear; import mir.ndslice; import mir.math.common: approxEqual; - immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; - immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; - immutable xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; + static immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; + static immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; + static immutable xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; - auto interpolation = linear!double(x.sliced, y.sliced); + auto interpolation = linear!double(x.rcslice, y.as!(const double).rcslice); - auto data = [0.0011, 0.0030, 0.0064, 0.0104, 0.0144, 0.0176, 0.0207, 0.0225, 0.0243, 0.0261, 0.0268, 0.0274, 0.0281, 0.0288, 0.0295, 0.0302, 0.0309, 0.0316, 0.0322, 0.0329, 0.0332, 0.0335, 0.0337, 0.0340, 0.0342, 0.0345, 0.0348, 0.0350, 0.0353, 0.0356]; + static immutable data = [0.0011, 0.0030, 0.0064, 0.0104, 0.0144, 0.0176, 0.0207, 0.0225, 0.0243, 0.0261, 0.0268, 0.0274, 0.0281, 0.0288, 0.0295, 0.0302, 0.0309, 0.0316, 0.0322, 0.0329, 0.0332, 0.0335, 0.0337, 0.0340, 0.0342, 0.0345, 0.0348, 0.0350, 0.0353, 0.0356]; () @trusted { assert(all!((a, b) => approxEqual(a, b, 1e-4, 1e-4))(xs.interp1(interpolation), data)); @@ -211,10 +201,10 @@ Optimization utility that can be used with interpolants if x should be extrapolated at interval given. By default interpolants uses binary search to find appropriate interval, -it has `O(log(.grid.length))` complexity. +it has `O(log(.gridScopeView.length))` complexity. If an interval is given, interpolant uses it instead of binary search. +/ -RefTuple!(T, size_t) atInterval(T)(in T value, size_t intervalIndex) +Tuple!(T, size_t) atInterval(T)(in T value, size_t intervalIndex) { return typeof(return)(value, intervalIndex); } @@ -222,9 +212,12 @@ RefTuple!(T, size_t) atInterval(T)(in T value, size_t intervalIndex) /// version(mir_test) unittest { + import mir.ndslice.allocation; import mir.ndslice.slice; import mir.interpolate.spline; - auto interpolant = spline!double([0.0, 1, 2].idup.sliced, [3, 4, -10].idup.sliced); + static immutable x = [0.0, 1, 2]; + static immutable y = [3.0, 4, -10]; + auto interpolant = spline!double(x.rcslice, y.sliced); assert(interpolant(1.3) != interpolant(1.3.atInterval(0))); assert(interpolant(1.3) == interpolant(1.3.atInterval(1))); } @@ -314,7 +307,7 @@ void shuffle3(size_t P, F, size_t N)(ref F[N] a, ref F[N] b, ref F[N] c, ref F[N { enum masks = generateShuffles3!(N, P); import std.meta: aliasSeqOf; - import ldc.simd: shufflevector; + import mir.internal.ldc_simd: shufflevector; alias V = __vector(F[N]); // @FUTURE@ vector support auto as = shufflevector!(V, aliasSeqOf!(masks[0]))(*cast(V*)a.ptr, *cast(V*)b.ptr); auto bs = shufflevector!(V, aliasSeqOf!(masks[1]))(*cast(V*)a.ptr, *cast(V*)b.ptr); @@ -354,7 +347,7 @@ void shuffle2(size_t P, F, size_t N)(ref F[N] a, ref F[N] b, ref F[N] c, ref F[N { enum masks = generateShuffles2!(N, P); import std.meta: aliasSeqOf; - import ldc.simd: shufflevector; + import mir.internal.ldc_simd: shufflevector; alias V = __vector(F[N]); // @FUTURE@ vector support auto as = shufflevector!(V, aliasSeqOf!(masks[0]))(*cast(V*)a.ptr, *cast(V*)b.ptr); auto bs = shufflevector!(V, aliasSeqOf!(masks[1]))(*cast(V*)a.ptr, *cast(V*)b.ptr); @@ -395,7 +388,7 @@ void shuffle1(size_t P, F, size_t N)(ref F[N] a, ref F[N] b, ref F[N] c, ref F[N { enum masks = generateShuffles1!(N, P); import std.meta: aliasSeqOf; - import ldc.simd: shufflevector; + import mir.internal.ldc_simd: shufflevector; alias V = __vector(F[N]); // @FUTURE@ vector support auto as = shufflevector!(V, aliasSeqOf!(masks[0]))(*cast(V*)a.ptr, *cast(V*)b.ptr); auto bs = shufflevector!(V, aliasSeqOf!(masks[1]))(*cast(V*)a.ptr, *cast(V*)b.ptr); @@ -585,7 +578,7 @@ auto vectorize(Kernel, F, size_t N, size_t R)(ref Kernel kernel, ref F[N] a, ref // } // else // { - F[N][R] _c = void;//Temporary array in case "c" overlaps "a" and/or "b". + F[N][R] _c;//Temporary array in case "c" overlaps "a" and/or "b". foreach(i; Iota!N) { auto r = kernel(a[i], b[i]); diff --git a/source/mir/interpolate/pchip.d b/source/mir/interpolate/pchip.d deleted file mode 100644 index 6b708447..00000000 --- a/source/mir/interpolate/pchip.d +++ /dev/null @@ -1,209 +0,0 @@ -/++ -$(H2 Piecewise Cubic Hermite Interpolating Polynomial) - -See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) - -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2017, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko - -Macros: -SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) -T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) -+/ -module mir.interpolate.pchip; - -public import mir.interpolate.spline: Spline; - -import std.traits; -private alias AliasSeq(T...) = T; -import mir.ndslice.slice; -import mir.primitives; -import mir.math.common: fmamath; -import mir.ndslice.internal: ConstIfPointer; - -@fmamath: - -/++ -Constructs piecewise cubic hermite interpolating polynomial with nodes on rectilinear grid. -+/ -template pchip(T, size_t N = 1, FirstGridIterator = immutable(T)*, NextGridIterators = Repeat!(N - 1, FirstGridIterator)) - if (isFloatingPoint!T && is(T == Unqual!T) && N <= 6) -{ - static assert (N == 1, "multivariate PCHIP is not implemented."); - - private alias GridIterators = AliasSeq!(FirstGridIterator, NextGridIterators); - private alias GridVectors = Spline!(T, N, GridIterators).GridVectors; - - /++ - Unbounded piecewise spline hermite interpolating polynomial. - Params: - grid = `x` values for interpolant - values = `f(x)` values for interpolant - Constraints: - `grid` and `values` must have the same length >= 3 - Returns: $(SUBREF spline, Spline) - +/ - @fmamath Spline!(T, N, GridIterators) pchip(yIterator, SliceKind ykind)( - GridVectors grid, - scope Slice!(yIterator, N, ykind) values) @safe - { - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - auto ret = Spline!T(grid); - ret._values = values; - pchipSlopes(grid, values, typeof(return).pickDataSubslice(ret._data, 1)); - return ret.move; - } -} - -/// -version(mir_test) -@safe unittest -{ - import mir.math.common: approxEqual; - import mir.algorithm.iteration: all; - import mir.ndslice.allocation: slice; - import mir.ndslice.slice: sliced; - import mir.ndslice.topology: vmap; - - auto x = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22].idup.sliced; - auto y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6].idup.sliced; - auto interpolant = pchip!double(x, y); - - auto xs = x[0 .. $ - 1] + 0.5; - - () @trusted { - auto ys = xs.vmap(interpolant); - - assert(ys.all!approxEqual([ - 5.333333333333334, - 2.500000000000000, - 10.000000000000000, - 4.288971807628524, - 11.202580845771145, - 16.250000000000000, - 17.962962962962962, - 5.558593750000000, - 17.604662698412699, - ])); - }(); -} - -// Check direction equality -version(mir_test) -@safe unittest -{ - import mir.math.common: approxEqual; - import mir.ndslice.slice: sliced; - import mir.ndslice.allocation: slice; - import mir.ndslice.topology: retro, map; - - auto points = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22].idup.sliced; - auto values = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6].idup.sliced; - - auto results = [ - 5.333333333333334, - 2.500000000000000, - 10.000000000000000, - 4.288971807628524, - 11.202580845771145, - 16.250000000000000, - 17.962962962962962, - 5.558593750000000, - 17.604662698412699, - ]; - auto interpolant = pchip!double(points, values); - - auto pointsR = slice(-points.retro); - auto valuesR = values.retro.slice; - auto interpolantR = pchip!double(pointsR, valuesR); - - version(X86_64) - assert(map!interpolant(points[0 .. $ - 1] + 0.5) == map!interpolantR(pointsR.retro[0 .. $ - 1] - 0.5)); -} - - -/++ -Computes slopes for piecewise spline hermite interpolating polynomial. -Params: - points = `x` values for interpolant - values = `f(x)` values for interpolant - slopes = uninitialized ndslice to write slopes into -Constraints: - `points`, `values`, and `slopes` must have the same length >= 3 -+/ -void pchipSlopes(IG, IV, IS, SliceKind gkind, SliceKind vkind, SliceKind skind)( - scope Slice!(IG, 1, gkind) points, - scope Slice!(IV, 1, vkind) values, - scope Slice!(IS, 1, skind) slopes) @trusted -{ - if (points.length < 3) - assert(0); - if (points.length != values.length) - assert(0); - if (points.length != slopes.length) - assert(0); - - auto step0 = cast()(points[1] - points[0]); - auto step1 = cast()(points[2] - points[1]); - auto diff0 = cast()(values[1] - values[0]); - auto diff1 = cast()(values[2] - values[1]); - diff0 /= step0; - diff1 /= step1; - - slopes.front = pchipTail(step0, step1, diff0, diff1); - for(size_t i = 1;;) - { - import mir.math.common: copysign; - if (diff0 && diff1 && copysign(1f, diff0) == copysign(1f, diff1)) - { - auto w0 = step1 * 2 + step0; - auto w1 = step0 * 2 + step1; - slopes[i] = (w0 + w1) / (w0 / diff0 + w1 / diff1); - } - else - { - slopes[i] = 0; - } - if (++i == slopes.length - 1) - { - break; - } - step0 = step1; - diff0 = diff1; - step1 = points[i + 1] - points[i + 0]; - diff1 = values[i + 1] - values[i + 0]; - diff1 /= step1; - } - slopes.back = pchipTail(step1, step0, diff1, diff0); -} - -auto pchipTail(T)(in T step0, in T step1, in T diff0, in T diff1) -{ - import mir.math.common: copysign, fabs; - if (!diff0) - { - return 0; - } - auto slope = ((step0 * 2 + step1) * diff0 - step0 * diff1) / (step0 + step1); - if (copysign(1f, slope) != copysign(1f, diff0)) - { - return 0; - } - if ((copysign(1f, diff0) != copysign(1f, diff1)) && (fabs(slope) > fabs(diff0 * 3))) - { - return diff0 * 3; - } - return slope; -} - -template Repeat(ulong n, L...) -{ - static if (n) - { - import std.meta: Repeat; - alias Repeat = std.meta.Repeat!(n, L); - } - else - alias Repeat = L[0 .. 0]; -} diff --git a/source/mir/interpolate/polynomial.d b/source/mir/interpolate/polynomial.d index f2f8716e..1f4c2d96 100644 --- a/source/mir/interpolate/polynomial.d +++ b/source/mir/interpolate/polynomial.d @@ -3,9 +3,9 @@ $(H2 Lagrange Barycentric Interpolation) See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2019, Symmetry Investments & Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) @@ -13,8 +13,10 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.interpolate.polynomial; +/// public import mir.interpolate: atInterval; -import mir.functional: RefTuple; +import core.lifetime: move; +import mir.functional: Tuple; import mir.internal.utility : isFloatingPoint, Iota; import mir.interpolate: findInterval; import mir.math.common; @@ -26,12 +28,14 @@ import mir.rc.array; import mir.utility: min, swap; import std.traits: Unqual; -@optmath: +@fmamath: /// version(mir_test) -@safe pure unittest +// @safe pure +unittest { + import mir.test; import mir.algorithm.iteration: all; import mir.math; import mir.ndslice; @@ -59,7 +63,7 @@ version(mir_test) auto l_ldx = l.withDerivative(testX); auto l_ld_ld2x = l.withTwoDerivatives(testX); - assert(lx.approxEqual(f(testX))); + lx.shouldApprox == f(testX); assert(l_ldx[].all!approxEqual(fWithDerivative(testX)[])); assert(l_ld_ld2x[].all!approxEqual(fWithTwoDerivatives(testX)[])); } @@ -68,28 +72,29 @@ version(mir_test) /++ Constructs barycentric lagrange interpolant. +/ -Lagrange!(T, maxDerivative) lagrange(uint maxDerivative = 0, T, X)(scope const T[] x, scope const X[] y) +Lagrange!(T, maxDerivative) lagrange(uint maxDerivative = 0, T, X)(scope const X[] x, scope const T[] y) @trusted if (maxDerivative < 16) { - return x.sliced.lagrange!maxDerivative(y.sliced); + import mir.ndslice.allocation: rcslice; + return x.rcslice!(immutable X).lagrange!maxDerivative(y.sliced); } /// ditto -Lagrange!(Unqual!(Slice!(YIterator, 1, ykind).DeepElement), maxDerivative) - lagrange(uint maxDerivative = 0, XIterator, SliceKind xkind, YIterator, SliceKind ykind)(scope Slice!(XIterator, 1, xkind) x, scope Slice!(YIterator, 1, ykind) y) @trusted +Lagrange!(Unqual!(Slice!(Iterator, 1, kind).DeepElement), maxDerivative, X) + lagrange(uint maxDerivative = 0, X, Iterator, SliceKind kind)(Slice!(RCI!(immutable X)) x, Slice!(Iterator, 1, kind) y) @trusted if (maxDerivative < 16) { - alias T = Unqual!(Slice!(YIterator, 1, ykind).DeepElement); - return Lagrange!(T, maxDerivative)(rcarray!(immutable T)(x), rcarray!T(y)); + alias T = Unqual!(Slice!(Iterator, 1, kind).DeepElement); + return Lagrange!(T, maxDerivative)(x.move, rcarray!T(y)); } /++ +/ -struct Lagrange(T, uint maxAdditionalFunctions = 0) +struct Lagrange(T, uint maxAdditionalFunctions = 0, X = T) if (isFloatingPoint!T && maxAdditionalFunctions < 16) { /// $(RED for internal use only.) - RCArray!(immutable T) _grid; + Slice!(RCI!(immutable X)) _grid; /// $(RED for internal use only.) RCArray!(immutable T) _inversedBarycentricWeights; /// $(RED for internal use only.) @@ -97,13 +102,18 @@ struct Lagrange(T, uint maxAdditionalFunctions = 0) /// $(RED for internal use only.) T[maxAdditionalFunctions + 1] _asums; -@optmath @safe pure @nogc: +@fmamath @safe pure @nogc extern(D): + + /// + enum uint derivativeOrder = maxAdditionalFunctions; + /// + enum uint dimensionCount = 1; /++ Complexity: `O(N)` +/ pragma(inline, false) - this(RCArray!(immutable T) grid, RCArray!T values, RCArray!(immutable T) inversedBarycentricWeights) + this(Slice!(RCI!(immutable X)) grid, RCArray!T values, RCArray!(immutable T) inversedBarycentricWeights) { import mir.algorithm.iteration: all; assert(grid.length > 1); @@ -114,22 +124,21 @@ struct Lagrange(T, uint maxAdditionalFunctions = 0) { enum T md = T.min_normal / T.epsilon; static immutable exc = new Exception(msg); - if (!grid[].diff.all!(a => md <= a && a <= T.max)) - throw exc; + if (!grid.lightScope.diff.all!(a => md <= a && a <= T.max)) + { import mir.exception : toMutable; throw exc.toMutable; } } swap(_grid, grid); swap(_inversedBarycentricWeights, inversedBarycentricWeights); swap(_normalizedValues[0], values); - auto x = _grid[].sliced; auto w = _inversedBarycentricWeights[].sliced; foreach (_; Iota!maxAdditionalFunctions) { auto y = _normalizedValues[_][].sliced; static if (_ == 0) _asums[_] = y.asumNorm; - _normalizedValues[_ + 1] = RCArray!T(x.length, true, false); + _normalizedValues[_ + 1] = RCArray!T(_grid.length, true, false); auto d = _normalizedValues[_ + 1][].sliced; - polynomialDerivativeValues(d, x, y, w); + polynomialDerivativeValues(d, _grid.lightScope, y, w); _asums[_ + 1] = d.asumNorm * _asums[_]; } } @@ -137,10 +146,10 @@ struct Lagrange(T, uint maxAdditionalFunctions = 0) /++ Complexity: `O(N^^2)` +/ - this(RCArray!(immutable T) grid, RCArray!T values) + this(Slice!(RCI!(immutable X)) grid, RCArray!T values) { - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - auto weights = grid[].sliced.inversedBarycentricWeights; + import core.lifetime: move; + auto weights = grid.lightScope.inversedBarycentricWeights; this(grid.move, values.move, weights.move); } @@ -154,7 +163,9 @@ scope const: @property { /// - ref const(RCArray!(immutable T)) grid() { return _grid; } + ref const(Slice!(RCI!(immutable X))) grid() { return _grid; } + /// + immutable(X)[] gridScopeView() return scope const @property @trusted { return _grid.lightScope.field; } /// ref const(RCArray!(immutable T)) inversedBarycentricWeights() { return _inversedBarycentricWeights; } /// @@ -176,24 +187,23 @@ scope const: pragma(inline, false) auto opCall(T x) { - return opCall!derivative(RefTuple!(size_t, T)(this.findInterval(x), x)); + return opCall!derivative(Tuple!(size_t, T)(this.findInterval(x), x)); } /// pragma(inline, false) - auto opCall(RefTuple!(size_t, T) tuple) + auto opCall(Tuple!(size_t, T) tuple) { const x = tuple[1]; const idx = tuple[0]; - const grid = _grid[].sliced; const inversedBarycentricWeights = _inversedBarycentricWeights[].sliced; - scope Slice!(const(T)*)[derivative + 1] values; + Slice!(const(T)*)[derivative + 1] values; foreach (i; Iota!(derivative + 1)) values[i] = _normalizedValues[i][].sliced; const T[2] pd = [ - T(x - grid[idx + 0]).fabs, - T(x - grid[idx + 1]).fabs, + T(x - _grid[idx + 0]).fabs, + T(x - _grid[idx + 1]).fabs, ]; ptrdiff_t fastIndex = pd[0] < T.min_normal ? idx + 0 : @@ -215,9 +225,9 @@ scope const: } T d = 0; T[derivative + 1] n = 0; - foreach (i; 0 .. grid.length) + foreach (i; 0 .. _grid.length) { - T w = 1 / (inversedBarycentricWeights[i] * (x - grid[i])); + T w = 1 / (inversedBarycentricWeights[i] * (x - _grid[i])); d += w; foreach (_; Iota!(derivative + 1)) n[_] += w * values[_][i]; @@ -252,17 +262,18 @@ scope const: +/ pragma(inline, false) @nogc -RCArray!(immutable T) inversedBarycentricWeights(T)(scope Slice!(const(T)*) x) +RCArray!(immutable T) inversedBarycentricWeights(T)(Slice!(const(T)*) x) if (isFloatingPoint!T) { + const n = x.length; - scope prodsa = RCArray!(Prod!T)(n); + scope prodsa = RCArray!(ProdAccumulator!T)(n); scope p = prodsa[].sliced; foreach (triplet; n.iota.triplets) with(triplet) { foreach (l; left) { - auto e = wipProd(x[center] - x[l]); + auto e = prod!T(x[center] - x[l]); p[l] *= -e; p[center] *= e; } @@ -271,7 +282,7 @@ RCArray!(immutable T) inversedBarycentricWeights(T)(scope Slice!(const(T)*) x) const minExp = long.max.reduce!min(p.member!"exp"); foreach (ref e; p) e = e.ldexp(1 - minExp); - auto ret = rcarray!(immutable T)(p.member!"value"); + auto ret = rcarray!(immutable T)(p.member!"prod"); return ret; } diff --git a/source/mir/interpolate/spline.d b/source/mir/interpolate/spline.d index 88622485..9a1d0963 100644 --- a/source/mir/interpolate/spline.d +++ b/source/mir/interpolate/spline.d @@ -1,11 +1,13 @@ /++ $(H2 Cubic Spline Interpolation) -See_also: $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) +The module provides common C2 splines, monotone (PCHIP) splines, Akima splines and others. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2017, Kaleidic Associates Advisory Limited and Ilya Yaroshenko -Authors: Ilya Yaroshenko +See_also: $(LREF SplineType), $(REF_ALTTEXT $(TT interp1), interp1, mir, interpolate) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, interpolate, $1)$(NBSP) @@ -13,81 +15,146 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.interpolate.spline; -public import mir.interpolate: atInterval; +import core.lifetime: move; +import mir.functional; +import mir.internal.utility; import mir.interpolate; -import mir.interpolate: Repeat; +import mir.math.common; +import mir.ndslice.slice; +import mir.primitives; +import mir.rc.array; +import mir.utility: min, max; +import std.meta: AliasSeq, staticMap; +import std.traits: Unqual; -// Rectilinear grid - default -// Regular grid - linspace -// Cartesian grid with uniblocks - iota +/// +public import mir.interpolate: atInterval; -import std.traits; -import std.meta; -import mir.primitives; -import mir.functional; -import mir.internal.utility: Iota; -import mir.math.common: fmamath; -import mir.ndslice.internal; -import mir.ndslice.slice; -import mir.ndslice.traits; +static immutable msg_min = "spline interpolant: minimal allowed length for the grid equals 2."; +static immutable msg_eq = "spline interpolant: X and Y values length should be equal."; +static immutable splineConfigurationMsg = "spline configuration: .boundary method requires equal left and right boundaries"; + +version(D_Exceptions) +{ + static immutable exc_min = new Exception(msg_min); + static immutable exc_eq = new Exception(msg_eq); + static immutable splineConfigurationException = new Exception(splineConfigurationMsg); +} + +private template ValueType(T, X) +{ + static if (__traits(compiles, {enum N = T.dimensionCount;})) + alias ValueType = typeof(T.init.opCall(Repeat!(T.dimensionCount, X.init))); + else + alias ValueType = typeof(T.init.opCall(X.init)); +} @fmamath: /// -@trusted pure version(mir_test) unittest +@safe pure @nogc version(mir_test) unittest { import mir.algorithm.iteration: all; import mir.math.common: approxEqual; import mir.ndslice.slice: sliced; + import mir.ndslice.allocation: rcslice; import mir.ndslice.topology: vmap; - auto x = [-1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22].idup.sliced; - auto y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6].idup.sliced; + static immutable xdata = [-1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; + auto x = xdata.rcslice; + static immutable ydata = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; + auto y = ydata.sliced; - auto interpolant = spline!double(x, y); // constructs Spline + auto interpolant = spline!double(x, y, SplineConfiguration!double()); // constructs Spline auto xs = x + 0.5; // input X values for cubic spline - /// not-a-knot (default) - assert(xs.vmap(interpolant).all!approxEqual([ + static immutable test_data0 = [ -0.68361541, 7.28568719, 10.490694 , 0.36192032, 11.91572713, 16.44546433, 17.66699525, 4.52730869, - 19.22825394, -2.3242592 ])); + 19.22825394, -2.3242592 ]; + /// not-a-knot (default) + assert(xs.vmap(interpolant).all!approxEqual(test_data0)); - /// natural cubic spline - interpolant = spline!double(x, y, SplineBoundaryType.secondDerivative); - assert(xs.vmap(interpolant).all!approxEqual([ + static immutable test_data1 = [ 10.85298372, 5.26255911, 10.71443229, 0.1824536 , 11.94324989, 16.45633939, 17.59185094, 4.86340188, - 17.8565408 , 2.81856494])); + 17.8565408 , 2.81856494]; + /// natural cubic spline + interpolant = spline!double(x, y, SplineBoundaryType.secondDerivative); + assert(xs.vmap(interpolant).all!approxEqual(test_data1)); - /// set both boundary derivatives to 3 - interpolant = spline!double(x, y, SplineBoundaryType.firstDerivative, 3); - assert(xs.vmap(interpolant).all!approxEqual([ + static immutable test_data2 = [ + 9.94191781, 5.4223652 , 10.69666392, 0.1971149 , 11.93868415, + 16.46378847, 17.56521661, 4.97656997, 17.39645585, 4.54316446]; + /// set both boundary second derivatives to 3 + interpolant = spline!double(x, y, SplineBoundaryType.secondDerivative, 3); + assert(xs.vmap(interpolant).all!approxEqual(test_data2)); + + static immutable test_data3 = [ 16.45728263, 4.27981687, 10.82295092, 0.09610695, 11.95252862, 16.47583126, 17.49964521, 5.26561539, - 16.21803478, 8.96104974])); + 16.21803478, 8.96104974]; + /// set both boundary derivatives to 3 + interpolant = spline!double(x, y, SplineBoundaryType.firstDerivative, 3); + assert(xs.vmap(interpolant).all!approxEqual(test_data3)); + static immutable test_data4 = [ + 16.45730084, 4.27966112, 10.82337171, 0.09403945, + 11.96265209, 16.44067375, 17.6374694 , 4.67438921, + 18.6234452 , -0.05582876]; /// different boundary conditions interpolant = spline!double(x, y, SplineBoundaryCondition!double(SplineBoundaryType.firstDerivative, 3), SplineBoundaryCondition!double(SplineBoundaryType.secondDerivative, -5)); - assert(xs.vmap(interpolant).all!approxEqual([ - 16.45730084, 4.27966112, 10.82337171, 0.09403945, - 11.96265209, 16.44067375, 17.6374694 , 4.67438921, - 18.6234452 , -0.05582876])); + assert(xs.vmap(interpolant).all!approxEqual(test_data4)); + + + static immutable test_data5 = [ + 12.37135558, 4.99638066, 10.74362441, 0.16008641, + 11.94073593, 16.47908148, 17.49841853, 5.26600921, + 16.21796051, 8.96102894]; // ditto interpolant = spline!double(x, y, SplineBoundaryCondition!double(SplineBoundaryType.secondDerivative, -5), SplineBoundaryCondition!double(SplineBoundaryType.firstDerivative, 3)); - assert(xs.vmap(interpolant).all!approxEqual([ - 12.37135558, 4.99638066, 10.74362441, 0.16008641, - 11.94073593, 16.47908148, 17.49841853, 5.26600921, - 16.21796051, 8.96102894])); + assert(xs.vmap(interpolant).all!approxEqual(test_data5)); + + static immutable test_data6 = [ + 11.40871379, 2.64278898, 9.55774317, 4.84791141, 11.24842121, + 16.16794267, 18.58060557, 5.2531411 , 17.45509005, 1.86992521]; + /// Akima spline + interpolant = spline!double(x, y, SplineType.akima); + assert(xs.vmap(interpolant).all!approxEqual(test_data6)); + + static immutable test_data7 = [ + 12.363463972190182, 3.066421497605127, 9.848385331143952, 4.429544487015751, 11.244211106655975, + 16.255750063889600, 18.140374440374440, 5.291585909796099, 17.722477222271888, 3.181558328118484, + ]; + /// Modified Akima spline + interpolant = spline!double(x, y, SplineType.makima); + assert(xs.vmap(interpolant).all!approxEqual(test_data7)); + + /// Double Quadratic spline + interpolant = spline!double(x, y, SplineType.doubleQuadratic); + import mir.interpolate.utility: ParabolaKernel; + auto kernel1 = ParabolaKernel!double(x[2], x[3], x[4], y[2], y[3], y[4]); + auto kernel2 = ParabolaKernel!double( x[3], x[4], x[5], y[3], y[4], y[5]); + // weighted sum of quadratic functions + auto c = 0.35; // from [0 .. 1] + auto xp = c * x[3] + (1 - c) * x[4]; + auto yp = c * kernel1(xp) + (1 - c) * kernel2(xp); + assert(interpolant(xp).approxEqual(yp)); + // check parabolic extrapolation of the boundary intervals + kernel1 = ParabolaKernel!double(x[0], x[1], x[2], y[0], y[1], y[2]); + kernel2 = ParabolaKernel!double(x[$ - 3], x[$ - 2], x[$ - 1], y[$ - 3], y[$ - 2], y[$ - 1]); + assert(interpolant(x[0] - 23.421).approxEqual(kernel1(x[0] - 23.421))); + assert(interpolant(x[$ - 1] + 23.421).approxEqual(kernel2(x[$ - 1] + 23.421))); } /// @safe pure version(mir_test) unittest { + import mir.rc.array: rcarray; import mir.algorithm.iteration: all; import mir.functional: aliasCall; import mir.math.common: approxEqual; @@ -95,8 +162,8 @@ import mir.ndslice.traits; import mir.ndslice.slice: sliced; import mir.ndslice.topology: vmap, map; - auto x = [-1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22].idup.sliced; - auto y = [ + auto x = rcarray!(immutable double)(-1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22).asSlice; + auto y = rcarray( 8.77842512, 7.96429686, 7.77074363, @@ -106,90 +173,88 @@ import mir.ndslice.traits; 1.48167283, 2.8267636 , 0.40200172, - 7.78980608].sliced; - + 7.78980608).asSlice; + auto interpolant = x.spline!double(y); // default boundary condition is 'not-a-knot' auto xs = x + 0.5; - ()@trusted{ - - auto ys = xs.vmap(interpolant); + auto ys = xs.vmap(interpolant); - auto r = - [ 5.56971848, - 9.30342403, - 4.44139761, + auto r = + [5.56971848, + 9.30342403, + 4.44139761, -0.74740285, - 3.00994108, - 1.50750417, - 1.73144979, - 2.64860361, - 0.64413911, + 3.00994108, + 1.50750417, + 1.73144979, + 2.64860361, + 0.64413911, 10.81768928]; - assert(all!approxEqual(ys, r)); - - // first derivative - auto d1 = xs.vmap(interpolant.aliasCall!"withDerivative").map!"a[1]"; - auto r1 = - [-4.51501279, - 2.15715986, - -7.28363308, - -2.14050449, - 0.03693092, - -0.49618999, - 0.58109933, - -0.52926703, - 0.7819035 , - 6.70632693]; - assert(all!approxEqual(d1, r1)); - - // second derivative - auto d2 = xs.vmap(interpolant.aliasCall!"withTwoDerivatives").map!"a[2]"; - auto r2 = - [ 7.07104751, - -2.62293241, - -0.01468508, - 5.70609505, - -2.02358911, - 0.72142061, - 0.25275483, - -0.6133589 , - 1.26894416, - 2.68067146]; - assert(all!approxEqual(d2, r2)); - - // third derivative (6 * a) - auto d3 = xs.vmap(interpolant.aliasCall!("opCall", 3)).map!"a[3]"; - auto r3 = - [-3.23132664, - -3.23132664, - 14.91047457, - -3.46891432, - 1.88520325, - -0.16559031, - -0.44056064, - 0.47057577, - 0.47057577, - 0.47057577]; - assert(all!approxEqual(d3, r3)); - }(); + assert(all!approxEqual(ys, r)); + + // first derivative + auto d1 = xs.vmap(interpolant.aliasCall!"withDerivative").map!"a[1]"; + auto r1 = + [-4.51501279, + 2.15715986, + -7.28363308, + -2.14050449, + 0.03693092, + -0.49618999, + 0.58109933, + -0.52926703, + 0.7819035 , + 6.70632693]; + assert(all!approxEqual(d1, r1)); + + // second derivative + auto d2 = xs.vmap(interpolant.aliasCall!"withTwoDerivatives").map!"a[2]"; + auto r2 = + [7.07104751, + -2.62293241, + -0.01468508, + 5.70609505, + -2.02358911, + 0.72142061, + 0.25275483, + -0.6133589 , + 1.26894416, + 2.68067146]; + assert(all!approxEqual(d2, r2)); + + // third derivative (6 * a) + auto d3 = xs.vmap(interpolant.aliasCall!("opCall", 3)).map!"a[3]"; + auto r3 = + [-3.23132664, + -3.23132664, + 14.91047457, + -3.46891432, + 1.88520325, + -0.16559031, + -0.44056064, + 0.47057577, + 0.47057577, + 0.47057577]; + assert(all!approxEqual(d3, r3)); } /// R -> R: Cubic interpolation version(mir_test) @safe unittest { + import mir.test; import mir.algorithm.iteration: all; import mir.math.common: approxEqual; import mir.ndslice; - immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; - auto y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; + static immutable x = [0, 1, 2, 3, 5.00274, 7.00274, 10.0055, 20.0137, 30.0192]; + static immutable y = [0.0011, 0.0011, 0.0030, 0.0064, 0.0144, 0.0207, 0.0261, 0.0329, 0.0356,]; auto xs = [1, 2, 3, 4.00274, 5.00274, 6.00274, 7.00274, 8.00548, 9.00548, 10.0055, 11.0055, 12.0082, 13.0082, 14.0082, 15.0082, 16.011, 17.011, 18.011, 19.011, 20.0137, 21.0137, 22.0137, 23.0137, 24.0164, 25.0164, 26.0164, 27.0164, 28.0192, 29.0192, 30.0192]; - auto interpolation = spline!double(x.sliced, y.sliced); + auto interpolation = spline!double(x.rcslice, y.sliced); auto data = [ 0.0011 , 0.003 , 0.0064 , 0.01042622, 0.0144 , @@ -199,9 +264,7 @@ version(mir_test) 0.03314357, 0.03335896, 0.03355892, 0.03375674, 0.03396413, 0.03419436, 0.03446018, 0.03477529, 0.03515072, 0.0356 ]; - ()@trusted{ - assert(all!approxEqual(xs.sliced.vmap(interpolation), data)); - }(); + assert(all!approxEqual(xs.sliced.vmap(interpolation), data)); } /// R^2 -> R: Bicubic interpolation @@ -221,14 +284,14 @@ unittest alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; ///// set interpolant //// - auto x0 = [-1.0, 2, 8, 15].idup.sliced; - auto x1 = [-4.0, 2, 5, 10, 13].idup.sliced; + static immutable x0 = [-1.0, 2, 8, 15]; + static immutable x1 = [-4.0, 2, 5, 10, 13]; auto grid = cartesian(x0, x1); - auto interpolant = spline!(double, 2)(x0, x1, grid.map!f); + auto interpolant = spline!(double, 2)(x0.rcslice, x1.rcslice, grid.map!f); ///// compute test data //// - auto test_grid = cartesian(x0 + 1.23, x1 + 3.23); + auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); // auto test_grid = cartesian(x0 + 0, x1 + 0); auto real_data = test_grid.map!f; auto interp_data = test_grid.vmap(interpolant); @@ -267,26 +330,27 @@ unittest alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); ///// set test function //// - const y_x0 = 2; - const y_x1 = -7; - const y_x2 = 5; - const y_x0x1 = 10; - const y_x0x1x2 = 3; + enum y_x0 = 2; + enum y_x1 = -7; + enum y_x2 = 5; + enum y_x0x1 = 10; + enum y_x0x1x2 = 3; // this function should be approximated very well alias f = (x0, x1, x2) => y_x0 * x0 + y_x1 * x1 + y_x2 * x2 + y_x0x1 * x0 * x1 + y_x0x1x2 * x0 * x1 * x2 - 11; ///// set interpolant //// - auto x0 = [-1.0, 2, 8, 15].idup.sliced; - auto x1 = [-4.0, 2, 5, 10, 13].idup.sliced; - auto x2 = [3, 3.7, 5].idup.sliced; + static immutable x0 = [-1.0, 2, 8, 15]; + static immutable x1 = [-4.0, 2, 5, 10, 13]; + static immutable x2 = [3, 3.7, 5]; auto grid = cartesian(x0, x1, x2); - auto interpolant = spline!(double, 3)(x0, x1, x2, grid.map!f); + auto interpolant = spline!(double, 3)(x0.rcslice, x1.rcslice, x2.rcslice, grid.map!f); + assert(interpolant.convexity == [SplineConvexity.none, SplineConvexity.none, SplineConvexity.convex]); ///// compute test data //// - auto test_grid = cartesian(x0 + 1.23, x1 + 3.23, x2 - 3); + auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23, x2.sliced - 3); auto real_data = test_grid.map!f; auto interp_data = test_grid.vmap(interpolant); @@ -326,16 +390,168 @@ unittest // assert(appreq(d[1][1][1], y_x0x1x2)); } + +/// Monotone PCHIP +version(mir_test) +@safe unittest +{ + import mir.test; + import mir.math.common: approxEqual; + import mir.algorithm.iteration: all; + import mir.ndslice.allocation: rcslice; + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: vmap; + + static immutable x = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; + static immutable y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; + auto interpolant = spline!double(x.rcslice, y.sliced, SplineType.monotone); + interpolant.argmin.should == 2; + interpolant.withDerivative(2)[1].should == 0; + interpolant.argmax.should == 12; + interpolant.withDerivative(12)[1].should == 0; + + auto xs = x[0 .. $ - 1].sliced + 0.5; + + auto ys = xs.vmap(interpolant); + + assert(ys.all!approxEqual([ + 5.333333333333334, + 2.500000000000000, + 10.000000000000000, + 4.288971807628524, + 11.202580845771145, + 16.250000000000000, + 17.962962962962962, + 5.558593750000000, + 17.604662698412699, + ])); +} + +// Check direction equality +version(mir_test) +@safe unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + import mir.ndslice.allocation: rcslice; + import mir.ndslice.topology: retro, vmap; + + static immutable points = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; + static immutable values = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; + + auto results = [ + 5.333333333333334, + 2.500000000000000, + 10.000000000000000, + 4.288971807628524, + 11.202580845771145, + 16.250000000000000, + 17.962962962962962, + 5.558593750000000, + 17.604662698412699, + ]; + auto interpolant = spline!double(points.rcslice, values.sliced, SplineType.monotone); + + auto pointsR = rcslice(-points.retro); + auto valuesR = values.retro.rcslice; + auto interpolantR = spline!double(pointsR, valuesR, SplineType.monotone); + + version(X86_64) + assert(vmap(points[0 .. $ - 1].sliced + 0.5, interpolant) == vmap(pointsR.retro[0 .. $ - 1] - 0.5, interpolantR)); +} + +/// argmin, +, - tests +version(mir_test) +unittest +{ + import mir.test; + import mir.ndslice.slice: sliced; + import mir.ndslice.allocation: rcslice; + + static immutable points = [1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; + static immutable values = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; + auto interpolant = spline!double(points.rcslice, values.sliced); + + auto argmin = 5.898317667706634; + interpolant.argmin.shouldApprox == argmin; + interpolant(argmin).shouldApprox == -0.8684781710737299; + interpolant.opCall!2(argmin)[1].shouldApprox == 0; + + auto argmax = 19.8816211020945; + interpolant.argmax.shouldApprox == argmax; + interpolant.withDerivative(argmax)[1].shouldApprox == 0; + interpolant(argmax).shouldApprox == 19.54377309088673; + + auto zeroInterpolant = interpolant - interpolant; + zeroInterpolant.opCall!3(13.3).should == [0.0, 0.0, 0.0, 0.0]; + + static immutable pointsR = [1.0, 3, 4,]; + static immutable valuesR = [13.0, 12, 10]; + auto interpolantR = spline!double(pointsR.rcslice, valuesR.sliced); + + auto sumInterpolant = interpolant + interpolantR; + + sumInterpolant(2.3).shouldApprox == interpolant(2.3) + interpolantR(2.3); + sumInterpolant(3.3).shouldApprox == interpolant(3.3) + interpolantR(3.3); +} + +/++ +Cubic Spline types. + +The first derivatives are guaranteed to be continuous for all cubic splines. ++/ +extern(C++, "mir", "interpolate") +enum SplineType +{ + /++ + Spline with contiguous second derivative. + +/ + c2, + /++ + $(HTTPS en.wikipedia.org/wiki/Cubic_Hermite_spline#Cardinal_spline, Cardinal) and Catmull–Rom splines. + +/ + cardinal, + /++ + The interpolant preserves monotonicity in the interpolation data and does not overshoot if the data is not smooth. + It is also known as $(HTTPS docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.interpolate.PchipInterpolator.html, PCHIP) + in numpy and Matlab. + +/ + monotone, + /++ + Weighted sum of two nearbor quadratic functions. + It is used in $(HTTPS s3-eu-west-1.amazonaws.com/og-public-downloads/smile-interpolation-extrapolation.pdf, financial analysis). + +/ + doubleQuadratic, + /++ + $(HTTPS en.wikipedia.org/wiki/Akima_spline, Akima spline). + +/ + akima, + /++ + $(HTTPS https://www.mathworks.com/help/matlab/ref/makima.html, Modified Akima spline). + +/ + makima, +} + +/++ +Spline convexity type ++/ +enum SplineConvexity +{ + /// Neither convex nor concave spline + none = 0, + /// Concave spline + concave = -1, + /// Convex spline + convex = 1, +} + /++ Constructs multivariate cubic spline in symmetrical form with nodes on rectilinear grid. Result has continues second derivatives throughout the curve / nd-surface. +/ -template spline(T, size_t N = 1, FirstGridIterator = immutable(T)*, NextGridIterators = Repeat!(N - 1, FirstGridIterator)) +template spline(T, size_t N = 1, X = T) if (isFloatingPoint!T && is(T == Unqual!T) && N <= 6) { - private alias GridIterators = AliasSeq!(FirstGridIterator, NextGridIterators); - private alias GridVectors = Spline!(T, N, GridIterators).GridVectors; - /++ Params: grid = immutable `x` values for interpolant @@ -346,14 +562,28 @@ template spline(T, size_t N = 1, FirstGridIterator = immutable(T)*, NextGridIter `grid` and `values` must have the same length >= 3 Returns: $(LREF Spline) +/ - Spline!(T, N, GridIterators) spline(yIterator, SliceKind ykind)( - GridVectors grid, - scope Slice!(yIterator, N, ykind) values, + Spline!(T, N, X) spline(yIterator, SliceKind ykind)( + Repeat!(N, Slice!(RCI!(immutable X))) grid, + Slice!(yIterator, N, ykind) values, + SplineBoundaryType typeOfBoundaries = SplineBoundaryType.notAKnot, + in T valueOfBoundaryConditions = 0, + ) + { + import core.lifetime: forward; + return spline(forward!grid, forward!values, SplineType.c2, 0, typeOfBoundaries, valueOfBoundaryConditions); + } + + Spline!(T, N, X) spline(yIterator, SliceKind ykind)( + Repeat!(N, Slice!(RCI!(immutable X))) grid, + Slice!(yIterator, N, ykind) values, + SplineType kind, + in T param = 0, SplineBoundaryType typeOfBoundaries = SplineBoundaryType.notAKnot, in T valueOfBoundaryConditions = 0, ) { - return spline(grid, values, SplineBoundaryCondition!T(typeOfBoundaries, valueOfBoundaryConditions)); + import core.lifetime: forward; + return spline(forward!grid, forward!values, SplineBoundaryCondition!T(typeOfBoundaries, valueOfBoundaryConditions), kind, param); } /++ @@ -361,61 +591,119 @@ template spline(T, size_t N = 1, FirstGridIterator = immutable(T)*, NextGridIter grid = immutable `x` values for interpolant values = `f(x)` values for interpolant boundaries = $(LREF SplineBoundaryCondition) for both tails. + kind = $(LREF SplineType) type of cubic spline. + param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). + Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. Constraints: `grid` and `values` must have the same length >= 3 Returns: $(LREF Spline) +/ - Spline!(T, N, GridIterators) spline(yIterator, SliceKind ykind)( - GridVectors grid, - scope Slice!(yIterator, N, ykind) values, + Spline!(T, N, X) spline(yIterator, SliceKind ykind)( + Repeat!(N, Slice!(RCI!(immutable X))) grid, + Slice!(yIterator, N, ykind) values, SplineBoundaryCondition!T boundaries, + SplineType kind = SplineType.c2, + in T param = 0, ) { - return spline(grid, values, boundaries, boundaries); + import core.lifetime: forward; + return spline(forward!grid, forward!values, boundaries, boundaries, kind, param); } /++ Params: grid = immutable `x` values for interpolant values = `f(x)` values for interpolant - rBoundary = $(LREF SplineBoundaryCondition) for left tail. - lBoundary = $(LREF SplineBoundaryCondition) for right tail. + lBoundary = $(LREF SplineBoundaryCondition) for left tail. + rBoundary = $(LREF SplineBoundaryCondition) for right tail. + kind = $(LREF SplineType) type of cubic spline. + param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). + Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. Constraints: `grid` and `values` must have the same length >= 3 Returns: $(LREF Spline) +/ - Spline!(T, N, GridIterators) spline(yIterator, SliceKind ykind)( - GridVectors grid, - scope Slice!(yIterator, N, ykind) values, - SplineBoundaryCondition!T rBoundary, + Spline!(T, N, X) spline(yIterator, SliceKind ykind)( + Repeat!(N, Slice!(RCI!(immutable X))) grid, + Slice!(yIterator, N, ykind) values, SplineBoundaryCondition!T lBoundary, + SplineBoundaryCondition!T rBoundary, + SplineType kind = SplineType.c2, + in T param = 0, ) { - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - auto ret = typeof(return)(grid); + auto ret = typeof(return)(forward!grid); ret._values = values; - ret._computeDerivatives(rBoundary, lBoundary); - return ret.move; + ret._computeDerivatives(kind, param, lBoundary, rBoundary); + return ret; + } + + /++ + Params: + grid = immutable `x` values for interpolant + values = `f(x)` values for interpolant + configuration = $(LREF SplineConfiguration) + Constraints: + `grid` and `values` must have the same length >= 3 + Returns: $(LREF Spline) + +/ + Spline!(T, N, X) spline(yIterator, SliceKind ykind)( + Repeat!(N, Slice!(RCI!(immutable X))) grid, + Slice!(yIterator, N, ykind) values, + SplineConfiguration!T configuration, + ) + { + auto ret = typeof(return)(forward!grid); + ret._values = values; + with(configuration) + ret._computeDerivatives(kind, param, leftBoundary, rightBoundary); + return ret; } } /++ -Cubic Spline Boundary Condition Type +Cubic Spline Boundary Condition Type. -See_also: $(LREF SplineBoundaryCondition) +See_also: $(LREF SplineBoundaryCondition) $(LREF SplineType) +/ +extern(C++, "mir", "interpolate") enum SplineBoundaryType { - /// not implemented - periodic = -1, - /// (default) + /++ + Not-a-knot (or cubic) boundary condition. + It is an aggresive boundary condition that is used only for C2 splines and is default for all API calls. + For other then C2 splines, `notAKnot` is changed internally to + a default boundary type for used $(LREF SplineType). + +/ notAKnot, - /// set the first derivative + /++ + Set the first derivative. + +/ firstDerivative, - /// set the second derivative + /++ + Set the second derivative. + +/ secondDerivative, - /// + /++ + Default for Cardinal and Double-Quadratic splines. + +/ parabolic, + /++ + Default for monotone (aka PHCIP ) splines. + +/ + monotone, + /++ + Default for Akima splines. + +/ + akima, + /++ + Default for Modified Akima splines. + +/ + makima, + /++ + Not implemented. + +/ + periodic = -1, } /++ @@ -423,111 +711,336 @@ Cubic Spline Boundary Condition See_also: $(LREF SplineBoundaryType) +/ +extern(C++, "mir", "interpolate") struct SplineBoundaryCondition(T) + if (__traits(isFloating, T)) { + import mir.serde: serdeOptional, serdeIgnoreDefault; + /// type (default is $(LREF SplineBoundaryType.notAKnot)) - SplineBoundaryType type = SplineBoundaryType.notAKnot; + SplineBoundaryType type; + +@serdeOptional @serdeIgnoreDefault: + /// value (default is 0) T value = 0; } -// private auto iter(alias s) = s.iterator; +/// Spline configuration +struct SplineConfiguration(T) + if (__traits(isFloating, T)) +{ + import mir.serde: serdeOptional, serdeIgnoreDefault, serdeIgnoreOutIfAggregate, serdeIgnore; + + /// + @serdeOptional @serdeIgnoreDefault + SplineType kind; + /// + @serdeOptional @serdeIgnoreOutIfAggregate!"a.symmetric" + SplineBoundaryCondition!T leftBoundary; + /// + @serdeOptional @serdeIgnoreOutIfAggregate!"a.symmetric" + SplineBoundaryCondition!T rightBoundary; + + /++ + Returns: + true of `leftBoundary` equals `rightBoundary`. + +/ + @serdeIgnore + bool symmetric() const @property + { + return leftBoundary == rightBoundary; + } + + /// + @serdeOptional + void boundary(SplineBoundaryCondition!T boundary) @property + { + leftBoundary = rightBoundary = boundary; + } + + /// + @serdeIgnoreOutIfAggregate!"!a.symmetric" + SplineBoundaryCondition!T boundary() const @property + { + assert(!symmetric, splineConfigurationMsg); + return leftBoundary; + } + + /++ + Tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). + Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. + +/ + @serdeOptional @serdeIgnoreDefault + T param = 0; +} + +/// Spline configuration with two boundaries +struct SplineSymmetricConfiguration(T) + if (__traits(isFloating, T)) +{ + import mir.serde: serdeOptional, serdeIgnoreDefault; + +@serdeOptional @serdeIgnoreDefault: + + /// + SplineType type; + /// + SplineBoundaryCondition!T boundary; + /++ + Tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). + Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. + +/ + T param = 0; +} /++ Multivariate cubic spline with nodes on rectilinear grid. +/ -struct Spline(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterators...) - if (N && N <= 6 && NextGridIterators.length == N - 1) +struct Spline(F, size_t N = 1, X = F) + if (N && N <= 6) { import mir.rc.array; - package alias GridIterators = AliasSeq!(FirstGridIterator, NextGridIterators); - package alias GridVectors = staticMap!(GridVector, GridIterators); - -@fmamath: - /// Aligned buffer allocated with `mir.internal.memory`. $(RED For internal use.) - mir_slice!(mir_rci!(F[2 ^^ N]), N) _data; + Slice!(RCI!(F[2 ^^ N]), N) _data; /// Grid iterators. $(RED For internal use.) - GridIterators _grid; + Repeat!(N, RCI!(immutable X)) _grid; + /// + SplineConvexity[N] convexity; - import mir.utility: min, max; - package enum alignment = min(64u, F[2 ^^ N].sizeof).max(size_t.sizeof); + enum uint dimensionCount = N; + +@fmamath extern(D): + + bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc + { + if (rhs._data != this._data) + return false; + static foreach (d; 0 .. N) + if (gridScopeView!d != rhs.gridScopeView!d) + return false; + return true; + } /++ +/ - this(GridVectors grid) @safe @nogc + this(Repeat!(N, Slice!(RCI!(immutable X))) grid) @safe @nogc { size_t length = 1; size_t[N] shape; - enum msg = "spline/pchip interpolant: minimal allowed length for the grid equals 2."; - version(D_Exceptions) - static immutable exc = new Exception(msg); foreach(i, ref x; grid) { if (x.length < 2) { - version(D_Exceptions) - throw exc; - else - assert(0, msg); + version(D_Exceptions) { import mir.exception : toMutable; throw exc_min.toMutable; } + else assert(0, msg_min); } length *= shape[i] = x.length; + this._grid[i] = x._iterator.move; } - - auto rca = mir_rcarray!(F[2 ^^ N])(length); - this._data = rca.asSlice.sliced(shape); - this._grid = staticMap!(iter, grid); + import mir.ndslice.allocation: rcslice; + this._data = shape.rcslice!(F[2 ^^ N]); } - package static auto pickDataSubslice(D)(auto scope ref D data, size_t index) @trusted + package static auto pickDataSubslice(D)(auto ref scope D data, size_t index) @trusted { auto strides = data.strides; foreach (i; Iota!(strides.length)) strides[i] *= DeepElementType!D.length; - return Slice!(F*, strides.length, Universal)(data.shape, strides, data._iterator._iterator.ptr + index); + return Slice!(F*, strides.length, Universal)(data.shape, strides, data._iterator.ptr + index); + } + + static if (N == 1) + /++ + Note: defined only for 1D splines + +/ + Spline opBinary(string op)(const Spline rhs) @trusted const + if (op == "+" || op == "-") + { + import mir.ndslice.allocation: rcslice; + import core.lifetime: move; + import mir.algorithm.setops: unionLength, multiwayUnion; + + auto lgrid = this.gridScopeView; + auto rgrid = rhs.gridScopeView; + scope const(F)[][2] grids = [lgrid, rgrid]; + auto length = grids[].unionLength; + grids = [lgrid, rgrid]; + + size_t j; + auto grid = RCArray!X(length); + auto data = length.rcslice!(F[2]); + auto un = grids[].multiwayUnion; + + while (!un.empty) + { + auto x = un.front; + un.popFront; + auto ly = this.opCall!1(x); + auto ry = rhs.opCall!1(x); + data[j] = mixin(`[ly[0] ` ~ op ~ ` ry[0], ly[1] ` ~ op ~ ` ry[1]]`); + grid[j++] = x; + } + + size_t convexCount; + size_t concaveCount; + foreach_reverse (i; 0 .. length - 1) + { + auto xdiff = grid[i + 1] - grid[i]; + auto ydiff = data[i + 1][0] - data[i][0]; + + auto convex1 = xdiff * (2 * data[i][1] + data[i + 1][1]) <= 3 * ydiff; + auto concave1 = xdiff * (2 * data[i][1] + data[i + 1][1]) >= 3 * ydiff; + auto convex2 = xdiff * (data[i][1] + 2 * data[i + 1][1]) >= 3 * ydiff; + auto concave2 = xdiff * (data[i][1] + 2 * data[i + 1][1]) <= 3 * ydiff; + convexCount += convex1 & convex2; + convexCount += concave1 & concave2; + } + + Spline ret; + ret._data = data.move; + ret._grid[0] = RCI!(immutable X)(cast(RCArray!(immutable X))grid) ; + + ret.convexity = + // convex kind has priority for the linear spline + convexCount == length - 1 ? SplineConvexity.convex : + concaveCount == length - 1 ? SplineConvexity.concave : + SplineConvexity.none; + + return ret; + } + + static if (N == 1) + template argminImpl(string pred) + if (pred == "a < b" || pred == "a > b") + { + F argminImpl()() @trusted const @property + { + import mir.functional: naryFun; + + static if (pred == "a < b") + auto min = F.max; + else + auto min = -F.max; + + F argmin; + + auto grid = gridScopeView; + foreach (i, ref y; _data.lightScope.field) + { + if (naryFun!pred(y[0], min)) + { + min = y[0]; + argmin = grid[i]; + } + } + + foreach (i; 0 .. grid.length - 1) + { + auto x = grid[i + 0]; + + auto y = SplineKernel!F( + grid[i + 0], + grid[i + 1], + x, // any point between + ).opCall!3( + _data[i + 0][0], + _data[i + 1][0], + _data[i + 0][1], + _data[i + 1][1], + ); + + // 3 ax^2 + 2 bx + c = y[1] + // 6 ax + 2 b= y[2] + // 6 a = y[3] + + auto a3 = y[3] * 0.5; + auto b2 = y[2] - y[3] * x; + auto c = y[1] - (b2 + a3 * x) * x; + auto d = b2 * b2 - 4 * a3 * c; + if (d < 0) + continue; + + import mir.math.common: sqrt; + F[2] x12 = [ + (-b2 - sqrt(d)) / (2 * a3), + (-b2 + sqrt(d)) / (2 * a3), + ]; + + foreach (xi; x12) + if (grid[i + 0] < xi && xi < grid[i + 1]) + { + auto yi = SplineKernel!F( + grid[i + 0], + grid[i + 1], + xi, + )( + _data[i + 0][0], + _data[i + 1][0], + _data[i + 0][1], + _data[i + 1][1], + ); + + if (naryFun!pred(yi, min)) + { + min = yi; + argmin = xi; + } + } + } + + return argmin; + } } + static if (N == 1) + /++ + Returns: spline argmin on the interpolation interval + Note: defined only for 1D splines + +/ + alias argmin = argminImpl!"a < b"; + + static if (N == 1) + /++ + Returns: spline argmax on the interpolation interval + Note: defined only for 1D splines + +/ + alias argmax = argminImpl!"a > b"; + /++ Assigns function values to the internal memory. $(RED For internal use.) +/ - void _values(SliceKind kind, Iterator)(scope Slice!(Iterator, N, kind) values) scope @property @trusted + void _values(SliceKind kind, Iterator)(Slice!(Iterator, N, kind) values) scope @property @trusted { assert(values.shape == _data.shape, "'values' should have the same shape as the .gridShape"); - pickDataSubslice(_data, 0)[] = values; + pickDataSubslice(_data.lightScope, 0)[] = values; } /++ Computes derivatives and stores them in `_data`. `_data` is assumed to be preinitialized with function values filled in `F[2 ^^ N][0]`. Params: - lbc = left boundary condition - rbc = right boundary condition + lBoundary = left boundary condition + rBoundary = right boundary condition + temp = temporal buffer length points count (optional) $(RED For internal use.) +/ - void _computeDerivatives()(SplineBoundaryCondition!F lbc, SplineBoundaryCondition!F rbc) scope @trusted nothrow @nogc + void _computeDerivatives(SplineType kind, F param, SplineBoundaryCondition!F lBoundary, SplineBoundaryCondition!F rBoundary) scope @trusted nothrow @nogc { - import mir.internal.memory; import mir.algorithm.iteration: maxLength; auto ml = this._data.maxLength; - auto temp_ptr = cast(F*) alignedAllocate(F[2 ^^ (N - 1)].sizeof * ml, alignment); - if (temp_ptr is null) - assert(0); - debug - { - temp_ptr.sliced(ml)[] = F.init; - } - _computeDerivativesTemp(lbc, rbc, temp_ptr.sliced(ml)); - alignedFree(temp_ptr); + auto temp = RCArray!F(ml); + auto tempSlice = temp[].sliced; + _computeDerivativesTemp(kind, param, lBoundary, rBoundary, tempSlice); } /// ditto pragma(inline, false) - void _computeDerivativesTemp()(SplineBoundaryCondition!F lbc, SplineBoundaryCondition!F rbc, Slice!(F*) temp) scope @system nothrow @nogc + void _computeDerivativesTemp(SplineType kind, F param, SplineBoundaryCondition!F lBoundary, SplineBoundaryCondition!F rBoundary, Slice!(F*) temp) scope @system nothrow @nogc { - import mir.internal.memory; import mir.algorithm.iteration: maxLength, each; import mir.ndslice.topology: map, byDim, evertPack; @@ -535,13 +1048,14 @@ struct Spline(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterat static if (N == 1) { - splineSlopes!(F, F)(_grid.sliced(_data._lengths[0]), pickDataSubslice(_data, 0), pickDataSubslice(_data, 1), temp, lbc, rbc); + convexity[0] = splineSlopes!(F, F)(_grid[0]._iterator.sliced(_data._lengths[0]), pickDataSubslice(_data.lightScope, 0), pickDataSubslice(_data.lightScope, 1), temp[0 .. _data._lengths[0]], kind, param, lBoundary, rBoundary); } else - foreach_reverse(i, ref x; _grid) + foreach_reverse(i; Iota!N) { // if (i == _grid.length - 1) _data + .lightScope .byDim!i .evertPack .each!((d){ @@ -551,7 +1065,16 @@ struct Spline(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterat auto y = pickDataSubslice(d, l); auto s = pickDataSubslice(d, L + l); // debug printf("ptr = %ld, stride = %ld, stride = %ld, d = %ld i = %ld l = %ld\n", d.iterator, d._stride!0, y._stride!0, d.length, i, l); - splineSlopes!(F, F)(x.sliced(_data._lengths[i]), y, s, temp, lbc, rbc); + auto c = splineSlopes!(F, F)(_grid[i]._iterator.sliced(_data._lengths[i]), y, s, temp[0 .. _data._lengths[i]], kind, param, lBoundary, rBoundary); + static if (l) + { + if (convexity[i] != c) + convexity[i] = SplineConvexity.none; + } + else + { + convexity[i] = c; + } // debug{ // (cast(void delegate() @nogc)(){ // writeln("y = ", y); @@ -566,15 +1089,22 @@ struct Spline(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterat @trusted: /// - Spline lightConst()() const @property { return *cast(Spline*)&this; } + Spline lightConst() const @property { return *cast(Spline*)&this; } + /// + Spline lightImmutable() immutable @property { return *cast(Spline*)&this; } + /// - Spline lightImmutable()() immutable @property { return *cast(Spline*)&this; } + Slice!(RCI!(immutable X)) grid(size_t dimension = 0)() return scope const @property + if (dimension < N) + { + return _grid[dimension].lightConst.sliced(_data._lengths[dimension]); + } /// - GridVectors[dimension] grid(size_t dimension = 0)() scope return const @property + immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted if (dimension < N) { - return _grid[dimension].sliced(_data._lengths[dimension]); + return _grid[dimension]._iterator[0 .. _data._lengths[dimension]]; } /++ @@ -587,7 +1117,7 @@ struct Spline(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterat } /// - size_t[N] gridShape()() scope const @property + size_t[N] gridShape() scope const @property { return _data.shape; } @@ -630,7 +1160,7 @@ struct Spline(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterat static if (N > 1 && derivative) pragma(msg, "Warning: multivariate cubic spline with derivatives was not tested!!!"); /++ - `(x)` and `[x]` operators. + `(x)` operator. Complexity: `O(log(points.length))` +/ @@ -643,25 +1173,25 @@ struct Spline(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterat alias Kernel = AliasCall!(SplineKernel!F, "opCall", derivative); enum rp2d = derivative == 3 ? 2 : derivative; - size_t[N] indexes = void; - Kernel[N] kernels = void; + size_t[N] indices; + Kernel[N] kernels; foreach(i; Iota!N) { static if (isInterval!(typeof(xs[i]))) { - indexes[i] = xs[i][1]; + indices[i] = xs[i][1]; auto x = xs[i][0]; } else { alias x = xs[i]; - indexes[i] = this.findInterval!i(x); + indices[i] = this.findInterval!i(x); } - kernels[i] = SplineKernel!F(_grid[i][indexes[i]], _grid[i][indexes[i] + 1], x); + kernels[i] = SplineKernel!F(_grid[i][indices[i]], _grid[i][indices[i] + 1], x); } - align(64) F[2 ^^ N * 2 ^^ N][2] local = void; + align(64) F[2 ^^ N * 2 ^^ N][2] local; void load(sizediff_t i)(const(F[2 ^^ N])* from, F[2 ^^ N]* to) { @@ -674,7 +1204,7 @@ struct Spline(F, size_t N = 1, FirstGridIterator = immutable(F)*, NextGridIterat } else { - from += strides[i] * indexes[i]; + from += strides[i] * indices[i]; load!(i - 1)(from, to); from += strides[i]; enum s = 2 ^^ (N - 1 - i); @@ -765,191 +1295,429 @@ Params: values = `f(x)` values for interpolant slopes = uninitialized ndslice to write slopes into temp = uninitialized temporary ndslice - lbc = left boundary condition - rbc = right boundary condition + kind = $(LREF SplineType) type of cubic spline. + param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). + Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. + lBoundary = left boundary condition + rBoundary = right boundary condition Constraints: `points`, `values`, and `slopes`, must have the same length > 3; `temp` must have length greater or equal to points less minus one. +Returs: + Spline convexity type +/ -void splineSlopes(F, T, IP, IV, IS, SliceKind gkind, SliceKind vkind, SliceKind skind)( - scope Slice!(IP, 1, gkind) points, - scope Slice!(IV, 1, vkind) values, - scope Slice!(IS, 1, skind) slopes, - scope Slice!(T*) temp, - SplineBoundaryCondition!F lbc, - SplineBoundaryCondition!F rbc, +SplineConvexity splineSlopes(F, T, P, IV, IS, SliceKind gkind, SliceKind vkind, SliceKind skind)( + Slice!(P*, 1, gkind) points, + Slice!(IV, 1, vkind) values, + Slice!(IS, 1, skind) slopes, + Slice!(T*) temp, + SplineType kind, + F param, + SplineBoundaryCondition!F lBoundary, + SplineBoundaryCondition!F rBoundary, ) @trusted { - import mir.ndslice.topology: diff, ipack; + import mir.ndslice.topology: diff, zip, slide; assert (points.length >= 2); assert (points.length == values.length); assert (points.length == slopes.length); - assert (temp.length + 1 >= points.length); + assert (temp.length == points.length); auto n = points.length; - auto pd = points.diff; - auto vd = values.diff; + typeof(slopes[0]) first, last; - auto xd = cast() pd.front; - auto yd = cast() vd.front; + auto xd = points.diff; + auto yd = values.diff; auto dd = yd / xd; + auto dd2 = points.zip(values).slide!(3, "(c[1] - a[1]) / (c[0] - a[0])"); - // static if (N == 2) - // { - // if (slopes.length!1 != values.length!1) - // assert(0); - // if (values.empty!1) - // return; - // } - - /// special case - static assert(SplineBoundaryType.notAKnot == 0); - with(SplineBoundaryType) - if (_expect(n == 3 && (rbc.type | lbc.type) == 0, false)) - { - import mir.interpolate.utility; - // static if (N == 1) + with(SplineType) final switch(kind) + { + case c2: + break; + case cardinal: + if (lBoundary.type == SplineBoundaryType.notAKnot) + lBoundary.type = SplineBoundaryType.parabolic; + if (rBoundary.type == SplineBoundaryType.notAKnot) + rBoundary.type = SplineBoundaryType.parabolic; + break; + case monotone: + if (lBoundary.type == SplineBoundaryType.notAKnot) + lBoundary.type = SplineBoundaryType.monotone; + if (rBoundary.type == SplineBoundaryType.notAKnot) + rBoundary.type = SplineBoundaryType.monotone; + break; + case doubleQuadratic: + if (lBoundary.type == SplineBoundaryType.notAKnot) + lBoundary.type = SplineBoundaryType.parabolic; + if (rBoundary.type == SplineBoundaryType.notAKnot) + rBoundary.type = SplineBoundaryType.parabolic; + break; + case akima: + if (lBoundary.type == SplineBoundaryType.notAKnot) + lBoundary.type = SplineBoundaryType.akima; + if (rBoundary.type == SplineBoundaryType.notAKnot) + rBoundary.type = SplineBoundaryType.akima; + break; + case makima: + if (lBoundary.type == SplineBoundaryType.notAKnot) + lBoundary.type = SplineBoundaryType.makima; + if (rBoundary.type == SplineBoundaryType.notAKnot) + rBoundary.type = SplineBoundaryType.makima; + break; + } + + if (n <= 3) + { + if (lBoundary.type == SplineBoundaryType.notAKnot) + lBoundary.type = SplineBoundaryType.parabolic; + if (rBoundary.type == SplineBoundaryType.notAKnot) + rBoundary.type = SplineBoundaryType.parabolic; + + if (n == 2) { - auto parabola = parabolaKernel(points[0], points[1], points[2], values[0], values[1], values[2]); - slopes[0] = parabola.withDerivative(points[0])[1]; - slopes[1] = parabola.withDerivative(points[1])[1]; - slopes[2] = parabola.withDerivative(points[2])[1]; + if (lBoundary.type == SplineBoundaryType.monotone + || lBoundary.type == SplineBoundaryType.makima + || lBoundary.type == SplineBoundaryType.akima) + lBoundary.type = SplineBoundaryType.parabolic; + if (rBoundary.type == SplineBoundaryType.monotone + || rBoundary.type == SplineBoundaryType.makima + || rBoundary.type == SplineBoundaryType.akima) + rBoundary.type = SplineBoundaryType.parabolic; + } + /// special case + if (rBoundary.type == SplineBoundaryType.parabolic + && lBoundary.type == SplineBoundaryType.parabolic) + { + import mir.interpolate.utility; + if (n == 3) + { + auto derivatives = parabolaDerivatives(points[0], points[1], points[2], values[0], values[1], values[2]); + slopes[0] = derivatives[0]; + slopes[1] = derivatives[1]; + slopes[2] = derivatives[2]; + } + else + { + assert(slopes.length == 2); + slopes.back = slopes.front = yd.front / xd.front; + } + return slopes.back >= slopes.front ? SplineConvexity.convex : SplineConvexity.concave; } - // else - // { - // foreach (i; 0 .. values.length!1) - // { - // auto parabolaDerivative = parabolaKernel!1(points[0], points[1], points[2], values[0][i], values[1][i], values[2][i]); - // slopes[0][i] = parabolaDerivative(points[0]); - // slopes[1][i] = parabolaDerivative(points[1]); - // slopes[2][i] = parabolaDerivative(points[2]); - // } - // } - return; } - with(SplineBoundaryType) switch(lbc.type) + with(SplineBoundaryType) final switch(lBoundary.type) { case periodic: - if (n > 2) - { - assert(0); - } - goto lsimple; + assert(0); case notAKnot: - if (n > 2) - { - auto b = pd[1]; - auto c = points.diff!2.front; - auto d = ((xd + 2 * c) * b * dd + xd * xd * (vd[1] / b)) / c; - auto r = b; - temp[0] = c / r; - slopes[0].ndassign = d / r; - break; - } - - lsimple: + auto dx0 = xd[0]; + auto dx1 = xd[1]; + auto dy0 = yd[0]; + auto dy1 = yd[1]; + auto dd0 = dy0 / dx0; + auto dd1 = dy1 / dx1; - temp.front = 0; - slopes.front.ndassign = dd; + slopes.front = dx1; + first = dx0 + dx1; + temp.front = ((dx0 + 2 * first) * dx1 * dd0 + dx0 ^^ 2 * dd1) / first; break; case firstDerivative: - temp.front = 0; - slopes.front.ndassign = lbc.value; + slopes.front = 1; + first = 0; + temp.front = lBoundary.value; break; case secondDerivative: - temp[0] = 0.5f; - slopes[0].ndassign = 1.5f * dd - 0.25f * lbc.value * xd; + slopes.front = 2; + first = 1; + temp.front = 3 * dd.front - 0.5 * lBoundary.value * xd.front; break; case parabolic: - if (n > 2 || rbc.type == SplineBoundaryType.periodic) - { - temp[0] = 1; - slopes[0].ndassign = 2 * dd; - break; - } + slopes.front = 1; + first = 1; + temp.front = 2 * dd.front; + break; + + case monotone: - slopes[0].ndassign = slopes[1].ndassign = dd; - return; + slopes.front = 1; + first = 0; + temp.front = pchipTail(xd[0], xd[1], dd[0], dd[1]); + break; - default: assert(0); - } + case akima: + + slopes.front = 1; + first = 0; + temp.front = akimaTail(dd[0], dd[1]); + break; + + case makima: + + slopes.front = 1; + first = 0; + temp.front = makimaTail(dd[0], dd[1]); + break; - foreach (i; 1 .. n - 1) - { - auto xq = pd[i]; - auto a = xq; - auto b = 2 * (xd + xq); - auto c = xd; - auto r = b - a * temp[i - 1]; - temp[i] = c / r; - auto yq = vd[i]; - auto dq = yq / xq; - auto d = 3 * (dq * xd + dd * xq); - slopes[i].ndassign = (d - a * slopes[i - 1]) / r; - xd = xq; - yd = yq; - dd = dq; } - with(SplineBoundaryType) switch(rbc.type) + with(SplineBoundaryType) final switch(rBoundary.type) { case periodic: - if (n > 2) - { - assert(0); - } - goto rsimple; + assert(0); case notAKnot: - if (n > 2) - { - auto a = points.diff!2[n - 3]; - auto b = pd[n - 3]; - auto r = b - a * temp[n - 2]; - auto d = ((xd + 2 * a) * b * dd + xd * xd * (vd[n - 3] / b)) / a; - slopes[n - 1].ndassign = (d - a * slopes[n - 2]) / r; - break; - } - - rsimple: - slopes[n - 1].ndassign = dd; + auto dx0 = xd[$ - 1]; + auto dx1 = xd[$ - 2]; + auto dy0 = yd[$ - 1]; + auto dy1 = yd[$ - 2]; + auto dd0 = dy0 / dx0; + auto dd1 = dy1 / dx1; + slopes.back = dx1; + last = dx0 + dx1; + temp.back = ((dx0 + 2 * last) * dx1 * dd0 + dx0 ^^ 2 * dd1) / last; break; - + case firstDerivative: - slopes[n - 1].ndassign = rbc.value; + slopes.back = 1; + last = 0; + temp.back = rBoundary.value; break; case secondDerivative: - slopes[n - 1].ndassign = (3 * dd + 0.5f * rbc.value * xd - slopes[n - 2]) / (2 - temp[n - 2]); + slopes.back = 2; + last = 1; + temp.back = 3 * dd.back + 0.5 * rBoundary.value * xd.back; break; case parabolic: - slopes[n - 1].ndassign = (2 * dd - slopes[n - 2]) / (1 - temp[n - 2]); + slopes.back = 1; + last = 1; + temp.back = 2 * dd.back; + break; + + case monotone: + + slopes.back = 1; + last = 0; + temp.back = pchipTail(xd[$ - 1], xd[$ - 2], dd[$ - 1], dd[$ - 2]); + break; + + case akima: + + slopes.back = 1; + last = 0; + temp.back = akimaTail(dd[$ - 1], dd[$ - 2]); + break; + + case makima: + + slopes.back = 1; + last = 0; + temp.back = makimaTail(dd[$ - 1], dd[$ - 2]); break; - default: assert(0); } + if (n > 2) with(SplineType) final switch(kind) + { + case c2: + + foreach (i; 1 .. n - 1) + { + auto dx0 = xd[i - 1]; + auto dx1 = xd[i - 0]; + auto dy0 = yd[i - 1]; + auto dy1 = yd[i - 0]; + slopes[i] = 2 * (dx0 + dx1); + temp[i] = 3 * (dy0 / dx0 * dx1 + dy1 / dx1 * dx0); + } + break; + + case cardinal: + + foreach (i; 1 .. n - 1) + { + slopes[i] = 1; + temp[i] = (1 - param) * dd2[i - 1]; + } + break; + + case monotone: + { + auto step0 = cast()xd[0]; + auto step1 = cast()xd[1]; + auto diff0 = cast()yd[0]; + auto diff1 = cast()yd[1]; + diff0 /= step0; + diff1 /= step1; + + for(size_t i = 1;;) + { + slopes[i] = 1; + if (diff0 && diff1 && copysign(1f, diff0) == copysign(1f, diff1)) + { + auto w0 = step1 * 2 + step0; + auto w1 = step0 * 2 + step1; + temp[i] = (w0 + w1) / (w0 / diff0 + w1 / diff1); + } + else + { + temp[i] = 0; + } + if (++i == n - 1) + { + break; + } + step0 = step1; + diff0 = diff1; + step1 = xd[i]; + diff1 = yd[i]; + diff1 /= step1; + } + } + break; + + case doubleQuadratic: + + foreach (i; 1 .. n - 1) + { + slopes[i] = 1; + temp[i] = dd[i - 1] + dd[i] - dd2[i - 1]; + } + break; + + case akima: + { + auto d3 = dd[1]; + auto d2 = dd[0]; + auto d1 = 2 * d2 - d3; + auto d0 = d1; + foreach (i; 1 .. n - 1) + { + d0 = d1; + d1 = d2; + d2 = d3; + d3 = i == n - 2 ? 2 * d2 - d1 : dd[i + 1]; + slopes[i] = 1; + temp[i] = akimaSlope(d0, d1, d2, d3); + } + break; + } + + case makima: + { + auto d3 = dd[1]; + auto d2 = dd[0]; + auto d1 = 2 * d2 - d3; + auto d0 = d1; + foreach (i; 1 .. n - 1) + { + d0 = d1; + d1 = d2; + d2 = d3; + d3 = i == n - 2 ? 2 * d2 - d1 : dd[i + 1]; + slopes[i] = 1; + temp[i] = makimaSlope(d0, d1, d2, d3); + } + break; + } + } + + foreach (i; 0 .. n - 1) + { + auto c = i == 0 ? first : kind == SplineType.c2 ? xd[i - 1] : 0; + auto a = i == n - 2 ? last : kind == SplineType.c2 ? xd[i + 1] : 0; + auto w = slopes[i] == 1 ? a : a / slopes[i]; + slopes[i + 1] -= w * c; + temp[i + 1] -= w * temp[i]; + } + + slopes.back = temp.back / slopes.back; + + size_t convexCount; + size_t concaveCount; foreach_reverse (i; 0 .. n - 1) { - slopes[i].ndassign !"-"= temp[i] * slopes[i + 1]; + auto c = i == 0 ? first : kind == SplineType.c2 ? xd[i - 1] : 0; + auto v = temp[i] - c * slopes[i + 1]; + slopes[i] = slopes[i] == 1 ? v : v / slopes[i]; + + auto xdiff = points[i + 1] - points[i]; + auto ydiff = values[i + 1] - values[i]; + + auto convex1 = xdiff * (2 * slopes[i] + slopes[i + 1]) <= 3 * ydiff; + auto concave1 = xdiff * (2 * slopes[i] + slopes[i + 1]) >= 3 * ydiff; + auto convex2 = xdiff * (slopes[i] + 2 * slopes[i + 1]) >= 3 * ydiff; + auto concave2 = xdiff * (slopes[i] + 2 * slopes[i + 1]) <= 3 * ydiff; + convexCount += convex1 & convex2; + convexCount += concave1 & concave2; } + + return + // convex kind has priority for the linear spline + convexCount == n - 1 ? SplineConvexity.convex : + concaveCount == n - 1 ? SplineConvexity.concave : + SplineConvexity.none; +} + +private F akimaTail(F)(in F d2, in F d3) +{ + auto d1 = 2 * d2 - d3; + auto d0 = 2 * d1 - d2; + return akimaSlope(d0, d1, d2, d3); +} + +private F akimaSlope(F)(in F d0, in F d1, in F d2, in F d3) +{ + if (d1 == d2) + return d1; + if (d0 == d1 && d2 == d3) + return (d1 + d2) * 0.5f; + if (d0 == d1) + return d1; + if (d2 == d3) + return d2; + auto w0 = fabs(d1 - d0); + auto w1 = fabs(d3 - d2); + auto ws = w0 + w1; + w0 /= ws; + w1 /= ws; + return w0 * d2 + w1 * d1; +} + +private F makimaTail(F)(in F d2, in F d3) +{ + auto d1 = 2 * d2 - d3; + auto d0 = 2 * d1 - d2; + return makimaSlope(d0, d1, d2, d3); +} + +private F makimaSlope(F)(in F d0, in F d1, in F d2, in F d3) +{ + if (d1 == d2) + return d1; + auto w0 = fabs(d1 - d0) + fabs(d1 + d0) * 0.5f; + auto w1 = fabs(d3 - d2) + fabs(d3 + d2) * 0.5f; + auto ws = w0 + w1; + w0 /= ws; + w1 /= ws; + return w0 * d2 + w1 * d1; } /// @@ -960,8 +1728,10 @@ struct SplineKernel(X) X w1 = 0; X wq = 0; +@fmamath: + /// - this()(X x0, X x1, X x) + this(X x0, X x1, X x) { step = x1 - x0; auto c0 = x - x0; @@ -976,7 +1746,7 @@ struct SplineKernel(X) if (derivative <= 3) { /// - auto opCall(Y)(in Y y0, in Y y1, in Y s0, in Y s1) const + auto opCall(Y)(const Y y0, const Y y1, const Y s0, const Y s1) const { auto diff = y1 - y0; auto z0 = s0 * step - diff; @@ -1009,6 +1779,20 @@ struct SplineKernel(X) return y; } } + + static if (derivative) + auto opCall(Y, size_t N)(scope ref const Y[N] y0, scope ref const Y[N] y1, scope ref const Y[N] s0, scope ref const Y[N] s1) + { + Y[N][derivative + 1] ret = void; + Y[derivative + 1][N] temp = void; + + static foreach(i; 0 .. N) + temp[i] = this.opCall!derivative(y0[i], y1[i], s0[i], s1[i]); + static foreach(i; 0 .. N) + static foreach(d; 0 .. derivative + 1) + ret[d][i] = temp[i][d]; + return ret; + } } /// @@ -1016,3 +1800,372 @@ struct SplineKernel(X) /// alias withTwoDerivatives = opCall!2; } + +package T pchipTail(T)(in T step0, in T step1, in T diff0, in T diff1) +{ + import mir.math.common: copysign, fabs; + if (!diff0) + { + return 0; + } + auto slope = ((step0 * 2 + step1) * diff0 - step0 * diff1) / (step0 + step1); + if (copysign(1f, slope) != copysign(1f, diff0)) + { + return 0; + } + if ((copysign(1f, diff0) != copysign(1f, diff1)) && (fabs(slope) > fabs(diff0 * 3))) + { + return diff0 * 3; + } + return slope; +} + +/++ +Spline interpolator used for non-rectiliner trapezoid-like greeds. +Params: + grid = rc-array of interpolation grid + data = rc-array of interpolator-like structures + typeOfBoundaries = $(LREF SplineBoundaryType) for both tails (optional). + valueOfBoundaryConditions = value of the boundary type (optional). +Constraints: + `grid` and `values` must have the same length >= 3 +Returns: $(LREF Spline) ++/ +MetaSpline!(T, X) metaSpline(F, X, T)( + RCArray!(immutable X) grid, + RCArray!(const T) data, + SplineBoundaryType typeOfBoundaries = SplineBoundaryType.notAKnot, + const F valueOfBoundaryConditions = 0, + ) +{ + import core.lifetime: move; + return metaSpline!(F, X, T)(grid.move, data.move, SplineType.c2, 0, typeOfBoundaries, valueOfBoundaryConditions); +} + +/++ +Spline interpolator used for non-rectiliner trapezoid-like greeds. +Params: + grid = rc-array of interpolation grid + data = rc-array of interpolator-like structures + kind = $(LREF SplineType) type of cubic spline. + param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). + Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. + typeOfBoundaries = $(LREF SplineBoundaryType) for both tails (optional). + valueOfBoundaryConditions = value of the boundary type (optional). +Constraints: + `grid` and `values` must have the same length >= 3 +Returns: $(LREF Spline) ++/ +MetaSpline!(T, X) metaSpline(F, X, T)( + RCArray!(immutable X) grid, + RCArray!(const T) data, + SplineType kind, + const F param = 0, + SplineBoundaryType typeOfBoundaries = SplineBoundaryType.notAKnot, + const F valueOfBoundaryConditions = 0, + ) +{ + import core.lifetime: move; + return metaSpline!(F, X, T)(grid.move, data.move, SplineBoundaryCondition!F(typeOfBoundaries, valueOfBoundaryConditions), kind, param); +} + +/++ +Spline interpolator used for non-rectiliner trapezoid-like greeds. +Params: + grid = rc-array of interpolation grid + data = rc-array of interpolator-like structures + boundaries = $(LREF SplineBoundaryCondition) for both tails. + kind = $(LREF SplineType) type of cubic spline. + param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). + Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. +Constraints: + `grid` and `values` must have the same length >= 3 +Returns: $(LREF Spline) ++/ +MetaSpline!(T, X) metaSpline(F, X, T)( + RCArray!(immutable X) grid, + RCArray!(const T) data, + SplineBoundaryCondition!F boundaries, + SplineType kind = SplineType.c2, + const F param = 0, + ) +{ + import core.lifetime: move; + return metaSpline!(F, X, T)(grid.move, data.move, SplineConfiguration!F(kind, boundaries, boundaries, param)); +} + +/++ +Spline interpolator used for non-rectiliner trapezoid-like greeds. +Params: + grid = rc-array of interpolation grid + data = rc-array of interpolator-like structures + lBoundary = $(LREF SplineBoundaryCondition) for left tail. + rBoundary = $(LREF SplineBoundaryCondition) for right tail. + kind = $(LREF SplineType) type of cubic spline. + param = tangent power parameter for cardinal $(LREF SplineType) (ignored by other spline types). + Use `1` for zero derivatives at knots and `0` for Catmull–Rom spline. +Constraints: + `grid` and `values` must have the same length >= 3 +Returns: $(LREF Spline) ++/ +MetaSpline!(T, X) metaSpline(F, X, T)( + RCArray!(immutable X) grid, + RCArray!(const T) data, + SplineBoundaryCondition!F lBoundary, + SplineBoundaryCondition!F rBoundary, + SplineType kind = SplineType.c2, + const F param = 0, + ) +{ + import core.lifetime: move; + return metaSpline(grid.move, data.move, SplineConfiguration!F(kind, lBoundary, rBoundary, param)); +} + +/++ +Spline interpolator used for non-rectiliner trapezoid-like greeds. +Params: + grid = rc-array of interpolation grid + data = rc-array of interpolator-like structures + configuration = $(LREF SplineConfiguration) +Constraints: + `grid` and `values` must have the same length >= 3 +Returns: $(LREF Spline) ++/ +MetaSpline!(T, X) metaSpline(F, X, T)( + RCArray!(immutable X) grid, + RCArray!(const T) data, + SplineConfiguration!F configuration, + ) +{ + import core.lifetime: move; + return MetaSpline!(T, X)(grid.move, data.move, configuration); +} + +/// ditto +struct MetaSpline(T, X) +{ + import mir.interpolate.utility: DeepType; + // alias ElementInterpolator = Linear!(F, N, X); + alias F = ValueType!(T, X); + /// + private Repeat!(3, Spline!F) splines; + /// + RCArray!(const T) data; + // + private RCArray!F _temp; + /// + SplineConfiguration!F configuration; + + /// + this( + RCArray!(immutable X) grid, + RCArray!(const T) data, + SplineConfiguration!F configuration, + ) + { + import core.lifetime: move; + + if (grid.length < 2) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exc_min.toMutable; } + else assert(0, msg_min); + } + + if (grid.length != data.length) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exc_eq.toMutable; } + else assert(0, msg_eq); + } + + this.data = data.move; + this._temp = grid.length; + this.splines[0] = grid.asSlice; + this.splines[1] = grid.asSlice; + this.splines[2] = grid.moveToSlice; + this.configuration = configuration; + } + + /// + bool opEquals()(auto ref scope const typeof(this) rhs) scope const @trusted pure nothrow @nogc + { + return this.gridScopeView == rhs.gridScopeView + && this.data == rhs.data + && this.configuration == rhs.configuration; + } + + /// + MetaLinear lightConst()() const @property { return *cast(MetaLinear*)&this; } + + /// + immutable(X)[] gridScopeView(size_t dimension = 0)() return scope const @property @trusted + if (dimension == 0) + { + return splines[0].gridScopeView; + } + + /++ + Returns: intervals count. + +/ + size_t intervalCount(size_t dimension = 0)() scope const @property + if (dimension == 0) + { + assert(data.length > 1); + return data.length - 1; + } + + /// + enum uint derivativeOrder = 3; + + static if (__traits(compiles, {enum N = T.dimensionCount;})) + /// + enum uint dimensionCount = T.dimensionCount + 1; + + /// + template opCall(uint derivative = 0) + // if (derivative <= derivativeOrder) + { + /++ + `(x)` operator. + Complexity: + `O(log(grid.length))` + +/ + auto opCall(X...)(const X xs) scope const @trusted + // if (X.length == dimensionCount) + { + F[2][][derivative + 1] mutable; + + static foreach (o; 0 .. derivative + 1) + { + mutable[o] = cast(F[2][]) splines[o]._data.lightScope.field; + assert(mutable[o].length == data.length); + } + + static if (!derivative) + { + foreach (i, ref d; data) + mutable[0][i][0] = d(xs[1 .. $]); + } + else + { + foreach (i, ref d; data) + { + auto node = d.opCall!derivative(xs[1 .. $]); + static foreach (o; 0 .. derivative + 1) + mutable[o][i][0] = node[o]; + } + } + + static foreach (o; 0 .. derivative + 1) + { + (*cast(Spline!F*)&splines[o])._computeDerivativesTemp( + configuration.kind, + configuration.param, + configuration.leftBoundary, + configuration.rightBoundary, + (cast(F[])_temp[]).sliced); + } + + static if (!derivative) + { + return splines[0](xs[0]); + } + else + { + typeof(splines[0].opCall!derivative(xs[0]))[derivative + 1] ret = void; + static foreach (o; 0 .. derivative + 1) + {{ + auto s = splines[o].opCall!derivative(xs[0]); + static foreach (r; 0 .. derivative + 1) + ret[r][o] = s[r]; + + }} + return ret; + } + } + } + + /// + alias withDerivative = opCall!1; + /// + alias withTwoDerivatives = opCall!2; +} + +/// 2D trapezoid-like (not rectilinear) linear interpolation +version(mir_test) +unittest +{ + auto x = [ + [0.0, 1, 2, 3, 5], + [-4.0, 3, 4], + [0.0, 10], + ]; + auto y = [ + [4.0, 0, 9, 23, 40], + [9.0, 0, 3], + [4.0, 40], + ]; + + auto g = [7.0, 10, 15]; + + import mir.rc.array: RCArray; + import mir.ndslice.allocation: rcslice; + + auto d = RCArray!(Spline!double)(3); + + foreach (i; 0 .. x.length) + d[i] = spline!double(x[i].rcslice!(immutable double), y[i].rcslice!(const double)); + + auto trapezoidInterpolator = metaSpline!double(g.rcarray!(immutable double), d.lightConst); + + auto val = trapezoidInterpolator(9.0, 1.8); +} + +version(mir_test) +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice; + alias appreq = (a, b) => approxEqual(a, b, 10e-10, 10e-10); + + //// set test function //// + enum y_x0 = 2; + enum y_x1 = -7; + enum y_x0x1 = 3; + + // this function should be approximated very well + alias f = (x0, x1) => y_x0 * x0 + y_x1 * x1 + y_x0x1 * x0 * x1 - 11; + + ///// set interpolant //// + static immutable x0 = [-1.0, 2, 8, 15]; + static immutable x1 = [-4.0, 2, 5, 10, 13]; + + auto grid = cartesian(x0, x1) + .map!f + .rcslice + .lightConst; + + auto int0 = spline!double(x1.rcslice!(immutable double), grid[0]); + auto int1 = spline!double(x1.rcslice!(immutable double), grid[1]); + auto int2 = spline!double(x1.rcslice!(immutable double), grid[2]); + auto int3 = spline!double(x1.rcslice!(immutable double), grid[3]); + + auto interpolant = metaSpline(x0.rcarray!(immutable double), rcarray(int0, int1, int2, int3).lightConst, SplineConfiguration!double.init); + assert(interpolant == interpolant); + + ///// compute test data //// + auto test_grid = cartesian(x0.sliced + 1.23, x1.sliced + 3.23); + // auto test_grid = cartesian(x0 + 0, x1 + 0); + auto real_data = test_grid.map!f; + auto interp_data = test_grid.vmap(interpolant); + + ///// verify result //// + assert(all!appreq(interp_data, real_data)); + + auto z0 = 1.23; + auto z1 = 3.21; + auto d = interpolant.withDerivative(z0, z1); + assert(appreq(d[0][0], f(z0, z1))); + assert(appreq(d[1][0], y_x0 + y_x0x1 * z1)); + assert(appreq(d[0][1], y_x1 + y_x0x1 * z0)); + assert(appreq(d[1][1], y_x0x1)); +} diff --git a/source/mir/interpolate/utility.d b/source/mir/interpolate/utility.d index 08f86e51..b520ce29 100644 --- a/source/mir/interpolate/utility.d +++ b/source/mir/interpolate/utility.d @@ -2,10 +2,18 @@ module mir.interpolate.utility; import mir.ndslice.slice; import std.traits; -import mir.math.common: optmath; +import mir.math.common: fmamath; + +package template DeepType(T) +{ + static if (is(T : E[N], E, size_t N)) + alias DeepType = DeepType!E; + else + alias DeepType = T; +} /++ -ParabolaKernel structure. +Quadratic function structure +/ struct ParabolaKernel(T) { @@ -16,8 +24,18 @@ struct ParabolaKernel(T) /// T c = 0; +@fmamath: + + /// + this(T a, T b, T c) + { + this.a = a; + this.b = b; + this.c = c; + } + /// Builds parabola given three points - this(T)(T x0, T x1, T x2, T y0, T y1, T y2) + this(T x0, T x1, T x2, T y0, T y1, T y2) { auto b1 = x1 - x0; auto b2 = x2 - x1; @@ -33,6 +51,25 @@ struct ParabolaKernel(T) c = y1 - x1 * (a * x1 + b); } + /++ + Params: + x0 = `x0` + x1 = `x1` + y0 = `f(x0)` + y1 = `f(x1)` + d1 = `f'(x1)` + +/ + static ParabolaKernel fromFirstDerivative(T x0, T x1, T y0, T y1, T d1) + { + auto dy = y1 - y0; + auto dx = x1 - x0; + auto dd = dy / dx; + auto a = (d1 - dd) / dx; + auto b = dd - a * (x0 + x1); + auto c = y1 - (a * x1 + b) * x1; + return ParabolaKernel(a, b, c); + } + /// auto opCall(uint derivative = 0)(T x) const if (derivative <= 2) @@ -57,21 +94,172 @@ struct ParabolaKernel(T) } /// ditto -ParabolaKernel!(Unqual!(typeof(X.init - Y.init))) parabolaKernel(X, Y)(in X x0, in X x1, in X x2, in Y y0, in Y y1, in Y y2) +ParabolaKernel!(Unqual!(typeof(X.init - Y.init))) parabolaKernel(X, Y)(in X x0, in X x1, in X x2, const Y y0, const Y y1, const Y y2) { return typeof(return)(x0, x1, x2, y0, y1, y2); } +/++ +Returns: `[f'(x0), f'(x1), f'(x2)]` ++/ +Unqual!(typeof(X.init - Y.init))[3] parabolaDerivatives(X, Y)(in X x0, in X x1, in X x2, const Y y0, const Y y1, const Y y2) +{ + auto d0 = (y2 - y1) / (x2 - x1); + auto d1 = (y0 - y2) / (x0 - x2); + auto d2 = (y1 - y0) / (x1 - x0); + return [d1 + d2 - d0, d0 + d2 - d1, d0 + d1 - d2]; +} + +version(mir_test) /// unittest { import mir.math.common: approxEqual; - alias f = (double x) => 3 * (x ^^ 2) + 7 * x + 5; - auto p = parabolaKernel(4, 9, 20, f(4), f(9), f(20)); + alias f = (double x) => 3 * x ^^ 2 + 7 * x + 5; + auto x0 = 4; + auto x1 = 9; + auto x2 = 20; + auto p = parabolaKernel(x0, x1, x2, f(x0), f(x1), f(x2)); assert(p.a.approxEqual(3)); assert(p.b.approxEqual(7)); assert(p.c.approxEqual(5)); assert(p(10).approxEqual(f(10))); } + + +version(mir_test) +/// +unittest +{ + import mir.math.common: approxEqual; + + alias f = (double x) => 3 * x ^^ 2 + 7 * x + 5; + alias d = (double x) => 2 * 3 * x + 7; + auto x0 = 4; + auto x1 = 9; + auto p = ParabolaKernel!double.fromFirstDerivative(x0, x1, f(x0), f(x1), d(x1)); + + assert(p.a.approxEqual(3)); + assert(p.b.approxEqual(7)); + assert(p.c.approxEqual(5)); +} + +/++ +Cubic function structure ++/ +struct CubicKernel(T) +{ + /// + T a = 0; + /// + T b = 0; + /// + T c = 0; + /// + T d = 0; + +@fmamath: + + /// + this(T a, T b, T c, T d) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + /++ + Params: + x0 = `x0` + x1 = `x1` + y0 = `f(x0)` + y1 = `f(x1)` + dd0 = `f''(x0)` + d1 = `f'(x1)` + +/ + static CubicKernel fromSecondAndFirstDerivative(T x0, T x1, T y0, T y1, T dd0, T d1) + { + auto hdd0 = 0.5f * dd0; + auto dy = y1 - y0; + auto dx = x1 - x0; + auto dd = dy / dx; + auto a = 0.5f * ((d1 - dd) / dx - hdd0) / dx; + auto b = hdd0 - 3 * a * x0; + auto c = d1 - (3 * a * x1 + 2 * b) * x1; + auto d = y1 - ((a * x1 + b) * x1 + c) * x1; + return CubicKernel!T(a, b, c, d); + } + + /// + auto opCall(uint derivative = 0)(T x) const + if (derivative <= 3) + { + auto y = ((a * x + b) * x + c) * x + d; + static if (derivative == 0) + return y; + else + { + T[derivative + 1] ret; + ret[0] = y; + ret[1] = (3 * a * x + 2 * b) * x + c; + static if (derivative > 1) + { + ret[2] = 6 * a * x + 2 * b; + static if (derivative > 2) + ret[3] = 6 * a; + } + return ret; + } + } + + /// + alias withDerivative = opCall!1; + /// + alias withTwoDerivatives = opCall!2; +} + +version(mir_test) +/// +unittest +{ + import mir.math.common: approxEqual; + + alias f = (double x) => 3 * x ^^ 3 + 7 * x ^^ 2 + 5 * x + 10; + alias d = (double x) => 3 * 3 * x ^^ 2 + 2 * 7 * x + 5; + alias s = (double x) => 6 * 3 * x + 2 * 7; + auto x0 = 4; + auto x1 = 9; + auto p = CubicKernel!double.fromSecondAndFirstDerivative(x0, x1, f(x0), f(x1), s(x0), d(x1)); + + assert(p.a.approxEqual(3)); + assert(p.b.approxEqual(7)); + assert(p.c.approxEqual(5)); + assert(p.d.approxEqual(10)); + assert(p(13).approxEqual(f(13))); + assert(p.opCall!1(13)[1].approxEqual(d(13))); + assert(p.opCall!2(13)[2].approxEqual(s(13))); + assert(p.opCall!3(13)[3].approxEqual(18)); +} + + +package auto pchipTail(T)(in T step0, in T step1, in T diff0, in T diff1) +{ + import mir.math.common: copysign, fabs; + if (!diff0) + { + return 0; + } + auto slope = ((step0 * 2 + step1) * diff0 - step0 * diff1) / (step0 + step1); + if (copysign(1f, slope) != copysign(1f, diff0)) + { + return 0; + } + if ((copysign(1f, diff0) != copysign(1f, diff1)) && (fabs(slope) > fabs(diff0 * 3))) + { + return diff0 * 3; + } + return slope; +} diff --git a/source/mir/lob.d b/source/mir/lob.d new file mode 100644 index 00000000..4fa0ad90 --- /dev/null +++ b/source/mir/lob.d @@ -0,0 +1,40 @@ +/++ ++/ +module mir.lob; + +import mir.serde: serdeRegister; + +/++ +Values of type clob are encoded as a sequence of octets that should be interpreted as text +with an unknown encoding (and thus opaque to the application). ++/ +@serdeRegister +struct Clob +{ + /// + const(char)[] data; + + /// + int opCmp(scope const typeof(this) rhs) + @safe pure nothrow @nogc scope const + { + return __cmp(data, rhs.data); + } +} + +/++ +This is a sequence of octets with no interpretation (and thus opaque to the application). ++/ +@serdeRegister +struct Blob +{ + /// + const(ubyte)[] data; + + /// + int opCmp(scope const typeof(this) rhs) + @safe pure nothrow @nogc scope const + { + return __cmp(data, rhs.data); + } +} diff --git a/source/mir/math/func/expdigamma.d b/source/mir/math/func/expdigamma.d index 608682e5..64d38c1f 100644 --- a/source/mir/math/func/expdigamma.d +++ b/source/mir/math/func/expdigamma.d @@ -1,7 +1,7 @@ /** -License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) -Authors: Ilya Yaroshenko +Authors: Ilia Ki */ module mir.math.func.expdigamma; @@ -50,8 +50,9 @@ F expDigamma(F)(in F x) version(mir_test) unittest { - import std.meta; - import std.mathspecial; + import std.meta: AliasSeq; + import std.mathspecial: digamma; + import mir.math: approxEqual, exp, nextUp, nextDown; assert(approxEqual(expDigamma(0.001), exp(digamma(0.001)))); assert(approxEqual(expDigamma(0.1), exp(digamma(0.1)))); assert(approxEqual(expDigamma(1.0), exp(digamma(1.0)))); diff --git a/source/mir/math/func/hermite.d b/source/mir/math/func/hermite.d new file mode 100644 index 00000000..ce5763ba --- /dev/null +++ b/source/mir/math/func/hermite.d @@ -0,0 +1,182 @@ +/++ +Hermite Polynomial Coefficients + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: John Hall + ++/ + +module mir.math.func.hermite; + +/++ +Normalized (probabilist's) Hermite polynomial coefficients + +Params: + N = Degree of polynomial + +See_also: + $(LINK2 https://en.wikipedia.org/wiki/Hermite_polynomials, Hermite polynomials) ++/ +@safe pure nothrow +long[] hermiteCoefficientsNorm(size_t N) +{ + if (N == 0) { + return [1]; + } else { + typeof(return) output = [0, 1]; + if (N > 1) { + output.length = N + 1; + int K; + typeof(return) h_N_minus_1 = hermiteCoefficientsNorm(0); // to be copied to h_N_minus_2 in loop + h_N_minus_1.length = N; + typeof(return) h_N_minus_2; //value replaced later + h_N_minus_2.length = N - 1; + + foreach (size_t j; 2..(N + 1)) { + h_N_minus_2[0..(j - 1)] = h_N_minus_1[0..(j - 1)]; + h_N_minus_1[0..j] = output[0..j]; + K = -(cast(int) j - 1); + output[0] = K * h_N_minus_2[0]; + foreach (size_t i; 1..(j - 1)) { + output[i] = h_N_minus_1[i - 1] + K * h_N_minus_2[i]; + } + foreach (size_t i; (j - 1)..(j + 1)) { + output[i] = h_N_minus_1[i - 1]; + } + } + } + return output; + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.polynomial: polynomial; + import mir.rc.array: rcarray; + import mir.test: should; + + auto h0 = hermiteCoefficientsNorm(0).rcarray!(const double).polynomial; + auto h1 = hermiteCoefficientsNorm(1).rcarray!(const double).polynomial; + auto h2 = hermiteCoefficientsNorm(2).rcarray!(const double).polynomial; + auto h3 = hermiteCoefficientsNorm(3).rcarray!(const double).polynomial; + auto h4 = hermiteCoefficientsNorm(4).rcarray!(const double).polynomial; + auto h5 = hermiteCoefficientsNorm(5).rcarray!(const double).polynomial; + auto h6 = hermiteCoefficientsNorm(6).rcarray!(const double).polynomial; + auto h7 = hermiteCoefficientsNorm(7).rcarray!(const double).polynomial; + auto h8 = hermiteCoefficientsNorm(8).rcarray!(const double).polynomial; + auto h9 = hermiteCoefficientsNorm(9).rcarray!(const double).polynomial; + auto h10 = hermiteCoefficientsNorm(10).rcarray!(const double).polynomial; + + h0(3).should == 1; + h1(3).should == 3; + h2(3).should == 8; + h3(3).should == 18; + h4(3).should == 30; + h5(3).should == 18; + h6(3).should == -96; + h7(3).should == -396; + h8(3).should == -516; + h9(3).should == 1_620; + h10(3).should == 9_504; +} + +/// Also works with @nogc CTFE +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.ndslice.slice: sliced; + import mir.test: should; + + static immutable result = [-1, 0, 1]; + + static immutable hc2 = hermiteCoefficientsNorm(2); + hc2.sliced.should == result; +} + +/++ +Physicist's Hermite polynomial coefficients + +Params: + N = Degree of polynomial + +See_also: + $(LINK2 https://en.wikipedia.org/wiki/Hermite_polynomials, Hermite polynomials) ++/ +@safe pure nothrow +long[] hermiteCoefficients(size_t N) +{ + if (N == 0) { + return [1]; + } else { + typeof(return) output = [0, 2]; + if (N > 1) { + output.length = N + 1; + typeof(return) h_N_minus_1 = hermiteCoefficients(0); + h_N_minus_1.length = N; + + foreach (size_t j; 2..(N + 1)) { + h_N_minus_1[0..j] = output[0..j]; + output[0] = -h_N_minus_1[1]; + foreach (size_t i; 1..(j - 1)) { + output[i] = 2 * h_N_minus_1[i - 1] - (cast(int) i + 1) * h_N_minus_1[i + 1]; + } + foreach (size_t i; (j - 1)..(j + 1)) { + output[i] = 2 * h_N_minus_1[i - 1]; + } + } + } + return output; + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.polynomial: polynomial; + import mir.rc.array: rcarray; + import mir.test: should; + + auto h0 = hermiteCoefficients(0).rcarray!(const double).polynomial; + auto h1 = hermiteCoefficients(1).rcarray!(const double).polynomial; + auto h2 = hermiteCoefficients(2).rcarray!(const double).polynomial; + auto h3 = hermiteCoefficients(3).rcarray!(const double).polynomial; + auto h4 = hermiteCoefficients(4).rcarray!(const double).polynomial; + auto h5 = hermiteCoefficients(5).rcarray!(const double).polynomial; + auto h6 = hermiteCoefficients(6).rcarray!(const double).polynomial; + auto h7 = hermiteCoefficients(7).rcarray!(const double).polynomial; + auto h8 = hermiteCoefficients(8).rcarray!(const double).polynomial; + auto h9 = hermiteCoefficients(9).rcarray!(const double).polynomial; + auto h10 = hermiteCoefficients(10).rcarray!(const double).polynomial; + + h0(3).should == 1; + h1(3).should == 6; + h2(3).should == 34; + h3(3).should == 180; + h4(3).should == 876; + h5(3).should == 3_816; + h6(3).should == 14_136; + h7(3).should == 39_024; + h8(3).should == 36_240; + h9(3).should == -406_944; + h10(3).should == -3_093_984; +} + +/// Also works with @nogc CTFE +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.ndslice.slice: sliced; + import mir.test: should; + + static immutable result = [-2, 0, 4]; + + static immutable hc2 = hermiteCoefficients(2); + hc2.sliced.should == result; +} diff --git a/source/mir/math/func/normal.d b/source/mir/math/func/normal.d new file mode 100644 index 00000000..5a5e6e8a --- /dev/null +++ b/source/mir/math/func/normal.d @@ -0,0 +1,463 @@ +/** +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +*/ +/** + * Error Functions and Normal Distribution. + * + * Copyright: Based on the CEPHES math library, which is + * Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com). + * Authors: Stephen L. Moshier, ported to D by Don Clugston and David Nadlinger. Adopted to Mir by Ilia Ki. + */ +/** + * Macros: + * NAN = $(RED NAN) + * INTEGRAL = ∫ + * POWER = $1$2 + * Special Values + * $0 + */ + +module mir.math.func.normal; + +import std.traits: isFloatingPoint; +import mir.math.common; + +@safe pure nothrow @nogc: + +/// +T normalPDF(T)(const T z) + if (isFloatingPoint!T) +{ + import mir.math.common: sqrt, exp; + return T(SQRT2PIINV) * exp(z * z * -0.5); +} + +/// ditto +T normalPDF(T)(const T x, const T mean, const T stdDev) +if (isFloatingPoint!T) +{ + return normalPDF((x - mean) / stdDev) / stdDev; +} + +/++ +Computes the normal distribution cumulative distribution function (CDF). +The normal (or Gaussian, or bell-shaped) distribution is +defined as: +normalDist(x) = 1/$(SQRT) π $(INTEGRAL -$(INFINITY), x) exp( - $(POWER t, 2)/2) dt + = 0.5 + 0.5 * erf(x/sqrt(2)) + = 0.5 * erfc(- x/sqrt(2)) +To maintain accuracy at high values of x, use +normalCDF(x) = 1 - normalCDF(-x). +Accuracy: +Within a few bits of machine resolution over the entire +range. +References: +$(LINK http://www.netlib.org/cephes/ldoubdoc.html), +G. Marsaglia, "Evaluating the Normal Distribution", +Journal of Statistical Software 11, (July 2004). ++/ +T normalCDF(T)(const T a) + if (isFloatingPoint!T) +{ + pragma(inline, false); + import mir.math.constant: SQRT1_2; + + T x = a * T(SQRT1_2); + T z = fabs(x); + + if (z < 1) + { + return 0.5f + 0.5f * erf(x); + } + else + { + T y = 0.5f * erfce(z); + /* Multiply by exp(-x^2 / 2) */ + z = expx2(a, -1); + y = y * sqrt(z); + if (y != y) + y = 0; + if (x > 0) + y = 1 - y; + return y; + } +} + +/// ditto +T normalCDF(T)(const T x, const T mean, const T stdDev) + if (isFloatingPoint!T) +{ + return normalCDF((x - mean) / stdDev); +} + +version(mir_test) +@safe +unittest +{ + assert(fabs(normalCDF(1.0L) - (0.841344746068543))< 0.0000000000000005); + assert(normalCDF(double.infinity) == 1); + assert(normalCDF(100.0) == 1); + assert(normalCDF(8.0) < 1); + assert(normalCDF(-double.infinity) == 0); + assert(normalCDF(-100.0) == 0); + assert(normalCDF(-8.0) > 0); +} + +/// +T normalInvCDF(T)(const T p) +in { + assert(p >= 0 && p <= 1, "Domain error"); +} +do +{ + pragma(inline, false); + if (p <= 0 || p >= 1) + { + if (p == 0) + return -T.infinity; + if ( p == 1 ) + return T.infinity; + return T.nan; // domain error + } + int code = 1; + T y = p; + if ( y > (1 - T(EXP_2)) ) + { + y = 1 - y; + code = 0; + } + + T x, z, y2, x0, x1; + + if ( y > T(EXP_2) ) + { + y = y - 0.5L; + y2 = y * y; + x = y + y * (y2 * rationalPoly!(P0, Q0)(y2)); + return x * double(SQRT2PI); + } + + x = sqrt( -2 * log(y) ); + x0 = x - log(x)/x; + z = 1/x; + if ( x < 8 ) + { + x1 = z * rationalPoly!(P1, Q1)(z); + } + else if ( x < 32 ) + { + x1 = z * rationalPoly!(P2, Q2)(z); + } + else + { + x1 = z * rationalPoly!(P3, Q3)(z); + } + x = x0 - x1; + if ( code != 0 ) + { + x = -x; + } + return x; +} + +/// ditto +T normalInvCDF(T)(const T p, const T mean, const T stdDev) + if (isFloatingPoint!T) +{ + return normalInvCDF(p) * stdDev + mean; +} + +/// +version(mir_test) +@safe unittest +{ + import std.math: feqrel; + // TODO: Use verified test points. + // The values below are from Excel 2003. + assert(fabs(normalInvCDF(0.001) - (-3.09023230616779))< 0.00000000000005); + assert(fabs(normalInvCDF(1e-50) - (-14.9333375347885))< 0.00000000000005); + assert(feqrel(normalInvCDF(0.999L), -normalInvCDF(0.001L)) > real.mant_dig-6); + + // Excel 2003 gets all the following values wrong! + assert(normalInvCDF(0.0) == -real.infinity); + assert(normalInvCDF(1.0) == real.infinity); + assert(normalInvCDF(0.5) == 0); + // (Excel 2003 returns norminv(p) = -30 for all p < 1e-200). + // The value tested here is the one the function returned in Jan 2006. + real unknown1 = normalInvCDF(1e-250L); + assert( fabs(unknown1 -(-33.79958617269L) ) < 0.00000005); +} + +/// +enum real SQRT2PI = 2.50662827463100050241576528481104525L; // sqrt(2pi) +/// +enum real SQRT2PIINV = 1 / SQRT2PI; // 1 / sqrt(2pi) + +package(mir) { + +enum real EXP_2 = 0.135335283236612691893999494972484403L; /* exp(-2) */ + +/// +enum T MAXLOG(T) = log(T.max); +/// +enum T MINLOG(T) = log(T.min_normal * T.epsilon); // log(smallest denormal); +} + +/** + * Exponential of squared argument + * + * Computes y = exp(x*x) while suppressing error amplification + * that would ordinarily arise from the inexactness of the + * exponential argument x*x. + * + * If sign < 0, the result is inverted; i.e., y = exp(-x*x) . + * + * ACCURACY: + * Relative error: + * arithmetic domain # trials peak rms + * IEEE -106.566, 106.566 10^5 1.6e-19 4.4e-20 + */ +package(mir) +T expx2(T)(const T x_, int sign) +{ + /* + Cephes Math Library Release 2.9: June, 2000 + Copyright 2000 by Stephen L. Moshier + */ + const T M = 32_768.0; + const T MINV = 3.0517578125e-5L; + + T x = fabs(x_); + if (sign < 0) + x = -x; + + /* Represent x as an exact multiple of M plus a residual. + M is a power of 2 chosen so that exp(m * m) does not overflow + or underflow and so that |x - m| is small. */ + T m = MINV * floor(M * x + 0.5f); + T f = x - m; + + /* x^2 = m^2 + 2mf + f^2 */ + T u = m * m; + T u1 = 2 * m * f + f * f; + + if (sign < 0) + { + u = -u; + u1 = -u1; + } + + if (u + u1 > MAXLOG!T) + return T.infinity; + + /* u is exact, u1 is small. */ + return exp(u) * exp(u1); +} + +/** + Exponentially scaled erfc function + exp(x^2) erfc(x) + valid for x > 1. + Use with ndtrl and expx2l. */ +package(mir) +T erfce(T)(const T x) +{ + T y = 1 / x; + + if (x < 8) + { + return rationalPoly!(P, Q)(y); + } + else + { + return y * rationalPoly!(R, S)(y * y); + } +} + +private T rationalPoly(alias numerator, alias denominator, T)(const T x) pure nothrow +{ + return x.poly!numerator / x.poly!denominator; +} + +private T poly(alias vec, T)(const T x) +{ + import mir.internal.utility: Iota; + T y = T(vec[$ - 1]); + foreach_reverse(i; Iota!(vec.length - 1)) + { + y *= x; + y += T(vec[i]); + } + return y; +} + +private { + +/* erfc(x) = exp(-x^2) P(1/x)/Q(1/x) + 1/8 <= 1/x <= 1 + Peak relative error 5.8e-21 */ +immutable real[10] P = [ -0x1.30dfa809b3cc6676p-17, 0x1.38637cd0913c0288p+18, + 0x1.2f015e047b4476bp+22, 0x1.24726f46aa9ab08p+25, 0x1.64b13c6395dc9c26p+27, + 0x1.294c93046ad55b5p+29, 0x1.5962a82f92576dap+30, 0x1.11a709299faba04ap+31, + 0x1.11028065b087be46p+31, 0x1.0d8ef40735b097ep+30 +]; + +immutable real[11] Q = [ 0x1.14d8e2a72dec49f4p+19, 0x1.0c880ff467626e1p+23, + 0x1.04417ef060b58996p+26, 0x1.404e61ba86df4ebap+28, 0x1.0f81887bc82b873ap+30, + 0x1.4552a5e39fb49322p+31, 0x1.11779a0ceb2a01cep+32, 0x1.3544dd691b5b1d5cp+32, + 0x1.a91781f12251f02ep+31, 0x1.0d8ef3da605a1c86p+30, 1.0 +]; + +/* erfc(x) = exp(-x^2) 1/x R(1/x^2) / S(1/x^2) + 1/128 <= 1/x < 1/8 + Peak relative error 1.9e-21 */ +immutable real[5] R = [ 0x1.b9f6d8b78e22459ep-6, 0x1.1b84686b0a4ea43ap-1, + 0x1.b8f6aebe96000c2ap+1, 0x1.cb1dbedac27c8ec2p+2, 0x1.cf885f8f572a4c14p+1 +]; + +immutable real[6] S = [ + 0x1.87ae3cae5f65eb5ep-5, 0x1.01616f266f306d08p+0, 0x1.a4abe0411eed6c22p+2, + 0x1.eac9ce3da600abaap+3, 0x1.5752a9ac2faebbccp+3, 1.0 +]; + +/* erf(x) = x P(x^2)/Q(x^2) + 0 <= x <= 1 + Peak relative error 7.6e-23 */ +immutable real[7] T = [ 0x1.0da01654d757888cp+20, 0x1.2eb7497bc8b4f4acp+17, + 0x1.79078c19530f72a8p+15, 0x1.4eaf2126c0b2c23p+11, 0x1.1f2ea81c9d272a2ep+8, + 0x1.59ca6e2d866e625p+2, 0x1.c188e0b67435faf4p-4 +]; + +immutable real[7] U = [ 0x1.dde6025c395ae34ep+19, 0x1.c4bc8b6235df35aap+18, + 0x1.8465900e88b6903ap+16, 0x1.855877093959ffdp+13, 0x1.e5c44395625ee358p+9, + 0x1.6a0fed103f1c68a6p+5, 1.0 +]; + +} + +package(mir) +F erf(F)(const F x) + if(isFloatingPoint!F) +{ + if (x == 0) + return x; // deal with negative zero + if (x == -F.infinity) + return -1; + if (x == F.infinity) + return 1; + immutable ax = fabs(x); + if (ax > 1) + return 1 - erfc(x); + + return x * rationalPoly!(T, U)(x * x); +} + +/** + * Complementary error function + * + * erfc(x) = 1 - erf(x), and has high relative accuracy for + * values of x far from zero. (For values near zero, use erf(x)). + * + * 1 - erf(x) = 2/ $(SQRT)(π) + * $(INTEGRAL x, $(INFINITY)) exp( - $(POWER t, 2)) dt + * + * + * For small x, erfc(x) = 1 - erf(x); otherwise rational + * approximations are computed. + * + * A special function expx2(x) is used to suppress error amplification + * in computing exp(-x^2). + */ +package(mir) +T erfc(T)(const T a) +{ + if (a == T.infinity) + return 0; + if (a == -T.infinity) + return 2; + + immutable x = (a < 0) ? -a : a; + + if (x < 1) + return 1 - erf(a); + + if (-a * a < -MAXLOG!T) + { + // underflow + if (a < 0) return 2; + else return 0; + } + + T y; + immutable z = expx2(a, -1); + + y = 1 / x; + if (x < 8) + y = z * rationalPoly!(P, Q)(y); + else + y = z * y * rationalPoly!(R, S)(y * y); + + if (a < 0) + y = 2 - y; + + if (y == 0) + { + // underflow + if (a < 0) return 2; + else return 0; + } + + return y; +} + +private: + +static immutable real[8] P0 = +[ -0x1.758f4d969484bfdcp-7, 0x1.53cee17a59259dd2p-3, +-0x1.ea01e4400a9427a2p-1, 0x1.61f7504a0105341ap+1, -0x1.09475a594d0399f6p+2, +0x1.7c59e7a0df99e3e2p+1, -0x1.87a81da52edcdf14p-1, 0x1.1fb149fd3f83600cp-7 +]; + +static immutable real[8] Q0 = +[ -0x1.64b92ae791e64bb2p-7, 0x1.7585c7d597298286p-3, +-0x1.40011be4f7591ce6p+0, 0x1.1fc067d8430a425ep+2, -0x1.21008ffb1e7ccdf2p+3, +0x1.3d1581cf9bc12fccp+3, -0x1.53723a89fd8f083cp+2, 1.0 +]; + +static immutable real[10] P1 = +[ 0x1.20ceea49ea142f12p-13, 0x1.cbe8a7267aea80bp-7, +0x1.79fea765aa787c48p-2, 0x1.d1f59faa1f4c4864p+1, 0x1.1c22e426a013bb96p+4, +0x1.a8675a0c51ef3202p+5, 0x1.75782c4f83614164p+6, 0x1.7a2f3d90948f1666p+6, +0x1.5cd116ee4c088c3ap+5, 0x1.1361e3eb6e3cc20ap+2 +]; + +static immutable real[10] Q1 = +[ 0x1.3a4ce1406cea98fap-13, 0x1.f45332623335cda2p-7, +0x1.98f28bbd4b98db1p-2, 0x1.ec3b24f9c698091cp+1, 0x1.1cc56ecda7cf58e4p+4, +0x1.92c6f7376bf8c058p+5, 0x1.4154c25aa47519b4p+6, 0x1.1b321d3b927849eap+6, +0x1.403a5f5a4ce7b202p+4, 1.0 +]; + +static immutable real[8] P2 = +[ 0x1.8c124a850116a6d8p-21, 0x1.534abda3c2fb90bap-13, +0x1.29a055ec93a4718cp-7, 0x1.6468e98aad6dd474p-3, 0x1.3dab2ef4c67a601cp+0, +0x1.e1fb3a1e70c67464p+1, 0x1.b6cce8035ff57b02p+2, 0x1.9f4c9e749ff35f62p+1 +]; + +static immutable real[8] Q2 = +[ 0x1.af03f4fc0655e006p-21, 0x1.713192048d11fb2p-13, +0x1.4357e5bbf5fef536p-7, 0x1.7fdac8749985d43cp-3, 0x1.4a080c813a2d8e84p+0, +0x1.c3a4b423cdb41bdap+1, 0x1.8160694e24b5557ap+2, 1.0 +]; + +static immutable real[8] P3 = +[ -0x1.55da447ae3806168p-34, -0x1.145635641f8778a6p-24, +-0x1.abf46d6b48040128p-17, -0x1.7da550945da790fcp-11, -0x1.aa0b2a31157775fap-8, +0x1.b11d97522eed26bcp-3, 0x1.1106d22f9ae89238p+1, 0x1.029a358e1e630f64p+1 +]; + +static immutable real[8] Q3 = +[ -0x1.74022dd5523e6f84p-34, -0x1.2cb60d61e29ee836p-24, +-0x1.d19e6ec03a85e556p-17, -0x1.9ea2a7b4422f6502p-11, -0x1.c54b1e852f107162p-8, +0x1.e05268dd3c07989ep-3, 0x1.239c6aff14afbf82p+1, 1.0 +]; diff --git a/source/mir/math/numeric.d b/source/mir/math/numeric.d index 6ad24cde..2a14cc56 100644 --- a/source/mir/math/numeric.d +++ b/source/mir/math/numeric.d @@ -1,68 +1,77 @@ /++ This module contains simple numeric algorithms. - -License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). - -Authors: Ilya Yaroshenko - -Copyright: 2015-, Ilya Yaroshenko; 2017 Symmetry Investments Group and Kaleidic Associates Advisory Limited. - +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments Sponsors: This work has been sponsored by $(SUBREF http://symmetryinvestments.com, Symmetry Investments) and Kaleidic Associates. +/ module mir.math.numeric; import mir.math.common; import mir.primitives; -import std.range.primitives: isInputRange; - -import std.traits; +import mir.primitives: isInputRange; +import std.traits: CommonType, Unqual, isIterable, ForeachType, isPointer; +import mir.internal.utility: isFloatingPoint; /// -struct Prod(T) +struct ProdAccumulator(T) if (isFloatingPoint!T) { + alias F = Unqual!T; + /// - long exp = 1; + long exp = 1L; /// - T x = 0.5f; + F x = cast(F) 0.5; /// alias mantissa = x; /// - this(T value) + @safe pure @nogc nothrow + this(F value) { - int lexp; import mir.math.ieee: frexp; - x = frexp(value, lexp); - exp = lexp; + + int lexp; + this.x = frexp(value, lexp); + this.exp = lexp; } /// - this(long exp, T x) + @safe pure @nogc nothrow + this(long exp, F x) { this.exp = exp; this.x = x; } /// - void put(T e) + @safe pure @nogc nothrow + void put(U)(U e) + if (is(U : T)) { - int lexp; - import mir.math.ieee: frexp; - x *= frexp(e, lexp); - exp += lexp; - if (x.fabs < 0.5f) + static if (is(U == T)) { - x += x; - exp--; + int lexp; + import mir.math.ieee: frexp; + x *= frexp(e, lexp); + exp += lexp; + if (x.fabs < 0.5f) + { + x += x; + exp--; + } + } else { + return put(cast(T) e); } } /// - void put(Prod p) + @safe pure @nogc nothrow + void put(ProdAccumulator!T value) { - exp += p.exp; - x *= p.x; + exp += value.exp; + x *= value.x; if (x.fabs < 0.5f) { x += x; @@ -71,7 +80,38 @@ struct Prod(T) } /// - T value() const scope @property + void put(Range)(Range r) + if (isIterable!Range) + { + foreach (ref elem; r) + put(elem); + } + + import mir.ndslice.slice; + + /// ditto + void put(Range: Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind)(Range r) + { + static if (N > 1 && kind == Contiguous) + { + import mir.ndslice.topology: flattened; + this.put(r.flattened); + } + else + static if (isPointer!Iterator && kind == Contiguous) + { + this.put(r.field); + } + else + { + foreach(elem; r) + this.put(elem); + } + } + + /// + @safe pure @nogc nothrow + T prod() const scope @property { import mir.math.ieee: ldexp; int e = @@ -81,99 +121,364 @@ struct Prod(T) return ldexp(mantissa, e); } - Prod ldexp(long exp) const + /// + @safe pure @nogc nothrow + ProdAccumulator!T ldexp(long exp) const { - return Prod(this.exp + exp, mantissa); + return typeof(return)(this.exp + exp, mantissa); } // /// alias opOpAssign(string op : "*") = put; /// - Prod opUnary(string op : "-")() const + @safe pure @nogc nothrow + ProdAccumulator!T opUnary(string op : "-")() const { - return Prod(exp, -mantissa); + return typeof(return)(exp, -mantissa); } /// - Prod opUnary(string op : "+")() const + @safe pure @nogc nothrow + ProdAccumulator!T opUnary(string op : "+")() const { - return Prod(exp, +mantissa); + return typeof(return)(exp, +mantissa); } } -/++ -Decompose a range or nd-slice of floating point numbers into a single product structure. -+/ -Prod!(Unqual!(DeepElementType!Range)) -wipProd(Range)(Range r) - if (isInputRange!Range && isFloatingPoint!(DeepElementType!Range)) +/// +version(mir_test) +@safe pure nothrow +unittest { - import mir.algorithm.iteration: each; - typeof(return) prod; - r.each!(x => prod.put(x)); - return prod; + import mir.ndslice.slice: sliced; + + ProdAccumulator!float x; + x.put([1, 2, 3].sliced); + assert(x.prod == 6f); + x.put(4); + assert(x.prod == 24f); +} + +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + static immutable a = [1, 2, 3]; + ProdAccumulator!float x; + x.put(a); + assert(x.prod == 6f); + x.put(4); + assert(x.prod == 24f); + static assert(is(typeof(x.prod) == float)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + ProdAccumulator!double x; + x.put([1.0, 2.0, 3.0]); + assert(x.prod == 6.0); + x.put(4.0); + assert(x.prod == 24.0); + static assert(is(typeof(x.prod) == double)); +} + +package template prodType(T) +{ + import mir.math.sum: elementType; + + alias U = elementType!T; + + static if (__traits(compiles, { + auto temp = U.init * U.init; + temp *= U.init; + })) { + import mir.math.stat: statType; + + alias V = typeof(U.init * U.init); + alias prodType = statType!(V, false); + } else { + static assert(0, "prodType: Can't prod elements of type " ~ U.stringof); + } } /++ -Decompose a single floating point number. +Calculates the product of the elements of the input. + +This function uses a separate exponential accumulation algorithm to calculate the +product. A consequence of this is that the result must be a floating point type. +To calculate the product of a type that is not implicitly convertible to a +floating point type, use $(MREF mir, algorithm, iteration, reduce) or $(MREF mir, algorithm, iteration, fold). + +/++ +Params: + r = finite iterable range +Returns: + The prduct of all the elements in `r` +/ -Prod!T wipProd(T)(const T x) - if (isFloatingPoint!T) + +See_also: +$(MREF mir, algorithm, iteration, reduce) +$(MREF mir, algorithm, iteration, fold) ++/ +F prod(F, Range)(Range r) + if (isFloatingPoint!F && isIterable!Range) { - return typeof(return)(x); + import core.lifetime: move; + + ProdAccumulator!F prod; + prod.put(r.move); + return prod.prod; } /++ -Compute the product of the input range $(D r) using separate exponent accumulation. +Params: + r = finite iterable range + exp = value of exponent +Returns: + The mantissa, such that the product equals the mantissa times 2^^exp +/ -Unqual!(DeepElementType!Range) prod(Range)(Range r, ref long exp) - if (isInputRange!Range && isFloatingPoint!(DeepElementType!Range)) +F prod(F, Range)(Range r, ref long exp) + if (isFloatingPoint!F && isIterable!Range) { - auto prod = wipProd(r); + import core.lifetime: move; + + ProdAccumulator!F prod; + prod.put(r.move); exp = prod.exp; return prod.x; } -/// ditto -Unqual!(DeepElementType!Range) prod(Range)(Range r) - if (isInputRange!Range && isFloatingPoint!(DeepElementType!Range)) +/++ +Params: + r = finite iterable range +Returns: + The prduct of all the elements in `r` ++/ +prodType!Range prod(Range)(Range r) + if (isIterable!Range) +{ + import core.lifetime: move; + + alias F = typeof(return); + return .prod!(F, Range)(r.move); +} + +/++ +Params: + r = finite iterable range + exp = value of exponent +Returns: + The mantissa, such that the product equals the mantissa times 2^^exp ++/ +prodType!Range prod(Range)(Range r, ref long exp) + if (isIterable!Range) +{ + import core.lifetime: move; + + alias F = typeof(return); + return .prod!(F, Range)(r.move, exp); +} + +/++ +Params: + ar = values +Returns: + The prduct of all the elements in `ar` ++/ +prodType!T prod(T)(scope const T[] ar...) { - return wipProd(r).value; + alias F = typeof(return); + ProdAccumulator!F prod; + prod.put(ar); + return prod.prod; } -/// Arrays and Ranges +/// Product of arbitrary inputs version(mir_test) +@safe pure @nogc nothrow unittest { + assert(prod(1.0, 3, 4) == 12.0); + assert(prod!float(1, 3, 4) == 12f); +} + +/// Product of arrays and ranges +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + enum l = 2.0 ^^ (double.max_exp - 1); enum s = 2.0 ^^ -(double.max_exp - 1); auto r = [l, l, l, s, s, s, 0.8 * 2.0 ^^ 10]; + + assert(r.prod == 0.8 * 2.0 ^^ 10); + + // Can get the mantissa and exponent long e; - assert(r.prod(e) == 0.8); + assert(r.prod(e).approxEqual(0.8)); assert(e == 10); - assert(r.prod == 0.8 * 2.0 ^^ 10); } -/// Ndslices +/// Product of vector version(mir_test) +@safe pure nothrow unittest { - import mir.math.numeric: prod; import mir.ndslice.slice: sliced; import mir.algorithm.iteration: reduce; + import mir.math.common: approxEqual; enum l = 2.0 ^^ (double.max_exp - 1); enum s = 2.0 ^^ -(double.max_exp - 1); auto c = 0.8; auto u = c * 2.0 ^^ 10; - auto r = [l, l, l, - s, s, s, - u, u, u].sliced(3, 3); + auto r = [l, l, l, s, s, s, u, u, u].sliced; + + assert(r.prod == reduce!"a * b"(1.0, [u, u, u])); + long e; - assert(r.prod(e) == reduce!"a * b"(1.0, [c, c, c])); + assert(r.prod(e).approxEqual(reduce!"a * b"(1.0, [c, c, c]))); assert(e == 30); +} + +/// Product of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + import mir.algorithm.iteration: reduce; + + enum l = 2.0 ^^ (double.max_exp - 1); + enum s = 2.0 ^^ -(double.max_exp - 1); + auto c = 0.8; + auto u = c * 2.0 ^^ 10; + auto r = [ + [l, l, l], + [s, s, s], + [u, u, u] + ].fuse; + assert(r.prod == reduce!"a * b"(1.0, [u, u, u])); + + long e; + assert(r.prod(e) == reduce!"a * b"(1.0, [c, c, c])); + assert(e == 30); +} + +/// Column prod of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.topology: alongDim, byDim, map; + + auto x = [ + [2.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 5.0] + ].fuse; + + auto result = [4.0, 7.5, 7.5, 2.0, 5.25, 21.25]; + + // Use byDim or alongDim with map to compute mean of row/column. + assert(x.byDim!1.map!prod.all!approxEqual(result)); + assert(x.alongDim!0.map!prod.all!approxEqual(result)); + + // FIXME + // Without using map, computes the prod of the whole slice + // assert(x.byDim!1.prod.all!approxEqual(result)); + // assert(x.alongDim!0.prod.all!approxEqual(result)); +} + +/// Can also set output type +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + import mir.math.common: approxEqual; + import mir.ndslice.topology: repeat; + + auto x = [1, 2, 3].sliced; + assert(x.prod!float == 6f); +} + +/// Product of variables whose underlying types are implicitly convertible to double also have type double +version(mir_test) +@safe pure nothrow +unittest +{ + static struct Foo + { + int x; + alias x this; + } + + auto x = prod(1, 2, 3); + assert(x == 6.0); + static assert(is(typeof(x) == double)); + + auto y = prod([Foo(1), Foo(2), Foo(3)]); + assert(y == 6.0); + static assert(is(typeof(y) == double)); +} + +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: reduce; + import mir.math.common: approxEqual; + + enum l = 2.0 ^^ (double.max_exp - 1); + enum s = 2.0 ^^ -(double.max_exp - 1); + enum c = 0.8; + enum u = c * 2.0 ^^ 10; + static immutable r = [l, l, l, s, s, s, u, u, u]; + static immutable result1 = [u, u, u]; + static immutable result2 = [c, c, c]; + + assert(r.sliced.prod.approxEqual(reduce!"a * b"(1.0, result1))); + + long e; + assert(r.sliced.prod(e).approxEqual(reduce!"a * b"(1.0, result2))); + assert(e == 30); +} + +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: reduce; + import mir.math.common: approxEqual; + + enum l = 2.0 ^^ (float.max_exp - 1); + enum s = 2.0 ^^ -(float.max_exp - 1); + enum c = 0.8; + enum u = c * 2.0 ^^ 10; + static immutable r = [l, l, l, s, s, s, u, u, u]; + static immutable result1 = [u, u, u]; + static immutable result2 = [c, c, c]; + + assert(r.sliced.prod!double.approxEqual(reduce!"a * b"(1.0, result1))); + + long e; + assert(r.sliced.prod!double(e).approxEqual(reduce!"a * b"(1.0, result2))); + assert(e == 30); } /++ @@ -190,7 +495,8 @@ Unqual!(DeepElementType!Range) sumOfLog2s(Range)(Range r) /// version(mir_test) -@safe unittest +@safe pure +unittest { alias isNaN = x => x != x; @@ -205,3 +511,129 @@ version(mir_test) assert(isNaN(sumOfLog2s([-real.infinity]))); assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9); } + +/++ +Compute the sum of binary logarithms of the input range $(D r). +The error of this method is much smaller than with a naive sum of log. ++/ +Unqual!(DeepElementType!Range) sumOfLogs(Range)(Range r) + if (isFloatingPoint!(DeepElementType!Range)) +{ + import core.lifetime: move; + import mir.math.constant: LN2; + return typeof(return)(LN2) * sumOfLog2s(move(r)); +} + +/// +version(mir_test) +@safe pure +unittest +{ + import mir.math: LN2, approxEqual; + assert([ 0.25, 0.25, 0.25, 0.125 ].sumOfLogs.approxEqual(-9 * double(LN2))); +} + +/++ +Quickly computes factorial using extended +precision floating point type $(MREF mir,bignum,fp). + +Params: + count = number of product members + start = initial member value (optional) +Returns: `(count + start - 1)! / (start - 1)!` +Complexity: O(count) ++/ +auto factorial + (uint coefficientSize = 128) + (ulong count, ulong start = 1) + if (coefficientSize % (size_t.sizeof * 8) == 0 && coefficientSize >= (size_t.sizeof * 8)) + in (start) +{ + import mir.bignum.fp: Fp; + import mir.checkedint: mulu; + import mir.utility: _expect; + + alias R = Fp!coefficientSize; + R prod = 1LU; + + if (count) + { + ulong tempProd = start; + while(--count) + { + bool overflow; + ulong nextTempProd = mulu(tempProd, ++start, overflow); + if (_expect(!overflow, true)) + { + tempProd = nextTempProd; + continue; + } + else + { + prod *= R(tempProd); + tempProd = start; + } + } + prod *= R(tempProd); + } + + return prod; +} + +/// +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.bignum.fp: Fp; + import mir.math.common: approxEqual; + import mir.math.numeric: prod; + import mir.ndslice.topology: iota; + + static assert(is(typeof(factorial(33)) == Fp!128)); + static assert(is(typeof(factorial!256(33)) == Fp!256)); + static immutable double f33 = 8.68331761881188649551819440128e+36; + static assert(approxEqual(cast(double) factorial(33), f33)); + + assert(cast(double) factorial(0) == 1); + assert(cast(double) factorial(0, 100) == 1); + assert(cast(double) factorial(1, 100) == 100); + assert(approxEqual(cast(double) factorial(100, 1000), iota([100], 1000).prod!double)); +} + +/++ +Quickly computes binomial coefficient using extended +precision floating point type $(MREF mir,bignum,fp). + +Params: + n = number elements in the set + k = number elements in the subset +Returns: n choose k +Complexity: O(min(k, n - k)) ++/ +auto binomialCoefficient + (uint coefficientSize = 128) + (ulong n, uint k) + if (coefficientSize % (size_t.sizeof * 8) == 0 && coefficientSize >= (size_t.sizeof * 8)) + in (k <= n) +{ + if (k > n - k) + k = cast(uint)(n - k); + auto a = factorial!coefficientSize(k, n - k + 1); + auto b = factorial!coefficientSize(k); + return a / b; +} + +/// +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.bignum.fp: Fp; + import mir.math.common: approxEqual; + + static assert(is(typeof(binomialCoefficient(30, 18)) == Fp!128), typeof(binomialCoefficient(30, 18)).stringof); + static assert(cast(double) binomialCoefficient(30, 18) == 86493225); + + assert(approxEqual(cast(double) binomialCoefficient(100, 40), 1.374623414580281150126736972e+28)); +} diff --git a/source/mir/math/stat.d b/source/mir/math/stat.d new file mode 100644 index 00000000..72db9a0e --- /dev/null +++ b/source/mir/math/stat.d @@ -0,0 +1,3542 @@ +/++ +This module contains base statistical algorithms. + +Note that used specialized summing algorithms execute more primitive operations +than vanilla summation. Therefore, if in certain cases maximum speed is required +at expense of precision, one can use $(REF_ALTTEXT $(TT Summation.fast), Summation.fast, mir, math, sum)$(NBSP). + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) + +Authors: Shigeki Karita (original numir code), Ilia Ki, John Michael Hall + +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments + +Macros: +SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, math, $1)$(NBSP) +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) ++/ +module mir.math.stat; + +import core.lifetime: move; +import mir.internal.utility: isFloatingPoint; +import mir.math.common: fmamath; +import mir.math.sum; +import mir.ndslice.slice: Slice, SliceKind, hasAsSlice; +import mir.primitives; +import std.traits: Unqual, isArray, isMutable, isIterable, isIntegral, CommonType; + +/// +public import mir.math.sum: Summation; + +/// +package(mir) +template statType(T, bool checkComplex = true) +{ + import mir.internal.utility: isFloatingPoint; + + static if (isFloatingPoint!T) { + import std.traits: Unqual; + alias statType = Unqual!T; + } else static if (is(T : double)) { + alias statType = double; + } else static if (checkComplex) { + import mir.internal.utility: isComplex; + static if (isComplex!T) { + static if (__traits(getAliasThis, T).length == 1) + { + alias statType = .statType!(typeof(__traits(getMember, T, __traits(getAliasThis, T)[0]))); + } + else + { + alias statType = Unqual!T; + } + } else { + static assert(0, "statType: type " ~ T.stringof ~ " must be convertible to a complex floating point type"); + } + } else { + static assert(0, "statType: type " ~ T.stringof ~ " must be convertible to a floating point type"); + } +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static assert(is(statType!int == double)); + static assert(is(statType!uint == double)); + static assert(is(statType!double == double)); + static assert(is(statType!float == float)); + static assert(is(statType!real == real)); + + static assert(is(statType!(const(int)) == double)); + static assert(is(statType!(immutable(int)) == double)); + static assert(is(statType!(const(double)) == double)); + static assert(is(statType!(immutable(double)) == double)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.complex: Complex; + + static assert(is(statType!(Complex!float) == Complex!float)); + static assert(is(statType!(Complex!double) == Complex!double)); + static assert(is(statType!(Complex!real) == Complex!real)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static struct Foo { + float x; + alias x this; + } + + static assert(is(statType!Foo == double)); // note: this is not float +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.complex; + static struct Foo { + Complex!float x; + alias x this; + } + + static assert(is(statType!Foo == Complex!float)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static struct Foo { + double x; + alias x this; + } + + static assert(is(statType!Foo == double)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.complex; + static struct Foo { + Complex!double x; + alias x this; + } + + static assert(is(statType!Foo == Complex!double)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static struct Foo { + real x; + alias x this; + } + + static assert(is(statType!Foo == double)); // note: this is not real +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.complex; + static struct Foo { + Complex!real x; + alias x this; + } + + static assert(is(statType!Foo == Complex!real)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static struct Foo { + int x; + alias x this; + } + + static assert(is(statType!Foo == double)); // note: this is not ints +} + +/// +package(mir) +template meanType(T) +{ + import mir.math.sum: sumType; + + alias U = sumType!T; + + static if (__traits(compiles, { + auto temp = U.init + U.init; + auto a = temp / 2; + temp += U.init; + })) { + alias V = typeof((U.init + U.init) / 2); + alias meanType = statType!V; + } else { + static assert(0, "meanType: Can't calculate mean of elements of type " ~ U.stringof); + } +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static assert(is(meanType!(int[]) == double)); + static assert(is(meanType!(double[]) == double)); + static assert(is(meanType!(float[]) == float)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.complex; + static assert(is(meanType!(Complex!float[]) == Complex!float)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static struct Foo { + float x; + alias x this; + } + + static assert(is(meanType!(Foo[]) == float)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.complex; + static struct Foo { + Complex!float x; + alias x this; + } + + static assert(is(meanType!(Foo[]) == Complex!float)); +} + +/++ +Output range for mean. ++/ +struct MeanAccumulator(T, Summation summation) +{ + /// + size_t count; + /// + Summator!(T, summation) summator; + + /// + F mean(F = T)() const @safe @property pure nothrow @nogc + { + return cast(F) summator.sum / cast(F) count; + } + + /// + F sum(F = T)() const @safe @property pure nothrow @nogc + { + return cast(F) summator.sum; + } + + /// + void put(Range)(Range r) + if (isIterable!Range) + { + static if (hasShape!Range) + { + count += r.elementCount; + summator.put(r); + } + else + { + foreach(x; r) + { + count++; + summator.put(x); + } + } + } + + /// + void put()(T x) + { + count++; + summator.put(x); + } + + /// + void put(F = T)(MeanAccumulator!(F, summation) m) + { + count += m.count; + summator.put(cast(T) m.summator); + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + MeanAccumulator!(double, Summation.pairwise) x; + x.put([0.0, 1, 2, 3, 4].sliced); + assert(x.mean == 2); + x.put(5); + assert(x.mean == 2.5); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + MeanAccumulator!(float, Summation.pairwise) x; + x.put([0, 1, 2, 3, 4].sliced); + assert(x.mean == 2); + assert(x.sum == 10); + x.put(5); + assert(x.mean == 2.5); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + double[] x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25]; + double[] y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; + + MeanAccumulator!(float, Summation.pairwise) m0; + m0.put(x); + MeanAccumulator!(float, Summation.pairwise) m1; + m1.put(y); + m0.put(m1); + assert(m0.mean == 29.25 / 12); +} + +/++ +Computes the mean of the input. + +By default, if `F` is not floating point type or complex type, then the result +will have a `double` type if `F` is implicitly convertible to a floating point +type or a type for which `isComplex!F` is true. + +Params: + F = controls type of output + summation = algorithm for calculating sums (default: Summation.appropriate) +Returns: + The mean of all the elements in the input, must be floating point or complex type + +See_also: + $(SUBREF sum, Summation) ++/ +template mean(F, Summation summation = Summation.appropriate) +{ + /++ + Params: + r = range, must be finite iterable + +/ + @fmamath meanType!F mean(Range)(Range r) + if (isIterable!Range) + { + alias G = typeof(return); + MeanAccumulator!(G, ResolveSummationType!(summation, Range, G)) mean; + mean.put(r.move); + return mean.mean; + } + + /++ + Params: + ar = values + +/ + @fmamath meanType!F mean(scope const F[] ar...) + { + alias G = typeof(return); + MeanAccumulator!(G, ResolveSummationType!(summation, const(G)[], G)) mean; + mean.put(ar); + return mean.mean; + } +} + +/// ditto +template mean(Summation summation = Summation.appropriate) +{ + /++ + Params: + r = range, must be finite iterable + +/ + @fmamath meanType!Range mean(Range)(Range r) + if (isIterable!Range) + { + alias F = typeof(return); + return .mean!(F, summation)(r.move); + } + + /++ + Params: + ar = values + +/ + @fmamath meanType!T mean(T)(scope const T[] ar...) + { + alias F = typeof(return); + return .mean!(F, summation)(ar); + } +} + +/// ditto +template mean(F, string summation) +{ + mixin("alias mean = .mean!(F, Summation." ~ summation ~ ");"); +} + +/// ditto +template mean(string summation) +{ + mixin("alias mean = .mean!(Summation." ~ summation ~ ");"); +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + import mir.complex; + alias C = Complex!double; + + assert(mean([1.0, 2, 3]) == 2); + assert(mean([C(1, 3), C(2), C(3)]) == C(2, 1)); + + assert(mean!float([0, 1, 2, 3, 4, 5].sliced(3, 2)) == 2.5); + + static assert(is(typeof(mean!float([1, 2, 3])) == float)); +} + +/// Mean of vector +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + assert(x.mean == 29.25 / 12); +} + +/// Mean of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + + assert(x.mean == 29.25 / 12); +} + +/// Column mean of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: alongDim, byDim, map; + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + auto result = [1, 4.25, 3.25, 1.5, 2.5, 2.125]; + + // Use byDim or alongDim with map to compute mean of row/column. + assert(x.byDim!1.map!mean.all!approxEqual(result)); + assert(x.alongDim!0.map!mean.all!approxEqual(result)); + + // FIXME + // Without using map, computes the mean of the whole slice + // assert(x.byDim!1.mean == x.sliced.mean); + // assert(x.alongDim!0.mean == x.sliced.mean); +} + +/// Can also set algorithm or output type +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: repeat; + + //Set sum algorithm or output type + + auto a = [1, 1e100, 1, -1e100].sliced; + + auto x = a * 10_000; + + assert(x.mean!"kbn" == 20_000 / 4); + assert(x.mean!"kb2" == 20_000 / 4); + assert(x.mean!"precise" == 20_000 / 4); + assert(x.mean!(double, "precise") == 20_000.0 / 4); + + auto y = uint.max.repeat(3); + assert(y.mean!ulong == 12884901885 / 3); +} + +/++ +For integral slices, pass output type as template parameter to ensure output +type is correct. ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0, 1, 1, 2, 4, 4, + 2, 7, 5, 1, 2, 0].sliced; + + auto y = x.mean; + assert(y.approxEqual(29.0 / 12, 1.0e-10)); + static assert(is(typeof(y) == double)); + + assert(x.mean!float.approxEqual(29f / 12, 1.0e-10)); +} + +/++ +Mean works for complex numbers and other user-defined types (provided they +can be converted to a floating point or complex type) ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.complex.math: approxEqual; + import mir.ndslice.slice: sliced; + import mir.complex; + alias C = Complex!double; + + auto x = [C(1.0, 2), C(2, 3), C(3, 4), C(4, 5)].sliced; + assert(x.mean.approxEqual(C(2.5, 3.5))); +} + +/// Compute mean tensors along specified dimention of tensors +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice: alongDim, iota, as, map; + /++ + [[0,1,2], + [3,4,5]] + +/ + auto x = iota(2, 3).as!double; + assert(x.mean == (5.0 / 2.0)); + + auto m0 = [(0.0+3.0)/2.0, (1.0+4.0)/2.0, (2.0+5.0)/2.0]; + assert(x.alongDim!0.map!mean == m0); + assert(x.alongDim!(-2).map!mean == m0); + + auto m1 = [(0.0+1.0+2.0)/3.0, (3.0+4.0+5.0)/3.0]; + assert(x.alongDim!1.map!mean == m1); + assert(x.alongDim!(-1).map!mean == m1); + + assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!mean == iota([3, 4, 5], 3 * 4 * 5 / 2)); +} + +/// Arbitrary mean +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + assert(mean(1.0, 2, 3) == 2); + assert(mean!float(1, 2, 3) == 2); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + assert([1.0, 2, 3, 4].mean == 2.5); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.topology: iota, alongDim, map; + + auto x = iota([2, 2], 1); + auto y = x.alongDim!1.map!mean; + assert(y.all!approxEqual([1.5, 3.5])); + static assert(is(meanType!(typeof(y)) == double)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.ndslice.slice: sliced; + + static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; + + assert(x.sliced.mean == 29.25 / 12); + assert(x.sliced.mean!float == 29.25 / 12); +} + +/// +package(mir) +template hmeanType(T) +{ + import mir.math.sum: sumType; + + alias U = sumType!T; + + static if (__traits(compiles, { + U t = U.init + cast(U) 1; //added for when U.init = 0 + auto temp = cast(U) 1 / t + cast(U) 1 / t; + })) { + alias V = typeof(cast(U) 1 / ((cast(U) 1 / U.init + cast(U) 1 / U.init) / cast(U) 2)); + alias hmeanType = statType!V; + } else { + static assert(0, "hmeanType: Can't calculate hmean of elements of type " ~ U.stringof); + } +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.complex; + static assert(is(hmeanType!(int[]) == double)); + static assert(is(hmeanType!(double[]) == double)); + static assert(is(hmeanType!(float[]) == float)); + static assert(is(hmeanType!(Complex!float[]) == Complex!float)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.complex; + static struct Foo { + float x; + alias x this; + } + + static struct Bar { + Complex!float x; + alias x this; + } + + static assert(is(hmeanType!(Foo[]) == float)); + static assert(is(hmeanType!(Bar[]) == Complex!float)); +} + +/++ +Computes the harmonic mean of the input. + +By default, if `F` is not floating point type or complex type, then the result +will have a `double` type if `F` is implicitly convertible to a floating point +type or a type for which `isComplex!F` is true. + +Params: + F = controls type of output + summation = algorithm for calculating sums (default: Summation.appropriate) +Returns: + harmonic mean of all the elements of the input, must be floating point or complex type + +See_also: + $(SUBREF sum, Summation) ++/ +template hmean(F, Summation summation = Summation.appropriate) +{ + /++ + Params: + r = range + +/ + @fmamath hmeanType!F hmean(Range)(Range r) + if (isIterable!Range) + { + import mir.ndslice.topology: map; + + alias G = typeof(return); + auto numerator = cast(G) 1; + + static if (summation == Summation.fast && __traits(compiles, r.move.map!"numerator / a")) + { + return numerator / r.move.map!"numerator / a".mean!(G, summation); + } + else + { + MeanAccumulator!(G, ResolveSummationType!(summation, Range, G)) imean; + foreach (e; r) + imean.put(numerator / e); + return numerator / imean.mean; + } + } + + /++ + Params: + ar = values + +/ + @fmamath hmeanType!F hmean(scope const F[] ar...) + { + alias G = typeof(return); + + auto numerator = cast(G) 1; + + static if (summation == Summation.fast && __traits(compiles, ar.map!"numerator / a")) + { + return numerator / ar.map!"numerator / a".mean!(G, summation); + } + else + { + MeanAccumulator!(G, ResolveSummationType!(summation, const(G)[], G)) imean; + foreach (e; ar) + imean.put(numerator / e); + return numerator / imean.mean; + } + } +} + +/// ditto +template hmean(Summation summation = Summation.appropriate) +{ + /++ + Params: + r = range + +/ + @fmamath hmeanType!Range hmean(Range)(Range r) + if (isIterable!Range) + { + alias F = typeof(return); + return .hmean!(F, summation)(r.move); + } + + /++ + Params: + ar = values + +/ + @fmamath hmeanType!T hmean(T)(scope const T[] ar...) + { + alias F = typeof(return); + return .hmean!(F, summation)(ar); + } +} + +/// ditto +template hmean(F, string summation) +{ + mixin("alias hmean = .hmean!(F, Summation." ~ summation ~ ");"); +} + +/// ditto +template hmean(string summation) +{ + mixin("alias hmean = .hmean!(Summation." ~ summation ~ ");"); +} + +/// Harmonic mean of vector +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [20.0, 100.0, 2000.0, 10.0, 5.0, 2.0].sliced; + + assert(x.hmean.approxEqual(6.97269)); +} + +/// Harmonic mean of matrix +version(mir_test) +pure @safe +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.fuse: fuse; + + auto x = [ + [20.0, 100.0, 2000.0], + [10.0, 5.0, 2.0] + ].fuse; + + assert(x.hmean.approxEqual(6.97269)); +} + +/// Column harmonic mean of matrix +version(mir_test) +pure @safe +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice: fuse; + import mir.ndslice.topology: alongDim, byDim, map; + + auto x = [ + [20.0, 100.0, 2000.0], + [ 10.0, 5.0, 2.0] + ].fuse; + + auto y = [13.33333, 9.52381, 3.996004]; + + // Use byDim or alongDim with map to compute mean of row/column. + assert(x.byDim!1.map!hmean.all!approxEqual(y)); + assert(x.alongDim!0.map!hmean.all!approxEqual(y)); +} + +/// Can also pass arguments to hmean +version(mir_test) +pure @safe nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.topology: repeat; + import mir.ndslice.slice: sliced; + + //Set sum algorithm or output type + auto x = [1, 1e-100, 1, -1e-100].sliced; + + assert(x.hmean!"kb2".approxEqual(2)); + assert(x.hmean!"precise".approxEqual(2)); + assert(x.hmean!(double, "precise").approxEqual(2)); + + //Provide the summation type + assert(float.max.repeat(3).hmean!double.approxEqual(float.max)); +} + +/++ +For integral slices, pass output type as template parameter to ensure output +type is correct. ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [20, 100, 2000, 10, 5, 2].sliced; + + auto y = x.hmean; + + assert(y.approxEqual(6.97269)); + static assert(is(typeof(y) == double)); + + assert(x.hmean!float.approxEqual(6.97269)); +} + +/++ +hmean works for complex numbers and other user-defined types (provided they +can be converted to a floating point or complex type) ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.complex.math: approxEqual; + import mir.ndslice.slice: sliced; + import mir.complex; + alias C = Complex!double; + + auto x = [C(1, 2), C(2, 3), C(3, 4), C(4, 5)].sliced; + assert(x.hmean.approxEqual(C(1.97110904, 3.14849332))); +} + +/// Arbitrary harmonic mean +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = hmean(20.0, 100, 2000, 10, 5, 2); + assert(x.approxEqual(6.97269)); + + auto y = hmean!float(20, 100, 2000, 10, 5, 2); + assert(y.approxEqual(6.97269)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + static immutable x = [20.0, 100.0, 2000.0, 10.0, 5.0, 2.0]; + + assert(x.sliced.hmean.approxEqual(6.97269)); + assert(x.sliced.hmean!float.approxEqual(6.97269)); +} + +private +F nthroot(F)(in F x, in size_t n) + if (isFloatingPoint!F) +{ + import mir.math.common: sqrt, pow; + + if (n > 2) { + return pow(x, cast(F) 1 / cast(F) n); + } else if (n == 2) { + return sqrt(x); + } else if (n == 1) { + return x; + } else { + return cast(F) 1; + } +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + + assert(nthroot(9.0, 0).approxEqual(1)); + assert(nthroot(9.0, 1).approxEqual(9)); + assert(nthroot(9.0, 2).approxEqual(3)); + assert(nthroot(9.5, 2).approxEqual(3.08220700)); + assert(nthroot(9.0, 3).approxEqual(2.08008382)); +} + +/++ +Output range for gmean. ++/ +struct GMeanAccumulator(T) + if (isMutable!T && isFloatingPoint!T) +{ + import mir.math.numeric: ProdAccumulator; + + /// + size_t count; + /// + ProdAccumulator!T prodAccumulator; + + /// + F gmean(F = T)() const @property + if (isFloatingPoint!F) + { + import mir.math.common: exp2; + + return nthroot(cast(F) prodAccumulator.mantissa, count) * exp2(cast(F) prodAccumulator.exp / count); + } + + /// + void put(Range)(Range r) + if (isIterable!Range) + { + static if (hasShape!Range) + { + count += r.elementCount; + prodAccumulator.put(r); + } + else + { + foreach(x; r) + { + count++; + prodAccumulator.put(x); + } + } + } + + /// + void put()(T x) + { + count++; + prodAccumulator.put(x); + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + GMeanAccumulator!double x; + x.put([1.0, 2, 3, 4].sliced); + assert(x.gmean.approxEqual(2.21336384)); + x.put(5); + assert(x.gmean.approxEqual(2.60517108)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + GMeanAccumulator!float x; + x.put([1, 2, 3, 4].sliced); + assert(x.gmean.approxEqual(2.21336384)); + x.put(5); + assert(x.gmean.approxEqual(2.60517108)); +} + +/// +package(mir) +template gmeanType(T) +{ + import mir.math.numeric: prodType; + + alias U = prodType!T; + + static if (__traits(compiles, { + auto temp = U.init * U.init; + auto a = nthroot(temp, 2); + temp *= U.init; + })) { + alias V = typeof(nthroot(U.init * U.init, 2)); + alias gmeanType = statType!(V, false); + } else { + static assert(0, "gmeanType: Can't calculate gmean of elements of type " ~ U.stringof); + } +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static assert(is(gmeanType!int == double)); + static assert(is(gmeanType!double == double)); + static assert(is(gmeanType!float == float)); + static assert(is(gmeanType!(int[]) == double)); + static assert(is(gmeanType!(double[]) == double)); + static assert(is(gmeanType!(float[]) == float)); +} + +/++ +Computes the geometric average of the input. + +By default, if `F` is not floating point type, then the result will have a +`double` type if `F` is implicitly convertible to a floating point type. + +Params: + r = range, must be finite iterable +Returns: + The geometric average of all the elements in the input, must be floating point type + +See_also: + $(SUBREF numeric, prod) ++/ +@fmamath gmeanType!F gmean(F, Range)(Range r) + if (isFloatingPoint!F && isIterable!Range) +{ + alias G = typeof(return); + GMeanAccumulator!G gmean; + gmean.put(r.move); + return gmean.gmean; +} + +/// ditto +@fmamath gmeanType!Range gmean(Range)(Range r) + if (isIterable!Range) +{ + alias G = typeof(return); + return .gmean!(G, Range)(r.move); +} + +/++ +Params: + ar = values ++/ +@fmamath gmeanType!F gmean(F)(scope const F[] ar...) + if (isFloatingPoint!F) +{ + alias G = typeof(return); + GMeanAccumulator!G gmean; + gmean.put(ar); + return gmean.gmean; +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + assert(gmean([1.0, 2, 3]).approxEqual(1.81712059)); + + assert(gmean!float([1, 2, 3, 4, 5, 6].sliced(3, 2)).approxEqual(2.99379516)); + + static assert(is(typeof(gmean!float([1, 2, 3])) == float)); +} + +/// Geometric mean of vector +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [3.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 2.0].sliced; + + assert(x.gmean.approxEqual(2.36178395)); +} + +/// Geometric mean of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.fuse: fuse; + + auto x = [ + [3.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 2.0] + ].fuse; + + assert(x.gmean.approxEqual(2.36178395)); +} + +/// Column gmean of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: alongDim, byDim, map; + + auto x = [ + [3.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 2.0] + ].fuse; + auto result = [2.44948974, 2.73861278, 2.73861278, 1.41421356, 2.29128784, 2.91547594]; + + // Use byDim or alongDim with map to compute mean of row/column. + assert(x.byDim!1.map!gmean.all!approxEqual(result)); + assert(x.alongDim!0.map!gmean.all!approxEqual(result)); + + // FIXME + // Without using map, computes the mean of the whole slice + // assert(x.byDim!1.gmean.all!approxEqual(result)); + // assert(x.alongDim!0.gmean.all!approxEqual(result)); +} + +/// Can also set output type +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: repeat; + + auto x = [5120.0, 7340032, 32, 3758096384].sliced; + + assert(x.gmean!float.approxEqual(259281.45295212)); + + auto y = uint.max.repeat(2); + assert(y.gmean!float.approxEqual(cast(float) uint.max)); +} + +/++ +For integral slices, pass output type as template parameter to ensure output +type is correct. ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [5, 1, 1, 2, 4, 4, + 2, 7, 5, 1, 2, 10].sliced; + + auto y = x.gmean; + static assert(is(typeof(y) == double)); + + assert(x.gmean!float.approxEqual(2.79160522)); +} + +/// gean works for user-defined types, provided the nth root can be taken for them +version(mir_test) +@safe pure nothrow +unittest +{ + static struct Foo { + float x; + alias x this; + } + + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [Foo(1.0), Foo(2.0), Foo(3.0)].sliced; + assert(x.gmean.approxEqual(1.81712059)); +} + +/// Compute gmean tensors along specified dimention of tensors +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: alongDim, iota, map; + + auto x = [ + [1.0, 2, 3], + [4.0, 5, 6] + ].fuse; + + assert(x.gmean.approxEqual(2.99379516)); + + auto result0 = [2.0, 3.16227766, 4.24264069]; + assert(x.alongDim!0.map!gmean.all!approxEqual(result0)); + assert(x.alongDim!(-2).map!gmean.all!approxEqual(result0)); + + auto result1 = [1.81712059, 4.93242414]; + assert(x.alongDim!1.map!gmean.all!approxEqual(result1)); + assert(x.alongDim!(-1).map!gmean.all!approxEqual(result1)); + + auto y = [ + [ + [1.0, 2, 3], + [4.0, 5, 6] + ], [ + [7.0, 8, 9], + [10.0, 9, 10] + ] + ].fuse; + + auto result3 = [ + [2.64575131, 4.0, 5.19615242], + [6.32455532, 6.70820393, 7.74596669] + ]; + assert(y.alongDim!0.map!gmean.all!approxEqual(result3)); +} + +/// Arbitrary gmean +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + + assert(gmean(1.0, 2, 3).approxEqual(1.81712059)); + assert(gmean!float(1, 2, 3).approxEqual(1.81712059)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + + assert([1.0, 2, 3, 4].gmean.approxEqual(2.21336384)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + + assert(gmean([1, 2, 3]).approxEqual(1.81712059)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + static immutable x = [3.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 2.0]; + + assert(x.sliced.gmean.approxEqual(2.36178395)); + assert(x.sliced.gmean!float.approxEqual(2.36178395)); +} + +/++ +Computes the median of `slice`. + +By default, if `F` is not floating point type or complex type, then the result +will have a `double` type if `F` is implicitly convertible to a floating point +type or a type for which `isComplex!F` is true. + +Can also pass a boolean variable, `allowModify`, that allows the input slice to +be modified. By default, a reference-counted copy is made. + +Params: + F = output type + allowModify = Allows the input slice to be modified, default is false +Returns: + the median of the slice + +See_also: + $(SUBREF stat, mean) ++/ +template median(F, bool allowModify = false) +{ + /++ + Params: + slice = slice + +/ + @nogc + meanType!F median(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + { + static assert (!allowModify || + isMutable!(slice.DeepElement), + "allowModify must be false or the input must be mutable"); + alias G = typeof(return); + size_t len = slice.elementCount; + assert(len > 0, "median: slice must have length greater than zero"); + + import mir.ndslice.topology: as, flattened; + + static if (!allowModify) { + import mir.ndslice.allocation: rcslice; + + if (len > 2) { + auto view = slice.lightScope; + auto val = view.as!(Unqual!(slice.DeepElement)).rcslice; + auto temp = val.lightScope.flattened; + return .median!(G, true)(temp); + } else { + return mean!G(slice); + } + } else { + import mir.ndslice.sorting: partitionAt; + + auto temp = slice.flattened; + + if (len > 5) { + size_t half_n = len / 2; + partitionAt(temp, half_n); + if (len % 2 == 1) { + return cast(G) temp[half_n]; + } else { + //move largest value in first half of slice to half_n - 1 + partitionAt(temp[0 .. half_n], half_n - 1); + return (temp[half_n - 1] + temp[half_n]) / cast(G) 2; + } + } else { + return smallMedianImpl!(G)(temp); + } + } + } +} + +/// ditto +template median(bool allowModify = false) +{ + /// ditto + meanType!(Slice!(Iterator, N, kind)) + median(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + { + static assert (!allowModify || + isMutable!(DeepElementType!(Slice!(Iterator, N, kind))), + "allowModify must be false or the input must be mutable"); + alias F = typeof(return); + return .median!(F, allowModify)(slice.move); + } +} + +/++ +Params: + ar = array ++/ +meanType!(T[]) median(T)(scope const T[] ar...) +{ + import mir.ndslice.slice: sliced; + + alias F = typeof(return); + return median!(F, false)(ar.sliced); +} + +/++ +Params: + withAsSlice = input that satisfies hasAsSlice ++/ +auto median(T)(T withAsSlice) + if (hasAsSlice!T) +{ + return median(withAsSlice.asSlice); +} + +/// Median of vector +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5].sliced; + assert(x0.median == 5); + + auto x1 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced; + assert(x1.median == 5); +} + +/// Median of dynamic array +version(mir_test) +@safe pure nothrow +unittest +{ + auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5]; + assert(x0.median == 5); + + auto x1 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10]; + assert(x1.median == 5); +} + +/// Median of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + + auto x0 = [ + [9.0, 1, 0, 2, 3], + [4.0, 6, 8, 7, 10] + ].fuse; + + assert(x0.median == 5); +} + +/// Row median of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.fuse: fuse; + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: alongDim, byDim, map; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + + auto result = [1.75, 1.75].sliced; + + // Use byDim or alongDim with map to compute median of row/column. + assert(x.byDim!0.map!median.all!approxEqual(result)); + assert(x.alongDim!1.map!median.all!approxEqual(result)); +} + +/// Can allow original slice to be modified or set output type +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + auto x0 = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5].sliced; + assert(x0.median!true == 5); + + auto x1 = [9, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced; + assert(x1.median!(float, true) == 5); +} + +/// Arbitrary median +version(mir_test) +@safe pure nothrow +unittest +{ + assert(median(0, 1, 2, 3, 4) == 2); +} + +// @nogc test +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.ndslice.slice: sliced; + + static immutable x = [9.0, 1, 0, 2, 3]; + assert(x.sliced.median == 2); +} + +// withAsSlice test +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + import mir.rc.array: RCArray; + + static immutable a = [9.0, 1, 0, 2, 3, 4, 6, 8, 7, 10, 5]; + + auto x = RCArray!double(11); + foreach(i, ref e; x) + e = a[i]; + + assert(x.median.approxEqual(5)); +} + +/++ +For integral slices, can pass output type as template parameter to ensure output +type is correct ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + auto x = [9, 1, 0, 2, 3, 4, 6, 8, 7, 10].sliced; + assert(x.median!float == 5f); + + auto y = x.median; + assert(y == 5.0); + static assert(is(typeof(y) == double)); +} + +// additional logic tests +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [3, 3, 2, 0, 2, 0].sliced; + assert(x.median!float.approxEqual(2)); + + x[] = [2, 2, 4, 0, 4, 3]; + assert(x.median!float.approxEqual(2.5)); + x[] = [1, 4, 5, 4, 4, 3]; + assert(x.median!float.approxEqual(4)); + x[] = [1, 5, 3, 5, 2, 2]; + assert(x.median!float.approxEqual(2.5)); + x[] = [4, 3, 2, 1, 4, 5]; + assert(x.median!float.approxEqual(3.5)); + x[] = [4, 5, 3, 5, 5, 4]; + assert(x.median!float.approxEqual(4.5)); + x[] = [3, 3, 3, 0, 0, 1]; + assert(x.median!float.approxEqual(2)); + x[] = [4, 2, 2, 1, 2, 5]; + assert(x.median!float.approxEqual(2)); + x[] = [2, 3, 1, 4, 5, 5]; + assert(x.median!float.approxEqual(3.5)); + x[] = [1, 1, 4, 5, 5, 5]; + assert(x.median!float.approxEqual(4.5)); + x[] = [2, 4, 0, 5, 1, 0]; + assert(x.median!float.approxEqual(1.5)); + x[] = [3, 5, 2, 5, 4, 2]; + assert(x.median!float.approxEqual(3.5)); + x[] = [3, 5, 4, 1, 4, 3]; + assert(x.median!float.approxEqual(3.5)); + x[] = [4, 2, 0, 3, 1, 3]; + assert(x.median!float.approxEqual(2.5)); + x[] = [100, 4, 5, 0, 5, 1]; + assert(x.median!float.approxEqual(4.5)); + x[] = [100, 5, 4, 0, 5, 1]; + assert(x.median!float.approxEqual(4.5)); + x[] = [100, 5, 4, 0, 1, 5]; + assert(x.median!float.approxEqual(4.5)); + x[] = [4, 5, 100, 1, 5, 0]; + assert(x.median!float.approxEqual(4.5)); + x[] = [0, 1, 2, 2, 3, 4]; + assert(x.median!float.approxEqual(2)); + x[] = [0, 2, 2, 3, 4, 5]; + assert(x.median!float.approxEqual(2.5)); +} + +// smallMedianImpl tests +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x0 = [9.0, 1, 0, 2, 3].sliced; + assert(x0.median.approxEqual(2)); + + auto x1 = [9.0, 1, 0, 2].sliced; + assert(x1.median.approxEqual(1.5)); + + auto x2 = [9.0, 0, 1].sliced; + assert(x2.median.approxEqual(1)); + + auto x3 = [1.0, 0].sliced; + assert(x3.median.approxEqual(0.5)); + + auto x4 = [1.0].sliced; + assert(x4.median.approxEqual(1)); +} + +// Check issue #328 fixed +version(mir_test) +@safe pure nothrow +unittest { + import mir.ndslice.topology: iota; + + auto x = iota(18); + auto y = median(x); + assert(y == 8.5); +} + +private pure @trusted nothrow @nogc +F smallMedianImpl(F, Iterator)(Slice!Iterator slice) +{ + size_t n = slice.elementCount; + + assert(n > 0, "smallMedianImpl: slice must have elementCount greater than 0"); + assert(n <= 5, "smallMedianImpl: slice must have elementCount of 5 or less"); + + import mir.functional: naryFun; + import mir.ndslice.sorting: medianOf; + import mir.utility: swapStars; + + auto sliceI0 = slice._iterator; + + if (n == 1) { + return cast(F) *sliceI0; + } + + auto sliceI1 = sliceI0; + ++sliceI1; + + if (n > 2) { + auto sliceI2 = sliceI1; + ++sliceI2; + alias less = naryFun!("a < b"); + + if (n == 3) { + medianOf!less(sliceI0, sliceI1, sliceI2); + return cast(F) *sliceI1; + } else { + auto sliceI3 = sliceI2; + ++sliceI3; + if (n == 4) { + // Put min in slice[0], lower median in slice[1] + medianOf!less(sliceI0, sliceI1, sliceI2, sliceI3); + // Ensure slice[2] < slice[3] + medianOf!less(sliceI2, sliceI3); + return cast(F) (*sliceI1 + *sliceI2) / cast(F) 2; + } else { + auto sliceI4 = sliceI3; + ++sliceI4; + medianOf!less(sliceI0, sliceI1, sliceI2, sliceI3, sliceI4); + return cast(F) *sliceI2; + } + } + } else { + return cast(F) (*sliceI0 + *sliceI1) / cast(F) 2; + } +} + +// smallMedianImpl tests +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x0 = [9.0, 1, 0, 2, 3].sliced; + assert(x0.smallMedianImpl!double.approxEqual(2)); + + auto x1 = [9.0, 1, 0, 2].sliced; + assert(x1.smallMedianImpl!double.approxEqual(1.5)); + + auto x2 = [9.0, 0, 1].sliced; + assert(x2.smallMedianImpl!double.approxEqual(1)); + + auto x3 = [1.0, 0].sliced; + assert(x3.smallMedianImpl!double.approxEqual(0.5)); + + auto x4 = [1.0].sliced; + assert(x4.smallMedianImpl!double.approxEqual(1)); + + auto x5 = [2.0, 1, 0, 9].sliced; + assert(x5.smallMedianImpl!double.approxEqual(1.5)); + + auto x6 = [1.0, 2, 0, 9].sliced; + assert(x6.smallMedianImpl!double.approxEqual(1.5)); + + auto x7 = [1.0, 0, 9, 2].sliced; + assert(x7.smallMedianImpl!double.approxEqual(1.5)); +} + +/++ +Centers `slice`, which must be a finite iterable. + +By default, `slice` is centered by the mean. A custom function may also be +provided using `centralTendency`. + +Returns: + The elements in the slice with the average subtracted from them. ++/ +template center(alias centralTendency = mean!(Summation.appropriate)) +{ + import mir.ndslice.slice: isConvertibleToSlice, isSlice, Slice, SliceKind; + /++ + Params: + slice = slice + +/ + auto center(Iterator, size_t N, SliceKind kind)( + Slice!(Iterator, N, kind) slice) + { + import core.lifetime: move; + import mir.ndslice.internal: LeftOp, ImplicitlyUnqual; + import mir.ndslice.topology: vmap; + + auto m = centralTendency(slice.lightScope); + alias T = typeof(m); + return slice.move.vmap(LeftOp!("-", ImplicitlyUnqual!T)(m)); + } + + /// ditto + auto center(T)(T x) + if (isConvertibleToSlice!T && !isSlice!T) + { + import mir.ndslice.slice: toSlice; + return center(x.toSlice); + } +} + +/// Center vector +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [1.0, 2, 3, 4, 5, 6].sliced; + assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); + + // Can center using different functions + assert(x.center!hmean.all!approxEqual([-1.44898, -0.44898, 0.55102, 1.55102, 2.55102, 3.55102])); + assert(x.center!gmean.all!approxEqual([-1.99379516, -0.99379516, 0.00620483, 1.00620483, 2.00620483, 3.00620483])); + assert(x.center!median.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); + + // center operates lazily, if original slice is changed, then + auto y = x.center; + assert(y.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); + x[0]++; + assert(y.all!approxEqual([-1.5, -1.5, -0.5, 0.5, 1.5, 2.5])); +} + +/// Example of lazy behavior of center +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.allocation: slice; + import mir.ndslice.slice: sliced; + + auto x = [1.0, 2, 3, 4, 5, 6].sliced; + auto y = x.center; + auto z = x.center.slice; + assert(y.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); + x[0]++; + // y changes, while z does not + assert(y.all!approxEqual([-1.5, -1.5, -0.5, 0.5, 1.5, 2.5])); + assert(z.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); +} + +/// Center dynamic array +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + + auto x = [1.0, 2, 3, 4, 5, 6]; + assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); +} + +/// Center matrix +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice: fuse; + + auto x = [ + [0.0, 1, 2], + [3.0, 4, 5] + ].fuse; + + auto y = [ + [-2.5, -1.5, -0.5], + [ 0.5, 1.5, 2.5] + ].fuse; + + assert(x.center.all!approxEqual(y)); +} + +/// Column center matrix +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all, equal; + import mir.math.common: approxEqual; + import mir.ndslice: fuse; + import mir.ndslice.topology: alongDim, byDim, map; + + auto x = [ + [20.0, 100.0, 2000.0], + [10.0, 5.0, 2.0] + ].fuse; + + auto result = [ + [ 5.0, 47.5, 999], + [-5.0, -47.5, -999] + ].fuse; + + // Use byDim with map to compute average of row/column. + auto xCenterByDim = x.byDim!1.map!center; + auto resultByDim = result.byDim!1; + assert(xCenterByDim.equal!(equal!approxEqual)(resultByDim)); + + auto xCenterAlongDim = x.alongDim!0.map!center; + auto resultAlongDim = result.alongDim!0; + assert(xCenterByDim.equal!(equal!approxEqual)(resultAlongDim)); +} + +/// Can also pass arguments to average function used by center +version(mir_test) +pure @safe nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + //Set sum algorithm or output type + auto a = [1, 1e100, 1, -1e100]; + + auto x = a.sliced * 10_000; + + //Due to Floating Point precision, subtracting the mean from the second + //and fourth numbers in `x` does not change the value of the result + auto result = [5000, 1e104, 5000, -1e104].sliced; + + assert(x.center!(mean!"kbn") == result); + assert(x.center!(mean!"kb2") == result); + assert(x.center!(mean!"precise") == result); +} + +/++ +Passing a centered input to `variance` or `standardDeviation` with the +`assumeZeroMean` algorithm is equivalent to calculating `variance` or +`standardDeviation` on the original input. ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [1.0, 2, 3, 4, 5, 6].sliced; + assert(x.center.variance!"assumeZeroMean".approxEqual(x.variance)); + assert(x.center.standardDeviation!"assumeZeroMean".approxEqual(x.standardDeviation)); +} + +// dynamic array test +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + + double[] x = [1.0, 2, 3, 4, 5, 6]; + + assert(x.center.all!approxEqual([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5])); +} + +// withAsSlice test +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.rc.array: RCArray; + + static immutable a = [1.0, 2, 3, 4, 5, 6]; + static immutable result = [-2.5, -1.5, -0.5, 0.5, 1.5, 2.5]; + + auto x = RCArray!double(6); + foreach(i, ref e; x) + e = a[i]; + + assert(x.center.all!approxEqual(result)); +} + +/++ +Output range that applies function `fun` to each input before summing ++/ +struct MapSummator(alias fun, T, Summation summation) + if(isMutable!T) +{ + /// + Summator!(T, summation) summator; + + /// + F sum(F = T)() const @property + { + return cast(F) summator.sum; + } + + /// + void put(Range)(Range r) + if (isIterable!Range) + { + import mir.ndslice.topology: map; + summator.put(r.map!fun); + } + + /// + void put()(T x) + { + summator.put(fun(x)); + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: powi; + import mir.ndslice.slice: sliced; + + alias f = (double x) => (powi(x, 2)); + MapSummator!(f, double, Summation.pairwise) x; + x.put([0.0, 1, 2, 3, 4].sliced); + assert(x.sum == 30.0); + x.put(5); + assert(x.sum == 55.0); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + alias f = (double x) => (x + 1); + MapSummator!(f, double, Summation.pairwise) x; + x.put([0.0, 1, 2, 3, 4].sliced); + assert(x.sum == 15.0); + x.put(5); + assert(x.sum == 21.0); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.ndslice.slice: sliced; + + alias f = (double x) => (x + 1); + MapSummator!(f, double, Summation.pairwise) x; + static immutable a = [0.0, 1, 2, 3, 4]; + x.put(a.sliced); + assert(x.sum == 15.0); + x.put(5); + assert(x.sum == 21.0); +} + +version(mir_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + import mir.ndslice.slice: sliced; + + alias f = (double x) => (x + 1); + MapSummator!(f, double, Summation.pairwise) x; + auto a = [ + [0.0, 1, 2], + [3.0, 4, 5] + ].fuse; + auto b = [6.0, 7, 8].sliced; + x.put(a); + assert(x.sum == 21.0); + x.put(b); + assert(x.sum == 45.0); +} + +/++ +Variance algorithms. + +See Also: + $(WEB en.wikipedia.org/wiki/Algorithms_for_calculating_variance, Algorithms for calculating variance). ++/ +enum VarianceAlgo +{ + /++ + Performs Welford's online algorithm for updating variance. Can also `put` + another VarianceAccumulator of the same type, which uses the parallel + algorithm from Chan et al., described above. + +/ + online, + + /++ + Calculates variance using E(x^^2) - E(x)^2 (alowing for adjustments for + population/sample variance). This algorithm can be numerically unstable. + +/ + naive, + + /++ + Calculates variance using a two-pass algorithm whereby the input is first + centered and then the sum of squares is calculated from that. + +/ + twoPass, + + /++ + Calculates variance assuming the mean of the dataseries is zero. + +/ + assumeZeroMean +} + +/// +struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation) + if (isMutable!T && varianceAlgo == VarianceAlgo.naive) +{ + import mir.functional: naryFun; + + /// + this(Range)(Range r) + if (isIterable!Range) + { + import core.lifetime: move; + this.put(r.move); + } + + /// + this()(T x) + { + this.put(x); + } + + /// + MeanAccumulator!(T, summation) meanAccumulator; + + /// + Summator!(T, summation) sumOfSquares; + + /// + void put(Range)(Range r) + if (isIterable!Range) + { + foreach(x; r) + { + this.put(x); + } + } + + /// + void put()(T x) + { + meanAccumulator.put(x); + sumOfSquares.put(x * x); + } + +const: + + /// + size_t count() @property + { + return meanAccumulator.count; + } + + /// + F mean(F = T)() const @property + { + return meanAccumulator.mean; + } + + /// + F variance(F = T)(bool isPopulation) @property + { + return cast(F) sumOfSquares.sum / (count + isPopulation - 1) - + (cast(F) meanAccumulator.mean) ^^ 2 * (cast(F) count / (count + isPopulation - 1)); + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + enum PopulationTrueCT = true; + enum PopulationFalseCT = false; + bool PopulationTrueRT = true; + bool PopulationFalseRT = false; + + VarianceAccumulator!(double, VarianceAlgo.naive, Summation.naive) v; + v.put(x); + assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); + assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); + + v.put(4.0); + assert(v.variance(PopulationTrueRT).approxEqual(57.01923 / 13)); + assert(v.variance(PopulationTrueCT).approxEqual(57.01923 / 13)); + assert(v.variance(PopulationFalseRT).approxEqual(57.01923 / 12)); + assert(v.variance(PopulationFalseCT).approxEqual(57.01923 / 12)); +} + +/// +struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation) + if (isMutable!T && + varianceAlgo == VarianceAlgo.online) +{ + /// + this(Range)(Range r) + if (isIterable!Range) + { + import core.lifetime: move; + this.put(r.move); + } + + /// + this()(T x) + { + this.put(x); + } + + /// + MeanAccumulator!(T, summation) meanAccumulator; + + /// + Summator!(T, summation) centeredSumOfSquares; + + /// + void put(Range)(Range r) + if (isIterable!Range) + { + foreach(x; r) + { + this.put(x); + } + } + + /// + void put()(T x) + { + T delta = x; + if (count > 0) { + delta -= meanAccumulator.mean; + } + meanAccumulator.put(x); + centeredSumOfSquares.put(delta * (x - meanAccumulator.mean)); + } + + /// + void put()(VarianceAccumulator!(T, varianceAlgo, summation) v) + { + size_t oldCount = count; + T delta = v.mean; + if (oldCount > 0) { + delta -= meanAccumulator.mean; + } + meanAccumulator.put!T(v.meanAccumulator); + centeredSumOfSquares.put(v.centeredSumOfSquares.sum + delta * delta * v.count * oldCount / count); + } + +const: + + /// + size_t count() @property + { + return meanAccumulator.count; + } + + /// + F mean(F = T)() const @property + { + return meanAccumulator.mean; + } + + /// + F variance(F = T)(bool isPopulation) @property + { + return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1); + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + enum PopulationTrueCT = true; + enum PopulationFalseCT = false; + bool PopulationTrueRT = true; + bool PopulationFalseRT = false; + + VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; + v.put(x); + + assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); + assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); + + v.put(4.0); + assert(v.variance(PopulationTrueRT).approxEqual(57.01923 / 13)); + assert(v.variance(PopulationTrueCT).approxEqual(57.01923 / 13)); + assert(v.variance(PopulationFalseRT).approxEqual(57.01923 / 12)); + assert(v.variance(PopulationFalseCT).approxEqual(57.01923 / 12)); +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced; + auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + enum PopulationTrueCT = true; + enum PopulationFalseCT = false; + bool PopulationTrueRT = true; + bool PopulationFalseRT = false; + + VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; + v.put(x); + assert(v.variance(PopulationTrueRT).approxEqual(12.55208 / 6)); + assert(v.variance(PopulationTrueCT).approxEqual(12.55208 / 6)); + assert(v.variance(PopulationFalseRT).approxEqual(12.55208 / 5)); + assert(v.variance(PopulationFalseCT).approxEqual(12.55208 / 5)); + + v.put(y); + assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); + assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced; + auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + enum PopulationTrueCT = true; + enum PopulationFalseCT = false; + bool PopulationTrueRT = true; + bool PopulationFalseRT = false; + + VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; + v.put(x); + assert(v.variance(PopulationTrueRT).approxEqual(12.55208 / 6)); + assert(v.variance(PopulationTrueCT).approxEqual(12.55208 / 6)); + assert(v.variance(PopulationFalseRT).approxEqual(12.55208 / 5)); + assert(v.variance(PopulationFalseCT).approxEqual(12.55208 / 5)); + + VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) w; + w.put(y); + v.put(w); + assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); + assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.complex.math: approxEqual; + import mir.ndslice.slice: sliced; + import mir.complex: Complex; + + auto x = [Complex!double(1.0, 3), Complex!double(2), Complex!double(3)].sliced; + + VarianceAccumulator!(Complex!double, VarianceAlgo.online, Summation.naive) v; + v.put(x); + assert(v.variance(true).approxEqual(Complex!double(-4.0, -6) / 3)); + assert(v.variance(false).approxEqual(Complex!double(-4.0, -6) / 2)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; + v.put(x); + assert(v.variance(false).approxEqual(54.76562 / 11)); + + v.put(4.0); + assert(v.variance(false).approxEqual(57.01923 / 12)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25].sliced; + auto y = [2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) v; + v.put(x); + assert(v.variance(false).approxEqual(12.55208 / 5)); + + VarianceAccumulator!(double, VarianceAlgo.online, Summation.naive) w; + w.put(y); + v.put(w); + assert(v.variance(false).approxEqual(54.76562 / 11)); +} + +/// +struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation) + if (isMutable!T && varianceAlgo == VarianceAlgo.twoPass) +{ + import mir.functional: naryFun; + import mir.ndslice.slice: Slice, SliceKind, hasAsSlice; + + /// + MeanAccumulator!(T, summation) meanAccumulator; + + /// + Summator!(T, summation) centeredSumOfSquares; + + /// + this(Iterator, size_t N, SliceKind kind)( + Slice!(Iterator, N, kind) slice) + { + import core.lifetime: move; + import mir.ndslice.internal: LeftOp; + import mir.ndslice.topology: vmap, map; + + meanAccumulator.put(slice.lightScope); + centeredSumOfSquares.put(slice.move.vmap(LeftOp!("-", T)(meanAccumulator.mean)).map!(naryFun!"a * a")); + } + + /// + this(U)(U[] array) + { + import mir.ndslice.slice: sliced; + this(array.sliced); + } + + /// + this(T)(T withAsSlice) + if (hasAsSlice!T) + { + this(withAsSlice.asSlice); + } + + /// + this()(T x) + { + meanAccumulator.put(x); + centeredSumOfSquares.put(cast(T) 0); + } + +const: + + /// + size_t count() @property + { + return meanAccumulator.count; + } + + /// + F mean(F = T)() const @property + { + return meanAccumulator.mean; + } + + /// + F variance(F = T)(bool isPopulation) @property + { + return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1); + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + enum PopulationTrueCT = true; + enum PopulationFalseCT = false; + bool PopulationTrueRT = true; + bool PopulationFalseRT = false; + + auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x); + assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); + assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); +} + +// dynamic array test +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.rc.array: RCArray; + + double[] x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; + + auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x); + assert(v.centeredSumOfSquares.sum.approxEqual(54.76562)); +} + +// withAsSlice test +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + import mir.rc.array: RCArray; + + static immutable a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; + + auto x = RCArray!double(12); + foreach(i, ref e; x) + e = a[i]; + + auto v = VarianceAccumulator!(double, VarianceAlgo.twoPass, Summation.naive)(x); + assert(v.centeredSumOfSquares.sum.approxEqual(54.76562)); +} + +/// +struct VarianceAccumulator(T, VarianceAlgo varianceAlgo, Summation summation) + if (isMutable!T && varianceAlgo == VarianceAlgo.assumeZeroMean) +{ + import mir.ndslice.slice: Slice, SliceKind, hasAsSlice; + + private size_t _count; + + /// + Summator!(T, summation) centeredSumOfSquares; + + /// + this(Range)(Range r) + if (isIterable!Range) + { + this.put(r); + } + + /// + this()(T x) + { + this.put(x); + } + + /// + void put(Range)(Range r) + if (isIterable!Range) + { + foreach(x; r) + { + this.put(x); + } + } + + /// + void put()(T x) + { + _count++; + centeredSumOfSquares.put(x * x); + } + + /// + void put()(VarianceAccumulator!(T, varianceAlgo, summation) v) + { + _count += v.count; + centeredSumOfSquares.put(v.centeredSumOfSquares.sum); + } + +const: + + /// + size_t count() @property + { + return _count; + } + + /// + F mean(F = T)() const @property + { + return cast(F) 0; + } + + /// + F variance(F = T)(bool isPopulation) @property + { + return cast(F) centeredSumOfSquares.sum / (count + isPopulation - 1); + } +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + auto x = a.center; + + enum PopulationTrueCT = true; + enum PopulationFalseCT = false; + bool PopulationTrueRT = true; + bool PopulationFalseRT = false; + + VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; + v.put(x); + + assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); + assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); + + v.put(4.0); + assert(v.variance(PopulationTrueRT).approxEqual(70.76562 / 13)); + assert(v.variance(PopulationTrueCT).approxEqual(70.76562 / 13)); + assert(v.variance(PopulationFalseRT).approxEqual(70.76562 / 12)); + assert(v.variance(PopulationFalseCT).approxEqual(70.76562 / 12)); +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + auto b = a.center; + auto x = b[0 .. 6]; + auto y = b[6 .. $]; + + enum PopulationTrueCT = true; + enum PopulationFalseCT = false; + bool PopulationTrueRT = true; + bool PopulationFalseRT = false; + + VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; + v.put(x); + assert(v.variance(PopulationTrueRT).approxEqual(13.492188 / 6)); + assert(v.variance(PopulationTrueCT).approxEqual(13.492188 / 6)); + assert(v.variance(PopulationFalseRT).approxEqual(13.492188 / 5)); + assert(v.variance(PopulationFalseCT).approxEqual(13.492188 / 5)); + + v.put(y); + assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); + assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + auto b = a.center; + auto x = b[0 .. 6]; + auto y = b[6 .. $]; + + enum PopulationTrueCT = true; + enum PopulationFalseCT = false; + bool PopulationTrueRT = true; + bool PopulationFalseRT = false; + + VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; + v.put(x); + assert(v.variance(PopulationTrueRT).approxEqual(13.492188 / 6)); + assert(v.variance(PopulationTrueCT).approxEqual(13.492188 / 6)); + assert(v.variance(PopulationFalseRT).approxEqual(13.492188 / 5)); + assert(v.variance(PopulationFalseCT).approxEqual(13.492188 / 5)); + + VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) w; + w.put(y); + v.put(w); + assert(v.variance(PopulationTrueRT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationTrueCT).approxEqual(54.76562 / 12)); + assert(v.variance(PopulationFalseRT).approxEqual(54.76562 / 11)); + assert(v.variance(PopulationFalseCT).approxEqual(54.76562 / 11)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.complex.math: approxEqual; + import mir.ndslice.slice: sliced; + import mir.complex: Complex; + + auto a = [Complex!double(1.0, 3), Complex!double(2), Complex!double(3)].sliced; + auto x = a.center; + + VarianceAccumulator!(Complex!double, VarianceAlgo.assumeZeroMean, Summation.naive) v; + v.put(x); + assert(v.variance(true).approxEqual(Complex!double(-4.0, -6) / 3)); + assert(v.variance(false).approxEqual(Complex!double(-4.0, -6) / 2)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + auto x = a.center; + + VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; + v.put(x); + assert(v.variance(false).approxEqual(54.76562 / 11)); + + v.put(4.0); + assert(v.variance(false).approxEqual(70.76562 / 12)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + auto b = a.center; + auto x = b[0 .. 6]; + auto y = b[6 .. $]; + + VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) v; + v.put(x); + assert(v.variance(false).approxEqual(13.492188 / 5)); + + VarianceAccumulator!(double, VarianceAlgo.assumeZeroMean, Summation.naive) w; + w.put(y); + v.put(w); + assert(v.variance(false).approxEqual(54.76562 / 11)); +} + +/++ +Calculates the variance of the input + +By default, if `F` is not floating point type or complex type, then the result +will have a `double` type if `F` is implicitly convertible to a floating point +type or a type for which `isComplex!F` is true. + +Params: + F = controls type of output + varianceAlgo = algorithm for calculating variance (default: VarianceAlgo.online) + summation = algorithm for calculating sums (default: Summation.appropriate) +Returns: + The variance of the input, must be floating point or complex type ++/ +template variance( + F, + VarianceAlgo varianceAlgo = VarianceAlgo.online, + Summation summation = Summation.appropriate) +{ + /++ + Params: + r = range, must be finite iterable + isPopulation = true if population variance, false if sample variance (default) + +/ + @fmamath meanType!F variance(Range)(Range r, bool isPopulation = false) + if (isIterable!Range) + { + import core.lifetime: move; + + alias G = typeof(return); + auto varianceAccumulator = VarianceAccumulator!(G, varianceAlgo, ResolveSummationType!(summation, Range, G))(r.move); + return varianceAccumulator.variance(isPopulation); + } + + /++ + Params: + ar = values + +/ + @fmamath meanType!F variance(scope const F[] ar...) + { + alias G = typeof(return); + auto varianceAccumulator = VarianceAccumulator!(G, varianceAlgo, ResolveSummationType!(summation, const(G)[], G))(ar); + return varianceAccumulator.variance(false); + } +} + +/// ditto +template variance( + VarianceAlgo varianceAlgo = VarianceAlgo.online, + Summation summation = Summation.appropriate) +{ + /++ + Params: + r = range, must be finite iterable + isPopulation = true if population variance, false if sample variance (default) + +/ + @fmamath meanType!Range variance(Range)(Range r, bool isPopulation = false) + if(isIterable!Range) + { + import core.lifetime: move; + + alias F = typeof(return); + return .variance!(F, varianceAlgo, summation)(r.move, isPopulation); + } + + /++ + Params: + ar = values + +/ + @fmamath meanType!T variance(T)(scope const T[] ar...) + { + alias F = typeof(return); + return .variance!(F, varianceAlgo, summation)(ar); + } +} + +/// ditto +template variance(F, string varianceAlgo, string summation = "appropriate") +{ + mixin("alias variance = .variance!(F, VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");"); +} + +/// ditto +template variance(string varianceAlgo, string summation = "appropriate") +{ + mixin("alias variance = .variance!(VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");"); +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.complex.math: capproxEqual = approxEqual; + import mir.ndslice.slice: sliced; + import mir.complex; + alias C = Complex!double; + + assert(variance([1.0, 2, 3]).approxEqual(2.0 / 2)); + assert(variance([1.0, 2, 3], true).approxEqual(2.0 / 3)); + + assert(variance([C(1, 3), C(2), C(3)]).capproxEqual(C(-4, -6) / 2)); + + assert(variance!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).approxEqual(17.5 / 5)); + + static assert(is(typeof(variance!float([1, 2, 3])) == float)); +} + +/// Variance of vector +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + assert(x.variance.approxEqual(54.76562 / 11)); +} + +/// Variance of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.fuse: fuse; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + + assert(x.variance.approxEqual(54.76562 / 11)); +} + +/// Column variance of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: alongDim, byDim, map; + + auto x = [ + [0.0, 1.0, 1.5, 2.0], + [3.5, 4.25, 2.0, 7.5], + [5.0, 1.0, 1.5, 0.0] + ].fuse; + auto result = [13.16667 / 2, 7.041667 / 2, 0.1666667 / 2, 30.16667 / 2]; + + // Use byDim or alongDim with map to compute variance of row/column. + assert(x.byDim!1.map!variance.all!approxEqual(result)); + assert(x.alongDim!0.map!variance.all!approxEqual(result)); + + // FIXME + // Without using map, computes the variance of the whole slice + // assert(x.byDim!1.variance == x.sliced.variance); + // assert(x.alongDim!0.variance == x.sliced.variance); +} + +/// Can also set algorithm type +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + auto x = a + 1_000_000_000; + + auto y = x.variance; + assert(y.approxEqual(54.76562 / 11)); + + // The naive algorithm is numerically unstable in this case + auto z0 = x.variance!"naive"; + assert(!z0.approxEqual(y)); + + // But the two-pass algorithm provides a consistent answer + auto z1 = x.variance!"twoPass"; + assert(z1.approxEqual(y)); + + // And the assumeZeroMean algorithm is way off + auto z2 = x.variance!"assumeZeroMean"; + assert(z2.approxEqual(1.2e19 / 11)); +} + +/// Can also set algorithm or output type +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: repeat; + + //Set population variance, variance algorithm, sum algorithm or output type + + auto a = [1.0, 1e100, 1, -1e100].sliced; + auto x = a * 10_000; + + bool populationTrueRT = true; + bool populationFalseRT = false; + enum PopulationTrueCT = true; + + /++ + Due to Floating Point precision, when centering `x`, subtracting the mean + from the second and fourth numbers has no effect. Further, after centering + and squaring `x`, the first and third numbers in the slice have precision + too low to be included in the centered sum of squares. + +/ + assert(x.variance(populationFalseRT).approxEqual(2.0e208 / 3)); + assert(x.variance(populationTrueRT).approxEqual(2.0e208 / 4)); + assert(x.variance(PopulationTrueCT).approxEqual(2.0e208 / 4)); + + assert(x.variance!("online").approxEqual(2.0e208 / 3)); + assert(x.variance!("online", "kbn").approxEqual(2.0e208 / 3)); + assert(x.variance!("online", "kb2").approxEqual(2.0e208 / 3)); + assert(x.variance!("online", "precise").approxEqual(2.0e208 / 3)); + assert(x.variance!(double, "online", "precise").approxEqual(2.0e208 / 3)); + assert(x.variance!(double, "online", "precise")(populationTrueRT).approxEqual(2.0e208 / 4)); + + auto y = uint.max.repeat(3); + auto z = y.variance!ulong; + assert(z == 0.0); + static assert(is(typeof(z) == double)); +} + +/++ +For integral slices, pass output type as template parameter to ensure output +type is correct. ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + auto x = [0, 1, 1, 2, 4, 4, + 2, 7, 5, 1, 2, 0].sliced; + + auto y = x.variance; + assert(y.approxEqual(50.91667 / 11)); + static assert(is(typeof(y) == double)); + + assert(x.variance!float.approxEqual(50.91667 / 11)); +} + +/++ +Variance works for complex numbers and other user-defined types (provided they +can be converted to a floating point or complex type) ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.complex.math: approxEqual; + import mir.ndslice.slice: sliced; + import mir.complex; + alias C = Complex!double; + + auto x = [C(1, 2), C(2, 3), C(3, 4), C(4, 5)].sliced; + assert(x.variance.approxEqual((C(0, 10)) / 3)); +} + +/// Compute variance along specified dimention of tensors +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: as, iota, alongDim, map, repeat; + + auto x = [ + [0.0, 1, 2], + [3.0, 4, 5] + ].fuse; + + assert(x.variance.approxEqual(17.5 / 5)); + + auto m0 = [4.5, 4.5, 4.5]; + assert(x.alongDim!0.map!variance.all!approxEqual(m0)); + assert(x.alongDim!(-2).map!variance.all!approxEqual(m0)); + + auto m1 = [1.0, 1.0]; + assert(x.alongDim!1.map!variance.all!approxEqual(m1)); + assert(x.alongDim!(-1).map!variance.all!approxEqual(m1)); + + assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!variance.all!approxEqual(repeat(3600.0 / 2, 3, 4, 5))); +} + +/// Arbitrary variance +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + assert(variance(1.0, 2, 3) == 1.0); + assert(variance!float(1, 2, 3) == 1f); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual; + + assert([1.0, 2, 3, 4].variance.approxEqual(5.0 / 3)); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + import mir.ndslice.topology: iota, alongDim, map; + + auto x = iota([2, 2], 1); + auto y = x.alongDim!1.map!variance; + assert(y.all!approxEqual([0.5, 0.5])); + static assert(is(meanType!(typeof(y)) == double)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + import mir.ndslice.slice: sliced; + + static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; + + assert(x.sliced.variance.approxEqual(54.76562 / 11)); + assert(x.sliced.variance!float.approxEqual(54.76562 / 11)); +} + +/// +package(mir) +template stdevType(T) +{ + import mir.internal.utility: isFloatingPoint; + + alias U = meanType!T; + + static if (isFloatingPoint!U) { + alias stdevType = U; + } else { + static assert(0, "stdevType: Can't calculate standard deviation of elements of type " ~ U.stringof); + } +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static assert(is(stdevType!(int[]) == double)); + static assert(is(stdevType!(double[]) == double)); + static assert(is(stdevType!(float[]) == float)); +} + +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + static struct Foo { + float x; + alias x this; + } + + static assert(is(stdevType!(Foo[]) == float)); +} + +/++ +Calculates the standard deviation of the input + +By default, if `F` is not floating point type, then the result will have a +`double` type if `F` is implicitly convertible to a floating point type. + +Params: + F = controls type of output + varianceAlgo = algorithm for calculating variance (default: VarianceAlgo.online) + summation = algorithm for calculating sums (default: Summation.appropriate) +Returns: + The standard deviation of the input, must be floating point type type ++/ +template standardDeviation( + F, + VarianceAlgo varianceAlgo = VarianceAlgo.online, + Summation summation = Summation.appropriate) +{ + import mir.math.common: sqrt; + + /++ + Params: + r = range, must be finite iterable + isPopulation = true if population standard deviation, false if sample standard deviation (default) + +/ + @fmamath stdevType!F standardDeviation(Range)(Range r, bool isPopulation = false) + if (isIterable!Range) + { + import core.lifetime: move; + alias G = typeof(return); + return r.move.variance!(G, varianceAlgo, ResolveSummationType!(summation, Range, G))(isPopulation).sqrt; + } + + /++ + Params: + ar = values + +/ + @fmamath stdevType!F standardDeviation(scope const F[] ar...) + { + alias G = typeof(return); + return ar.variance!(G, varianceAlgo, ResolveSummationType!(summation, const(G)[], G)).sqrt; + } +} + +/// ditto +template standardDeviation( + VarianceAlgo varianceAlgo = VarianceAlgo.online, + Summation summation = Summation.appropriate) +{ + /++ + Params: + r = range, must be finite iterable + isPopulation = true if population standard deviation, false if sample standard deviation (default) + +/ + @fmamath stdevType!Range standardDeviation(Range)(Range r, bool isPopulation = false) + if(isIterable!Range) + { + import core.lifetime: move; + + alias F = typeof(return); + return .standardDeviation!(F, varianceAlgo, summation)(r.move, isPopulation); + } + + /++ + Params: + ar = values + +/ + @fmamath stdevType!T standardDeviation(T)(scope const T[] ar...) + { + alias F = typeof(return); + return .standardDeviation!(F, varianceAlgo, summation)(ar); + } +} + +/// ditto +template standardDeviation(F, string varianceAlgo, string summation = "appropriate") +{ + mixin("alias standardDeviation = .standardDeviation!(F, VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");"); +} + +/// ditto +template standardDeviation(string varianceAlgo, string summation = "appropriate") +{ + mixin("alias standardDeviation = .standardDeviation!(VarianceAlgo." ~ varianceAlgo ~ ", Summation." ~ summation ~ ");"); +} + +/// +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.slice: sliced; + + assert(standardDeviation([1.0, 2, 3]).approxEqual(sqrt(2.0 / 2))); + assert(standardDeviation([1.0, 2, 3], true).approxEqual(sqrt(2.0 / 3))); + + assert(standardDeviation!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).approxEqual(sqrt(17.5 / 5))); + + static assert(is(typeof(standardDeviation!float([1, 2, 3])) == float)); +} + +/// Standard deviation of vector +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.slice: sliced; + + auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + assert(x.standardDeviation.approxEqual(sqrt(54.76562 / 11))); +} + +/// Standard deviation of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.fuse: fuse; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + + assert(x.standardDeviation.approxEqual(sqrt(54.76562 / 11))); +} + +/// Column standard deviation of matrix +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: alongDim, byDim, map; + + auto x = [ + [0.0, 1.0, 1.5, 2.0], + [3.5, 4.25, 2.0, 7.5], + [5.0, 1.0, 1.5, 0.0] + ].fuse; + auto result = [13.16667 / 2, 7.041667 / 2, 0.1666667 / 2, 30.16667 / 2].map!sqrt; + + // Use byDim or alongDim with map to compute standardDeviation of row/column. + assert(x.byDim!1.map!standardDeviation.all!approxEqual(result)); + assert(x.alongDim!0.map!standardDeviation.all!approxEqual(result)); + + // FIXME + // Without using map, computes the standardDeviation of the whole slice + // assert(x.byDim!1.standardDeviation == x.sliced.standardDeviation); + // assert(x.alongDim!0.standardDeviation == x.sliced.standardDeviation); +} + +/// Can also set algorithm type +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.slice: sliced; + + auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced; + + auto x = a + 1_000_000_000; + + auto y = x.standardDeviation; + assert(y.approxEqual(sqrt(54.76562 / 11))); + + // The naive algorithm is numerically unstable in this case + auto z0 = x.standardDeviation!"naive"; + assert(!z0.approxEqual(y)); + + // But the two-pass algorithm provides a consistent answer + auto z1 = x.standardDeviation!"twoPass"; + assert(z1.approxEqual(y)); +} + +/// Can also set algorithm or output type +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.slice: sliced; + import mir.ndslice.topology: repeat; + + //Set population standard deviation, standardDeviation algorithm, sum algorithm or output type + + auto a = [1.0, 1e100, 1, -1e100].sliced; + auto x = a * 10_000; + + bool populationTrueRT = true; + bool populationFalseRT = false; + enum PopulationTrueCT = true; + + /++ + Due to Floating Point precision, when centering `x`, subtracting the mean + from the second and fourth numbers has no effect. Further, after centering + and squaring `x`, the first and third numbers in the slice have precision + too low to be included in the centered sum of squares. + +/ + assert(x.standardDeviation(populationFalseRT).approxEqual(sqrt(2.0e208 / 3))); + assert(x.standardDeviation(populationTrueRT).approxEqual(sqrt(2.0e208 / 4))); + assert(x.standardDeviation(PopulationTrueCT).approxEqual(sqrt(2.0e208 / 4))); + + assert(x.standardDeviation!("online").approxEqual(sqrt(2.0e208 / 3))); + assert(x.standardDeviation!("online", "kbn").approxEqual(sqrt(2.0e208 / 3))); + assert(x.standardDeviation!("online", "kb2").approxEqual(sqrt(2.0e208 / 3))); + assert(x.standardDeviation!("online", "precise").approxEqual(sqrt(2.0e208 / 3))); + assert(x.standardDeviation!(double, "online", "precise").approxEqual(sqrt(2.0e208 / 3))); + assert(x.standardDeviation!(double, "online", "precise")(populationTrueRT).approxEqual(sqrt(2.0e208 / 4))); + + auto y = uint.max.repeat(3); + auto z = y.standardDeviation!ulong; + assert(z == 0.0); + static assert(is(typeof(z) == double)); +} + +/++ +For integral slices, pass output type as template parameter to ensure output +type is correct. ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.slice: sliced; + + auto x = [0, 1, 1, 2, 4, 4, + 2, 7, 5, 1, 2, 0].sliced; + + auto y = x.standardDeviation; + assert(y.approxEqual(sqrt(50.91667 / 11))); + static assert(is(typeof(y) == double)); + + assert(x.standardDeviation!float.approxEqual(sqrt(50.91667 / 11))); +} + +/++ +Variance works for other user-defined types (provided they +can be converted to a floating point) ++/ +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + static struct Foo { + float x; + alias x this; + } + + Foo[] foo = [Foo(1f), Foo(2f), Foo(3f)]; + assert(foo.standardDeviation == 1f); +} + +/// Compute standard deviation along specified dimention of tensors +version(mir_test) +@safe pure +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: as, iota, alongDim, map, repeat; + + auto x = [ + [0.0, 1, 2], + [3.0, 4, 5] + ].fuse; + + assert(x.standardDeviation.approxEqual(sqrt(17.5 / 5))); + + auto m0 = repeat(sqrt(4.5), 3); + assert(x.alongDim!0.map!standardDeviation.all!approxEqual(m0)); + assert(x.alongDim!(-2).map!standardDeviation.all!approxEqual(m0)); + + auto m1 = [1.0, 1.0]; + assert(x.alongDim!1.map!standardDeviation.all!approxEqual(m1)); + assert(x.alongDim!(-1).map!standardDeviation.all!approxEqual(m1)); + + assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!standardDeviation.all!approxEqual(repeat(sqrt(3600.0 / 2), 3, 4, 5))); +} + +/// Arbitrary standard deviation +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: sqrt; + + assert(standardDeviation(1.0, 2, 3) == 1.0); + assert(standardDeviation!float(1, 2, 3) == 1f); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.math.common: approxEqual, sqrt; + assert([1.0, 2, 3, 4].standardDeviation.approxEqual(sqrt(5.0 / 3))); +} + +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.topology: iota, alongDim, map; + + auto x = iota([2, 2], 1); + auto y = x.alongDim!1.map!standardDeviation; + assert(y.all!approxEqual([sqrt(0.5), sqrt(0.5)])); + static assert(is(meanType!(typeof(y)) == double)); +} + +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + import mir.math.common: approxEqual, sqrt; + import mir.ndslice.slice: sliced; + + static immutable x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25, + 2.0, 7.5, 5.0, 1.0, 1.5, 0.0]; + + assert(x.sliced.standardDeviation.approxEqual(sqrt(54.76562 / 11))); + assert(x.sliced.standardDeviation!float.approxEqual(sqrt(54.76562 / 11))); +} + +/++ +A linear regression model with a single explanatory variable. ++/ +template simpleLinearRegression(Summation summation = Summation.kbn) +{ + import mir.ndslice.slice; + import mir.primitives: isInputRange; + + /++ + Params: + x = `x[i]` points + y = `f(x[i])` values + Returns: + The pair of shift and slope of the linear curve. + +/ + @fmamath + sumType!YRange[2] + simpleLinearRegression(XRange, YRange)(XRange x, YRange y) @safe + if (isInputRange!XRange && isInputRange!YRange && !(isArray!XRange && isArray!YRange) && isFloatingPoint!(sumType!YRange)) + in { + static if (hasLength!XRange && hasLength!YRange) + assert(x.length == y.length); + } + do { + alias X = typeof(sumType!XRange.init * sumType!XRange.init); + alias Y = sumType!YRange; + enum summationX = !__traits(isIntegral, X) ? summation: Summation.naive; + Summator!(X, summationX) xms = 0; + Summator!(Y, summation) yms = 0; + Summator!(X, summationX) xxms = 0; + Summator!(Y, summation) xyms = 0; + + static if (hasLength!XRange) + sizediff_t n = x.length; + else + sizediff_t n = 0; + + while (!x.empty) + { + static if (!(hasLength!XRange && hasLength!YRange)) + assert(!y.empty); + + static if (!hasLength!XRange) + n++; + + auto xi = x.front; + auto yi = y.front; + xms.put(xi); + yms.put(yi); + xxms.put(xi * xi); + xyms.put(xi * yi); + + y.popFront; + x.popFront; + } + + static if (!(hasLength!XRange && hasLength!YRange)) + assert(y.empty); + + auto xm = xms.sum; + auto ym = yms.sum; + auto xxm = xxms.sum; + auto xym = xyms.sum; + + auto slope = (xym * n - xm * ym) / (xxm * n - xm * xm); + + return [(ym - slope * xm) / n, slope]; + } + + /// ditto + @fmamath + sumType!(Y[])[2] + simpleLinearRegression(X, Y)(scope const X[] x, scope const Y[] y) @safe + { + return .simpleLinearRegression!summation(x.sliced, y.sliced); + } +} + +/// ditto +template simpleLinearRegression(string summation) +{ + mixin("alias simpleLinearRegression = .simpleLinearRegression!(Summation." ~ summation ~ ");"); +} + +/// +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.math.common: approxEqual; + static immutable x = [0, 1, 2, 3]; + static immutable y = [-1, 0.2, 0.9, 2.1]; + auto params = x.simpleLinearRegression(y); + assert(params[0].approxEqual(-0.95)); // shift + assert(params[1].approxEqual(1)); // slope +} diff --git a/source/mir/math/sum.d b/source/mir/math/sum.d index 2a0924ad..c74f106e 100644 --- a/source/mir/math/sum.d +++ b/source/mir/math/sum.d @@ -1,11 +1,11 @@ /++ This module contains summation algorithms. -License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) -Authors: Ilya Yaroshenko +Authors: Ilia Ki -Copyright: Copyright © 2015-, Ilya Yaroshenko +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +/ module mir.math.sum; @@ -20,6 +20,26 @@ unittest assert(r == ar.sum!"kbn"); assert(r == ar.sum!"kb2"); assert(r == ar.sum!"precise"); + assert(r == ar.sum!"decimal"); +} + +/// Decimal precise summation +version(mir_test) +unittest +{ + auto ar = [777.7, -777]; + assert(ar.sum!"decimal" == 0.7); + assert(sum!"decimal"(777.7, -777) == 0.7); + + // The exact binary reuslt is 0.7000000000000455 + assert(ar[0] + ar[1] == 0.7000000000000455); + assert(ar.sum!"fast" == 0.7000000000000455); + assert(ar.sum!"kahan" == 0.7000000000000455); + assert(ar.sum!"kbn" == 0.7000000000000455); + assert(ar.sum!"kb2" == 0.7000000000000455); + assert(ar.sum!"precise" == 0.7000000000000455); + + assert([1e-20, 1].sum!"decimal" == 1); } /// @@ -45,7 +65,7 @@ unittest version(mir_test) unittest { - import std.traits : isFloatingPoint; + import mir.internal.utility: isFloatingPoint; static struct Quaternion(F) if (isFloatingPoint!F) { @@ -99,8 +119,10 @@ All summation algorithms available for complex numbers. version(mir_test) unittest { - cdouble[] ar = [1.0 + 2i, 2 + 3i, 3 + 4i, 4 + 5i]; - cdouble r = 10 + 14i; + import mir.complex: Complex; + + auto ar = [Complex!double(1.0, 2), Complex!double(2.0, 3), Complex!double(3.0, 4), Complex!double(4.0, 5)]; + Complex!double r = Complex!double(10.0, 14); assert(r == ar.sum!"fast"); assert(r == ar.sum!"naive"); assert(r == ar.sum!"pairwise"); @@ -111,6 +133,7 @@ unittest assert(r == ar.sum!"kb2"); } assert(r == ar.sum!"precise"); + assert(r == ar.sum!"decimal"); } /// @@ -130,7 +153,7 @@ version(mir_test) assert(sum(ubyte.max.repeat(100)) == 25_500); //The result may overflow - assert(uint.max.repeat(3).sum() == 4_294_967_293U ); + assert(uint.max.repeat(3).sum == 4_294_967_293U ); //But a seed can be used to change the summation primitive assert(uint.max.repeat(3).sum(ulong.init) == 12_884_901_885UL); @@ -169,7 +192,7 @@ nothrow @nogc unittest Summator!(real, Summation.precise) s = 0.0; s.put(r); s -= 1.7L.pow(1000); - assert(s.sum() == -1); + assert(s.sum == -1); } /// Precise summation with output range @@ -182,59 +205,79 @@ nothrow @nogc unittest auto s = Summator!(float, Summation.precise)(0); s += M; s += M; - assert(float.infinity == s.sum()); //infinity + assert(float.infinity == s.sum); //infinity auto e = cast(Summator!(double, Summation.precise)) s; - assert(e.sum() < double.infinity); + assert(e.sum < double.infinity); assert(N+N == e.sum()); //finite number } /// Moving mean version(mir_test) +@safe pure nothrow @nogc unittest { - import mir.ndslice.topology: linspace; + import mir.internal.utility: isFloatingPoint; import mir.math.sum; - import mir.array.allocation: array; + import mir.ndslice.topology: linspace; + import mir.rc.array: rcarray; - class MovingAverage + struct MovingAverage(T) + if (isFloatingPoint!T) { - Summator!(double, Summation.precise) summator; + import mir.math.stat: MeanAccumulator; + + MeanAccumulator!(T, Summation.precise) meanAccumulator; double[] circularBuffer; size_t frontIndex; - double avg() @property const + @disable this(this); + + auto avg() @property const { - return summator.sum() / circularBuffer.length; + return meanAccumulator.mean; } this(double[] buffer) { assert(buffer.length); circularBuffer = buffer; - summator = 0; - summator.put(buffer); + meanAccumulator.put(buffer); } ///operation without rounding - void put(double x) + void put(T x) { import mir.utility: swap; - summator += x; + meanAccumulator.summator += x; swap(circularBuffer[frontIndex++], x); - summator -= x; - frontIndex %= circularBuffer.length; + frontIndex = frontIndex == circularBuffer.length ? 0 : frontIndex; + meanAccumulator.summator -= x; } } /// ma always keeps precise average of last 1000 elements - auto ma = new MovingAverage(linspace!double([1000], [0.0, 999]).array); + auto x = linspace!double([1000], [0.0, 999]).rcarray; + auto ma = MovingAverage!double(x[]); assert(ma.avg == (1000 * 999 / 2) / 1000.0); /// move by 10 elements - foreach(x; linspace!double([10], [1000.0, 1009.0])) - ma.put(x); + foreach(e; linspace!double([10], [1000.0, 1009.0])) + ma.put(e); assert(ma.avg == (1010 * 1009 / 2 - 10 * 9 / 2) / 1000.0); } +/// Arbitrary sum +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.complex; + alias C = Complex!double; + assert(sum(1, 2, 3, 4) == 10); + assert(sum!float(1, 2, 3, 4) == 10f); + assert(sum(1f, 2, 3, 4) == 10f); + assert(sum(C(1.0, 2), C(2, 3), C(3, 4), C(4, 5)) == C(10, 14)); +} + version(X86) version = X86_Any; version(X86_64) @@ -299,7 +342,7 @@ enum Summation Precise summation algorithm. The value of the sum is rounded to the nearest representable floating-point number using the $(LUCKY round-half-to-even rule). - The result can differ from the exact value on `X86`, `nextDown(proir) <= result && result <= nextUp(proir)`. + The result can differ from the exact value on 32bit `x86`, `nextDown(proir) <= result && result <= nextUp(proir)`. The current implementation re-establish special value semantics across iterations (i.e. handling ±inf). References: $(LINK2 http://www.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps, @@ -317,6 +360,17 @@ enum Summation +/ precise, + /++ + Precise decimal summation algorithm. + + The elements of the sum are converted to a shortest decimal representation that being converted back would result the same floating-point number. + The resulting decimal elements are summed without rounding. + The decimal sum is converted back to a binary floating point representation using round-half-to-even rule. + + See_also: The $(HTTPS github.com/ulfjack/ryu, Ryu algorithm) + +/ + decimal, + /++ $(WEB en.wikipedia.org/wiki/Kahan_summation, Kahan summation) algorithm. +/ @@ -400,7 +454,8 @@ Output range for summation. struct Summator(T, Summation summation) if (isMutable!T) { - static if (is(T == class) || is(T == interface) || hasElaborateAssign!T) + import mir.internal.utility: isComplex; + static if (is(T == class) || is(T == interface) || hasElaborateAssign!T && !isComplex!T) static assert (summation == Summation.naive, "Classes, interfaces, and structures with " ~ "elaborate constructor support only naive summation."); @@ -424,30 +479,27 @@ struct Summator(T, Summation summation) @attr: - static if (summation != Summation.pairwise) - @disable this(); - - static if (summation == Summation.pairwise) + static if (summation == Summation.pairwise) { private enum bool fastPairwise = is(F == float) || is(F == double) || - is(F == cfloat) || - is(F == cdouble) || + (isComplex!F && F.sizeof <= 16) || is(F : __vector(W[N]), W, size_t N); //false; + } alias F = T; static if (summation == Summation.precise) { import std.internal.scopebuffer; + import mir.appender; import mir.math.ieee: signbit; private: enum F M = (cast(F)(2)) ^^ (T.max_exp - 1); - F[16] scopeBufferArray = 0; - ScopeBuffer!F partials; + auto partials = scopedBuffer!(F, 8 * T.sizeof); //sum for NaN and infinity. - F s; + F s = summationInitValue!F; //Overflow Degree. Count of 2^^F.max_exp minus count of -(2^^F.max_exp) sizediff_t o; @@ -468,7 +520,7 @@ struct Summator(T, Summation summation) { debug(numeric) assert(!partials.length || .isFinite(s)); } - body + do { bool _break; foreach_reverse (i, y; partials) @@ -486,7 +538,7 @@ struct Summator(T, Summation summation) { debug(numeric) assert(.isFinite(result)); } - body + do { F x = s; s = x + y; @@ -523,11 +575,11 @@ struct Summator(T, Summation summation) { if (o == 0) return 0; - if (partials.length && (o == -1 || o == 1) && signbit(o * partials[$-1])) + if (partials.length && (o == -1 || o == 1) && signbit(o * partials.data[$-1])) { // problem case: decide whether result is representable F x = o * M; - F y = partials[$-1] / 2; + F y = partials.data[$-1] / 2; F h = x + y; F d = h - x; F l = (y - d) * 2; @@ -542,7 +594,7 @@ struct Summator(T, Summation summation) else { if (!.isInfinity(cast(T)y) || - ((partials.length > 1 && !signbit(l * partials[$-2])) && t == l)) + ((partials.length > 1 && !signbit(l * partials.data[$-2])) && t == l)) return 0; } } @@ -552,23 +604,23 @@ struct Summator(T, Summation summation) else static if (summation == Summation.kb2) { - F s; - F cs; - F ccs; + F s = summationInitValue!F; + F cs = summationInitValue!F; + F ccs = summationInitValue!F; } else static if (summation == Summation.kbn) { - F s; - F c; + F s = summationInitValue!F; + F c = summationInitValue!F; } else static if (summation == Summation.kahan) { - F s; - F c; - F y; // do not declare in the loop/put (algo can be used for matrixes and etc) - F t; // ditto + F s = summationInitValue!F; + F c = summationInitValue!F; + F y = summationInitValue!F; // do not declare in the loop/put (algo can be used for matrixes and etc) + F t = summationInitValue!F; // ditto } else static if (summation == Summation.pairwise) @@ -588,12 +640,19 @@ struct Summator(T, Summation summation) else static if (summation == Summation.naive) { - F s; + F s = summationInitValue!F; } else static if (summation == Summation.fast) { - F s; + F s = summationInitValue!F; + } + else + static if (summation == Summation.decimal) + { + import mir.bignum.decimal; + Decimal!128 s; + T ss = 0; } else static assert(0, "Unsupported summation type for std.numeric.Summator."); @@ -606,7 +665,6 @@ public: { static if (summation == Summation.precise) { - partials = scopeBuffer(scopeBufferArray); s = 0.0; o = 0; if (n) put(n); @@ -617,8 +675,8 @@ public: s = n; static if (isComplex!T) { - cs = 0 + 0fi; - ccs = 0 + 0fi; + cs = Complex!float(0, 0); + ccs = Complex!float(0, 0); } else { @@ -631,7 +689,7 @@ public: { s = n; static if (isComplex!T) - c = 0 + 0fi; + c = Complex!float(0, 0); else c = 0.0; } @@ -640,7 +698,7 @@ public: { s = n; static if (isComplex!T) - c = 0 + 0fi; + c = Complex!float(0, 0); else c = 0.0; } @@ -661,27 +719,18 @@ public: s = n; } else - static assert(0); - } - - // free ScopeBuffer - static if (summation == Summation.precise) - ~this() - { - version(LDC) pragma(inline, true); - partials.free; - } - - // copy ScopeBuffer if necessary - static if (summation == Summation.precise) - this(this) - { - auto a = partials[]; - if (scopeBufferArray.ptr !is a.ptr) + static if (summation == Summation.decimal) { - partials = scopeBuffer(scopeBufferArray); - partials.put(a); + ss = 0; + if (!(-n.infinity < n && n < n.infinity)) + { + ss = n; + n = 0; + } + s = n; } + else + static assert(0); } ///Adds `n` to the internal partial sums. @@ -695,7 +744,8 @@ public: if (.isFinite(x)) { size_t i; - foreach (y; partials[]) + auto partials_data = partials.data; + foreach (y; partials_data[]) { F h = x + y; if (.isInfinity(cast(T)h)) @@ -736,11 +786,11 @@ public: debug(numeric) assert(l.isFinite); if (l) { - partials[i++] = l; + partials_data[i++] = l; } x = h; } - partials.length = i; + partials.shrinkTo(i); if (x) { partials.put(x); @@ -791,15 +841,15 @@ public: { auto s_re = s.re; auto x_re = x.re; - s = x_re + s.im * 1fi; - x = s_re + x.im * 1fi; + s = F(x_re, s.im); + x = F(s_re, x.im); } if (fabs(s.im) < fabs(x.im)) { auto s_im = s.im; auto x_im = x.im; - s = s.re + x_im * 1fi; - x = x.re + s_im * 1fi; + s = F(s.re, x_im); + x = F(x.re, s_im); } F c = (s-t)+x; s = t; @@ -807,15 +857,15 @@ public: { auto c_re = c.re; auto cs_re = cs.re; - c = cs_re + c.im * 1fi; - cs = c_re + cs.im * 1fi; + c = F(cs_re, c.im); + cs = F(c_re, cs.im); } if (fabs(cs.im) < fabs(c.im)) { auto c_im = c.im; auto cs_im = cs.im; - c = c.re + cs_im * 1fi; - cs = cs.re + c_im * 1fi; + c = F(c.re, cs_im); + cs = F(cs.re, c_im); } F d = cs - t; d += c; @@ -850,15 +900,15 @@ public: { auto s_re = s.re; auto x_re = x.re; - s = x_re + s.im * 1fi; - x = s_re + x.im * 1fi; + s = F(x_re, s.im); + x = F(s_re, x.im); } if (fabs(s.im) < fabs(x.im)) { auto s_im = s.im; auto x_im = x.im; - s = s.re + x_im * 1fi; - x = x.re + s_im * 1fi; + s = F(s.re, x_im); + x = F(x.re, s_im); } F d = s - t; d += x; @@ -900,12 +950,26 @@ public: s += n; } else + static if (summation == summation.decimal) + { + import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; + if (-n.infinity < n && n < n.infinity) + { + auto decimal = genericBinaryToDecimal(n); + s += decimal; + } + else + { + ss += n; + } + } + else static assert(0); } ///ditto void put(Range)(Range r) - if (isIterable!Range) + if (isIterable!Range && !is(Range : __vector(V[N]), V, size_t N)) { static if (summation == Summation.pairwise && fastPairwise && isDynamicArray!Range) { @@ -936,8 +1000,8 @@ public: else static if (summation == Summation.fast) { - static if (isComplex!T) - F s0 = 0 + 0fi; + static if (isComplex!F) + F s0 = F(0, 0f); else F s0 = 0; foreach (ref elem; r) @@ -964,13 +1028,13 @@ public: else static if (isPointer!Iterator && kind == Contiguous) { - this.put(r.iterator[0 .. r.length]); + this.put(r.field); } else static if (summation == Summation.fast && N == 1) { - static if (isComplex!T) - F s0 = 0 + 0fi; + static if (isComplex!F) + F s0 = F(0, 0f); else F s0 = 0; import mir.algorithm.iteration: reduce; @@ -996,9 +1060,9 @@ public: in { assert(.isFinite(x)); } - body { + do { size_t i; - foreach (y; partials[]) + foreach (y; partials.data[]) { F h = x + y; debug(numeric) assert(.isFinite(h)); @@ -1016,7 +1080,7 @@ public: debug(numeric) assert(.isFinite(l)); if (l) { - partials[i++] = l; + partials.data[i++] = l; } x = h; } @@ -1039,7 +1103,7 @@ public: { debug(mir_sum) { - foreach (y; partials[]) + foreach (y; partials.data[]) { assert(y); assert(y.isFinite); @@ -1048,12 +1112,12 @@ public: import mir.ndslice.slice: sliced; import mir.ndslice.sorting: isSorted; import mir.ndslice.topology: map; - assert(partials[].sliced.map!fabs.isSorted); + assert(partials.data[].sliced.map!fabs.isSorted); } if (s) return s; - auto parts = partials[]; + auto parts = partials.data[]; F y = 0.0; //pick last if (parts.length) @@ -1139,7 +1203,12 @@ public: return s; } else - static assert(0); + static if (summation == Summation.decimal) + { + return cast(T) s + ss; + } + else + static assert(0); } version(none) @@ -1147,7 +1216,7 @@ public: F partialsSum()() const { debug(numeric) partialsDebug; - auto parts = partials[]; + auto parts = partials.data[]; F y = 0.0; //pick last if (parts.length) @@ -1175,8 +1244,7 @@ public: auto ret = typeof(return).init; ret.s = s; ret.o = o; - ret.partials = scopeBuffer(ret.scopeBufferArray); - foreach (p; partials[]) + foreach (p; partials.data[]) { ret.partials.put(p); } @@ -1261,8 +1329,7 @@ public: { static if (summation == Summation.precise) { - partials.free; - partials = scopeBuffer(scopeBufferArray); + partials.reset; s = 0.0; o = 0; if (rhs) put(rhs); @@ -1273,8 +1340,8 @@ public: s = rhs; static if (isComplex!T) { - cs = 0 + 0fi; - ccs = 0 + 0fi; + cs = T(0, 0f); + ccs = T(0.0, 0f); } else { @@ -1287,7 +1354,7 @@ public: { s = rhs; static if (isComplex!T) - c = 0 + 0fi; + c = T(0, 0f); else c = 0.0; } @@ -1296,7 +1363,7 @@ public: { s = rhs; static if (isComplex!T) - c = 0 + 0fi; + c = T(0, 0f); else c = 0.0; } @@ -1318,6 +1385,11 @@ public: s = rhs; } else + static if (summation == summation.decimal) + { + __ctor(rhs); + } + else static assert(0); } @@ -1334,7 +1406,7 @@ public: { s += rhs.s; o += rhs.o; - foreach (f; rhs.partials[]) + foreach (f; rhs.partials.data[]) put(f); } else @@ -1431,7 +1503,7 @@ public: { s -= rhs.s; o -= rhs.o; - foreach (f; rhs.partials[]) + foreach (f; rhs.partials.data[]) put(-f); } else @@ -1475,7 +1547,7 @@ public: } /// - + version(mir_test) @nogc nothrow unittest { @@ -1488,10 +1560,10 @@ public: foreach (e; r2) s2 -= e; s1 -= s2; s1 -= 1.7L.pow(1000); - assert(s1.sum() == -1); + assert(s1.sum == -1); } - + version(mir_test) @nogc nothrow unittest { @@ -1516,7 +1588,7 @@ public: } } - + version(mir_test) @nogc nothrow unittest { @@ -1586,7 +1658,7 @@ public: version(mir_test) unittest { - import mir.functional: RefTuple, refTuple; + import mir.functional: Tuple, tuple; import mir.ndslice.topology: map, iota, retro; import mir.array.allocation: array; import std.math: isInfinity, isFinite, isNaN; @@ -1594,50 +1666,50 @@ unittest Summator!(double, Summation.precise) summator = 0.0; enum double M = (cast(double)2) ^^ (double.max_exp - 1); - RefTuple!(double[], double)[] tests = [ - refTuple(new double[0], 0.0), - refTuple([0.0], 0.0), - refTuple([1e100, 1.0, -1e100, 1e-100, 1e50, -1, -1e50], 1e-100), - refTuple([1e308, 1e308, -1e308], 1e308), - refTuple([-1e308, 1e308, 1e308], 1e308), - refTuple([1e308, -1e308, 1e308], 1e308), - refTuple([M, M, -2.0^^1000], 1.7976930277114552e+308), - refTuple([M, M, M, M, -M, -M, -M], 8.9884656743115795e+307), - refTuple([2.0^^53, -0.5, -2.0^^-54], 2.0^^53-1.0), - refTuple([2.0^^53, 1.0, 2.0^^-100], 2.0^^53+2.0), - refTuple([2.0^^53+10.0, 1.0, 2.0^^-100], 2.0^^53+12.0), - refTuple([2.0^^53-4.0, 0.5, 2.0^^-54], 2.0^^53-3.0), - refTuple([M-2.0^^970, -1, M], 1.7976931348623157e+308), - refTuple([double.max, double.max*2.^^-54], double.max), - refTuple([double.max, double.max*2.^^-53], double.infinity), - refTuple(iota([1000], 1).map!(a => 1.0/a).array , 7.4854708605503451), - refTuple(iota([1000], 1).map!(a => (-1.0)^^a/a).array, -0.69264743055982025), //0.693147180559945309417232121458176568075500134360255254120680... - refTuple(iota([1000], 1).map!(a => 1.0/a).retro.array , 7.4854708605503451), - refTuple(iota([1000], 1).map!(a => (-1.0)^^a/a).retro.array, -0.69264743055982025), - refTuple([double.infinity, -double.infinity, double.nan], double.nan), - refTuple([double.nan, double.infinity, -double.infinity], double.nan), - refTuple([double.infinity, double.nan, double.infinity], double.nan), - refTuple([double.infinity, double.infinity], double.infinity), - refTuple([double.infinity, -double.infinity], double.nan), - refTuple([-double.infinity, 1e308, 1e308, -double.infinity], -double.infinity), - refTuple([M-2.0^^970, 0.0, M], double.infinity), - refTuple([M-2.0^^970, 1.0, M], double.infinity), - refTuple([M, M], double.infinity), - refTuple([M, M, -1], double.infinity), - refTuple([M, M, M, M, -M, -M], double.infinity), - refTuple([M, M, M, M, -M, M], double.infinity), - refTuple([-M, -M, -M, -M], -double.infinity), - refTuple([M, M, -2.^^971], double.max), - refTuple([M, M, -2.^^970], double.infinity), - refTuple([-2.^^970, M, M, -0X0.0000000000001P-0 * 2.^^-1022], double.max), - refTuple([M, M, -2.^^970, 0X0.0000000000001P-0 * 2.^^-1022], double.infinity), - refTuple([-M, 2.^^971, -M], -double.max), - refTuple([-M, -M, 2.^^970], -double.infinity), - refTuple([-M, -M, 2.^^970, 0X0.0000000000001P-0 * 2.^^-1022], -double.max), - refTuple([-0X0.0000000000001P-0 * 2.^^-1022, -M, -M, 2.^^970], -double.infinity), - refTuple([2.^^930, -2.^^980, M, M, M, -M], 1.7976931348622137e+308), - refTuple([M, M, -1e307], 1.6976931348623159e+308), - refTuple([1e16, 1., 1e-16], 10_000_000_000_000_002.0), + Tuple!(double[], double)[] tests = [ + tuple(new double[0], 0.0), + tuple([0.0], 0.0), + tuple([1e100, 1.0, -1e100, 1e-100, 1e50, -1, -1e50], 1e-100), + tuple([1e308, 1e308, -1e308], 1e308), + tuple([-1e308, 1e308, 1e308], 1e308), + tuple([1e308, -1e308, 1e308], 1e308), + tuple([M, M, -2.0^^1000], 1.7976930277114552e+308), + tuple([M, M, M, M, -M, -M, -M], 8.9884656743115795e+307), + tuple([2.0^^53, -0.5, -2.0^^-54], 2.0^^53-1.0), + tuple([2.0^^53, 1.0, 2.0^^-100], 2.0^^53+2.0), + tuple([2.0^^53+10.0, 1.0, 2.0^^-100], 2.0^^53+12.0), + tuple([2.0^^53-4.0, 0.5, 2.0^^-54], 2.0^^53-3.0), + tuple([M-2.0^^970, -1, M], 1.7976931348623157e+308), + tuple([double.max, double.max*2.^^-54], double.max), + tuple([double.max, double.max*2.^^-53], double.infinity), + tuple(iota([1000], 1).map!(a => 1.0/a).array , 7.4854708605503451), + tuple(iota([1000], 1).map!(a => (-1.0)^^a/a).array, -0.69264743055982025), //0.693147180559945309417232121458176568075500134360255254120680... + tuple(iota([1000], 1).map!(a => 1.0/a).retro.array , 7.4854708605503451), + tuple(iota([1000], 1).map!(a => (-1.0)^^a/a).retro.array, -0.69264743055982025), + tuple([double.infinity, -double.infinity, double.nan], double.nan), + tuple([double.nan, double.infinity, -double.infinity], double.nan), + tuple([double.infinity, double.nan, double.infinity], double.nan), + tuple([double.infinity, double.infinity], double.infinity), + tuple([double.infinity, -double.infinity], double.nan), + tuple([-double.infinity, 1e308, 1e308, -double.infinity], -double.infinity), + tuple([M-2.0^^970, 0.0, M], double.infinity), + tuple([M-2.0^^970, 1.0, M], double.infinity), + tuple([M, M], double.infinity), + tuple([M, M, -1], double.infinity), + tuple([M, M, M, M, -M, -M], double.infinity), + tuple([M, M, M, M, -M, M], double.infinity), + tuple([-M, -M, -M, -M], -double.infinity), + tuple([M, M, -2.^^971], double.max), + tuple([M, M, -2.^^970], double.infinity), + tuple([-2.^^970, M, M, -0X0.0000000000001P-0 * 2.^^-1022], double.max), + tuple([M, M, -2.^^970, 0X0.0000000000001P-0 * 2.^^-1022], double.infinity), + tuple([-M, 2.^^971, -M], -double.max), + tuple([-M, -M, 2.^^970], -double.infinity), + tuple([-M, -M, 2.^^970, 0X0.0000000000001P-0 * 2.^^-1022], -double.max), + tuple([-0X0.0000000000001P-0 * 2.^^-1022, -M, -M, 2.^^970], -double.infinity), + tuple([2.^^930, -2.^^980, M, M, M, -M], 1.7976931348622137e+308), + tuple([M, M, -1e307], 1.6976931348623159e+308), + tuple([1e16, 1., 1e-16], 10_000_000_000_000_002.0), ]; foreach (i, test; tests) { @@ -1652,7 +1724,7 @@ unittest } } -/** +/++ Sums elements of `r`, which must be a finite iterable. @@ -1661,26 +1733,128 @@ value, but its type will be used if it is not specified. Note that these specialized summing algorithms execute more primitive operations than vanilla summation. Therefore, if in certain cases maximum speed is required -at expense of precision, one can use $(XREF, numeric_summation, Summation.fast). +at expense of precision, one can use $(LREF Summation.fast). Returns: The sum of all the elements in the range r. - -See_Also: $(XREFMODULE, numeric_summation) contains detailed documentation and examples about available summation algorithms. - */ ++/ template sum(F, Summation summation = Summation.appropriate) - if (isFloatingPoint!F && isMutable!F) + if (isMutable!F) { + /// + template sum(Range) + if (isIterable!Range && isMutable!Range) + { + import core.lifetime: move; + + /// + F sum(Range r) + { + static if (isComplex!F && (summation == Summation.precise || summation == Summation.decimal)) + { + return sum(r, summationInitValue!F); + } + else + { + static if (summation == Summation.decimal) + { + Summator!(F, summation) sum = void; + sum = 0; + } + else + { + Summator!(F, ResolveSummationType!(summation, Range, sumType!Range)) sum; + } + sum.put(r.move); + return sum.sum; + } + } + + /// + F sum(Range r, F seed) + { + static if (isComplex!F && (summation == Summation.precise || summation == Summation.decimal)) + { + alias T = typeof(F.init.re); + static if (summation == Summation.decimal) + { + Summator!(T, summation) sumRe = void; + sumRe = seed.re; + + Summator!(T, summation) sumIm = void; + sumIm = seed.im; + } + else + { + auto sumRe = Summator!(T, Summation.precise)(seed.re); + auto sumIm = Summator!(T, Summation.precise)(seed.im); + } + import mir.ndslice.slice: isSlice; + static if (isSlice!Range) + { + import mir.algorithm.iteration: each; + r.each!((auto ref elem) + { + sumRe.put(elem.re); + sumIm.put(elem.im); + }); + } + else + { + foreach (ref elem; r) + { + sumRe.put(elem.re); + sumIm.put(elem.im); + } + } + return F(sumRe.sum, sumIm.sum); + } + else + { + static if (summation == Summation.decimal) + { + Summator!(F, summation) sum = void; + sum = seed; + } + else + { + auto sum = Summator!(F, ResolveSummationType!(summation, Range, F))(seed); + } + sum.put(r.move); + return sum.sum; + } + } + } + + /// template sum(Range) + if (isIterable!Range && !isMutable!Range) { + /// F sum(Range r) { - return SummationAlgo!(summation, Range, F)(r); + return .sum!(F, summation)(r.lightConst); } + /// F sum(Range r, F seed) { - return SummationAlgo!(summation, Range, F)(r, seed); + return .sum!(F, summation)(r.lightConst, seed); + } + } + + /// + F sum(scope const F[] r...) + { + static if (isComplex!F && (summation == Summation.precise || summation == Summation.decimal)) + { + return sum(r, summationInitValue!F); + } + else + { + Summator!(F, ResolveSummationType!(summation, const(F)[], F)) sum; + sum.put(r); + return sum.sum; } } } @@ -1688,20 +1862,50 @@ template sum(F, Summation summation = Summation.appropriate) ///ditto template sum(Summation summation = Summation.appropriate) { - auto sum(Range)(Range r) + /// + sumType!Range sum(Range)(Range r) + if (isIterable!Range && isMutable!Range) + { + import core.lifetime: move; + alias F = typeof(return); + alias s = .sum!(F, ResolveSummationType!(summation, Range, F)); + return s(r.move); + } + + /// + F sum(Range, F)(Range r, F seed) + if (isIterable!Range && isMutable!Range) + { + import core.lifetime: move; + alias s = .sum!(F, ResolveSummationType!(summation, Range, F)); + return s(r.move, seed); + } + + /// + sumType!Range sum(Range)(Range r) + if (isIterable!Range && !isMutable!Range) { - return SummationAlgo!(summation, Range, sumType!Range)(r); + return .sum!(typeof(return), summation)(r.lightConst); } + /// F sum(Range, F)(Range r, F seed) + if (isIterable!Range && !isMutable!Range) { - return SummationAlgo!(summation, Range, F)(r, seed); + return .sum!(F, summation)(r.lightConst, seed); + } + + /// + sumType!T sum(T)(scope const T[] ar...) + { + alias F = typeof(return); + return .sum!(F, ResolveSummationType!(summation, F[], F))(ar); } } ///ditto template sum(F, string summation) - if (isFloatingPoint!F && isMutable!F) + if (isMutable!F) { mixin("alias sum = .sum!(F, Summation." ~ summation ~ ");"); } @@ -1712,7 +1916,59 @@ template sum(string summation) mixin("alias sum = .sum!(Summation." ~ summation ~ ");"); } +private static immutable jaggedMsg = "sum: each slice should have the same length"; +version(D_Exceptions) + static immutable jaggedException = new Exception(jaggedMsg); + +/++ +Sum slices with a naive algorithm. ++/ +template sumSlices() +{ + import mir.primitives: DeepElementType; + import mir.ndslice.slice: Slice, SliceKind, isSlice; + /// + auto sumSlices(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) sliceOfSlices) + if (isSlice!(DeepElementType!(Slice!(Iterator, 1, kind)))) + { + import mir.ndslice.topology: as; + import mir.ndslice.allocation: slice; + alias T = Unqual!(DeepElementType!(DeepElementType!(Slice!(Iterator, 1, kind)))); + import mir.ndslice: slice; + if (sliceOfSlices.length == 0) + return typeof(slice(as!T(sliceOfSlices.front))).init; + auto ret = slice(as!T(sliceOfSlices.front)); + sliceOfSlices.popFront; + foreach (sl; sliceOfSlices) + { + if (sl.length != ret.length) + { + version (D_Exceptions) + { import mir.exception : toMutable; throw jaggedException.toMutable; } + else + assert(0); + } + ret[] += sl[]; + } + return ret; + } +} + +/// +version(mir_test) +unittest +{ + import mir.ndslice.topology: map, byDim; + import mir.ndslice.slice: sliced; + + auto ar = [[1, 2, 3], [10, 20, 30]]; + assert(ar.map!sliced.sumSlices == [11, 22, 33]); + import mir.ndslice.fuse: fuse; + auto a = [[[1.2], [2.1]], [[4.1], [5.2]]].fuse; + auto s = a.byDim!0.sumSlices; + assert(s == [[5.3], [7.300000000000001]]); +} version(mir_test) @safe pure nothrow unittest @@ -1815,6 +2071,31 @@ unittest } } +version(mir_test) +unittest +{ + assert(sum(1) == 1); + assert(sum(1, 2, 3) == 6); + assert(sum(1.0, 2.0, 3.0) == 6); +} + +version(mir_test) +unittest +{ + assert(sum!float(1) == 1f); + assert(sum!float(1, 2, 3) == 6f); + assert(sum!float(1.0, 2.0, 3.0) == 6f); +} + +version(mir_test) +unittest +{ + import mir.complex: Complex; + + assert(sum(Complex!float(1.0, 1.0), Complex!float(2.0, 2.0), Complex!float(3.0, 3.0)) == Complex!float(6.0, 6.0)); + assert(sum!(Complex!float)(Complex!float(1.0, 1.0), Complex!float(2.0, 2.0), Complex!float(3.0, 3.0)) == Complex!float(6.0, 6.0)); +} + version(LDC) version(X86_Any) version(mir_test) @@ -1864,78 +2145,67 @@ unittest } } -/++ -Precise summation. -+/ -private F sumPrecise(Range, F)(Range r, F seed = summationInitValue!F) - if (isFloatingPoint!F || isComplex!F) +// Confirm sum works for Slice!(const(double)*, 1)) +version(mir_test) +@safe pure nothrow +unittest { - static if (isFloatingPoint!F) - { - auto sum = Summator!(F, Summation.precise)(seed); - sum.put(r); - return sum.sum; - } - else - { - alias T = typeof(F.init.re); - auto sumRe = Summator!(T, Summation.precise)(seed.re); - auto sumIm = Summator!(T, Summation.precise)(seed.im); - import mir.ndslice.slice: isSlice; - static if (isSlice!Range) - { - import mir.algorithm.iteration: each; - r.each!((auto ref elem) - { - sumRe.put(elem.re); - sumIm.put(elem.im); - }); - } - else - { - foreach (ref elem; r) - { - sumRe.put(elem.re); - sumIm.put(elem.im); - } - } - return sumRe.sum + sumIm.sum * 1fi; - } + import mir.ndslice.slice: sliced; + double[] x = [1.0, 2, 3]; + auto y = x.sliced; + auto z = y.toConst; + assert(z.sum == 6); + assert(z.sum(0.0) == 6); + assert(z.sum!double == 6); + assert(z.sum!double(0.0) == 6); } -private template SummationAlgo(Summation summation, Range, F) +// Confirm sum works for const(Slice!(double*, 1)) +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + double[] x = [1.0, 2, 3]; + auto y = x.sliced; + const z = y; + assert(z.sum == 6); + assert(z.sum(0.0) == 6); + assert(z.sum!double == 6); + assert(z.sum!double(0.0) == 6); +} + +// Confirm sum works for const(Slice!(const(double)*, 1)) +version(mir_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + double[] x = [1.0, 2, 3]; + auto y = x.sliced; + const z = y.toConst; + assert(z.sum == 6); + assert(z.sum(0.0) == 6); + assert(z.sum!double == 6); + assert(z.sum!double(0.0) == 6); +} + +package(mir) +template ResolveSummationType(Summation summation, Range, F) { - static if (summation == Summation.precise) - alias SummationAlgo = sumPrecise!(Range, F); - else static if (summation == Summation.appropriate) { static if (isSummable!(Range, F)) - alias SummationAlgo = SummationAlgo!(Summation.pairwise, Range, F); + enum ResolveSummationType = Summation.pairwise; else static if (is(F == class) || is(F == struct) || is(F == interface)) - alias SummationAlgo = SummationAlgo!(Summation.naive, Range, F); + enum ResolveSummationType = Summation.naive; else - alias SummationAlgo = SummationAlgo!(Summation.fast, Range, F); + enum ResolveSummationType = Summation.fast; } else { - F SummationAlgo(Range r) - { - static if (__traits(compiles, {Summator!(F, summation) sum;})) - Summator!(F, summation) sum; - else - auto sum = Summator!(F, summation)(summationInitValue!F); - sum.put(r); - return sum.sum; - } - - F SummationAlgo(Range r, F s) - { - auto sum = Summator!(F, summation)(s); - sum.put(r); - return sum.sum; - } + enum ResolveSummationType = summation; } } @@ -1964,14 +2234,34 @@ private T summationInitValue(T)() } } -private template sumType(Range) +package(mir) +template elementType(T) { import mir.ndslice.slice: isSlice, DeepElementType; - static if (isSlice!Range) - alias T = Unqual!(DeepElementType!(Range.This)); + import std.traits: Unqual, ForeachType; + + static if (isIterable!T) { + static if (isSlice!T) + alias elementType = Unqual!(DeepElementType!(T.This)); + else + alias elementType = Unqual!(ForeachType!T); + } else { + alias elementType = Unqual!T; + } +} + +package(mir) +template sumType(Range) +{ + alias T = elementType!Range; + + static if (__traits(compiles, { + auto a = T.init + T.init; + a += T.init; + })) + alias sumType = typeof(T.init + T.init); else - alias T = Unqual!(ForeachType!Range); - alias sumType = typeof(T.init + T.init); + static assert(0, "sumType: Can't sum elements of type " ~ T.stringof); } /++ diff --git a/source/mir/ndslice/allocation.d b/source/mir/ndslice/allocation.d index efe030de..b5253fd0 100644 --- a/source/mir/ndslice/allocation.d +++ b/source/mir/ndslice/allocation.d @@ -43,9 +43,9 @@ $(T2 stdcUninitAlignedSlice, Allocates an uninitialized aligned slice using CRun $(T2 stdcFreeAlignedSlice, Frees memory using CRuntime) ) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -53,7 +53,7 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.ndslice.allocation; -import mir.math.common: optmath; +import mir.math.common: fmamath; import mir.ndslice.concatenation; import mir.ndslice.field: BitField; import mir.ndslice.internal; @@ -63,7 +63,7 @@ import mir.rc.array; import std.traits; import std.meta: staticMap; -@optmath: +@fmamath: /++ Allocates an n-dimensional reference-counted (thread-safe) slice. @@ -88,8 +88,7 @@ Slice!(RCI!T, N) { auto ret = (()@trusted => mininitRcslice!T(lengths))(); ret.lightScope.field[] = init; - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - return move(ret); + return ret; } /// ditto @@ -103,12 +102,25 @@ auto rcslice(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice import mir.algorithm.iteration: each; each!(emplaceRef!E)(result.lightScope, slice.lightScope); - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - return move(*(() @trusted => cast(Slice!(RCI!E, N)*) &result)()); + return *(() @trusted => cast(Slice!(RCI!E, N)*) &result)(); +} + +/// ditto +auto rcslice(T)(T[] array) +{ + return rcslice(array.sliced); +} + +/// ditto +auto rcslice(T, I)(I[] array) + if (!isImplicitlyConvertible!(I[], T[])) +{ + import mir.ndslice.topology: as; + return rcslice(array.sliced.as!T); } /// -version(mir_test) +version(mir_ndslice_test) @safe pure nothrow @nogc unittest { import mir.ndslice.slice: Slice; @@ -124,7 +136,7 @@ version(mir_test) } /// -version(mir_test) +version(mir_ndslice_test) @safe pure nothrow @nogc unittest { import mir.ndslice.slice: Slice; @@ -142,15 +154,15 @@ auto rcslice(size_t dim, Slices...)(Concatenation!(dim, Slices) concatenation) { alias T = Unqual!(concatenation.DeepElement); auto ret = (()@trusted => mininitRcslice!T(concatenation.shape))(); - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; ret.lightScope.opIndexAssign(concatenation); return ret; } /// -version(mir_test) +version(mir_ndslice_test) @safe pure nothrow @nogc unittest { + import mir.rc.array: RCI; import mir.ndslice.slice: Slice; import mir.ndslice.topology : iota; import mir.ndslice.concatenation; @@ -160,6 +172,34 @@ version(mir_test) static assert(is(typeof(tensor) == Slice!(RCI!ptrdiff_t, 2))); } +/++ +Allocates an n-dimensional reference-counted (thread-safe) slice without memory initialisation. +Params: + lengths = List of lengths for each dimension. +Returns: + n-dimensional slice ++/ +Slice!(RCI!T, N) + uninitRCslice(T, size_t N)(size_t[N] lengths...) +{ + immutable len = lengths.lengthsProduct; + auto _lengths = lengths; + return typeof(return)(_lengths, RCI!T(RCArray!T(len, false))); +} + +/// +version(mir_ndslice_test) +@safe pure nothrow @nogc unittest +{ + import mir.ndslice.slice: Slice; + import mir.rc.array: RCI; + auto tensor = uninitRCslice!int(5, 6, 7); + tensor[] = 1; + assert(tensor.length == 5); + assert(tensor.elementCount == 5 * 6 * 7); + static assert(is(typeof(tensor) == Slice!(RCI!int, 3))); +} + /++ Allocates a bitwise packed n-dimensional reference-counted (thread-safe) boolean slice. Params: @@ -179,7 +219,7 @@ Slice!(FieldIterator!(BitField!(RCI!size_t)), N) bitRcslice(size_t N)(size_t[N] /// 1D @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto bitarray = 100.bitRcslice; // allocates 16 bytes total (plus RC context) assert(bitarray.shape == cast(size_t[1])[100]); @@ -190,7 +230,7 @@ version(mir_test) unittest /// 2D @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto bitmatrix = bitRcslice(20, 6); // allocates 16 bytes total (plus RC context) assert(bitmatrix.shape == cast(size_t[2])[20, 6]); @@ -214,7 +254,7 @@ Slice!(RCI!T, N) mininitRcslice(T, size_t N)(size_t[N] lengths...) } /// -version(mir_test) +version(mir_ndslice_test) pure nothrow @nogc unittest { import mir.ndslice.slice: Slice; @@ -277,7 +317,7 @@ template slice(Args...) } /// -version(mir_test) +version(mir_ndslice_test) @safe pure nothrow unittest { import mir.ndslice.slice: Slice; @@ -289,13 +329,12 @@ version(mir_test) } /// 2D DataFrame example -version(mir_test) +version(mir_ndslice_test) @safe pure unittest { import mir.ndslice.slice; import mir.ndslice.allocation: slice; - - import std.datetime.date; + import mir.date: Date; auto dataframe = slice!(double, Date, string)(4, 3); assert(dataframe.length == 4); @@ -371,7 +410,7 @@ auto slice(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) } /// -version(mir_test) +version(mir_ndslice_test) @safe pure nothrow unittest { auto tensor = slice([2, 3], 5); @@ -397,7 +436,7 @@ auto slice(size_t dim, Slices...)(Concatenation!(dim, Slices) concatenation) } /// -version(mir_test) +version(mir_ndslice_test) @safe pure nothrow unittest { import mir.ndslice.slice: Slice; @@ -427,7 +466,7 @@ Slice!(FieldIterator!(BitField!(size_t*)), N) bitSlice(size_t N)(size_t[N] lengt } /// 1D -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { auto bitarray = bitSlice(100); // allocates 16 bytes total assert(bitarray.shape == [100]); @@ -437,7 +476,7 @@ Slice!(FieldIterator!(BitField!(size_t*)), N) bitSlice(size_t N)(size_t[N] lengt } /// 2D -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { auto bitmatrix = bitSlice(20, 6); // allocates 16 bytes total assert(bitmatrix.shape == [20, 6]); @@ -458,11 +497,15 @@ auto uninitSlice(T, size_t N)(size_t[N] lengths...) immutable len = lengths.lengthsProduct; import std.array : uninitializedArray; auto arr = uninitializedArray!(T[])(len); + version (mir_secure_memory) + {()@trusted{ + (cast(ubyte[])arr)[] = 0; + }();} return arr.sliced(lengths); } /// -version(mir_test) +version(mir_ndslice_test) @safe pure nothrow unittest { import mir.ndslice.slice: Slice; @@ -492,7 +535,7 @@ auto uninitAlignedSlice(T, size_t N)(size_t[N] lengths, uint alignment) @system } /// -version(mir_test) +version(mir_ndslice_test) @system pure nothrow unittest { import mir.ndslice.slice: Slice; @@ -557,7 +600,7 @@ auto makeSlice(Allocator, Iterator, size_t N, SliceKind kind) } /// Initialization with default value -version(mir_test) +version(mir_ndslice_test) @nogc unittest { import std.experimental.allocator; @@ -577,7 +620,7 @@ version(mir_test) Mallocator.instance.dispose(ar2); } -version(mir_test) +version(mir_ndslice_test) @nogc unittest { import std.experimental.allocator; @@ -608,6 +651,10 @@ makeUninitSlice(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] leng auto mem = alloc.allocate(len * T.sizeof); if (mem.length == 0) assert(0); auto array = () @trusted { return cast(T[]) mem; }(); + version (mir_secure_memory) + {() @trusted { + (cast(ubyte[])array)[] = 0; + }();} return array.sliced(lengths); } else @@ -617,7 +664,7 @@ makeUninitSlice(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] leng } /// -version(mir_test) +version(mir_ndslice_test) @system @nogc unittest { import std.experimental.allocator; @@ -644,17 +691,17 @@ auto ndarray(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice import mir.array.allocation : array; static if (slice.N == 1) { - return array(slice); + return slice.array; } else { import mir.ndslice.topology: ipack, map; - return array(slice.ipack!1.map!(a => .ndarray(a))); + return slice.ipack!1.map!(.ndarray).array; } } /// -version(mir_test) +version(mir_ndslice_test) @safe pure nothrow unittest { import mir.ndslice.topology : iota; @@ -690,7 +737,7 @@ auto makeNdarray(T, Allocator, Iterator, size_t N, SliceKind kind)(auto ref Allo } /// -version(mir_test) +version(mir_ndslice_test) @nogc unittest { import std.experimental.allocator; @@ -750,7 +797,7 @@ L: } /// -version(mir_test) +version(mir_ndslice_test) @safe pure unittest { int err; @@ -763,7 +810,7 @@ version(mir_test) } /// Slice from ndarray -version(mir_test) +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice, shape; @@ -774,7 +821,7 @@ unittest assert(s == array); } -version(mir_test) +version(mir_ndslice_test) @safe pure unittest { int err; @@ -783,7 +830,7 @@ version(mir_test) assert(shape[1] == 0); } -version(mir_test) +version(mir_ndslice_test) nothrow unittest { import mir.ndslice.allocation; @@ -818,7 +865,13 @@ Slice!(T*, N) stdcUninitSlice(T, size_t N)(size_t[N] lengths...) { import core.stdc.stdlib: malloc; immutable len = lengths.lengthsProduct; - auto ptr = len ? cast(T*) malloc(len * T.sizeof) : null; + auto p = malloc(len * T.sizeof); + if (p is null) assert(0); + version (mir_secure_memory) + { + (cast(ubyte*)p)[0 .. len * T.sizeof] = 0; + } + auto ptr = len ? cast(T*) p : null; return ptr.sliced(lengths); } @@ -854,11 +907,15 @@ See_also: void stdcFreeSlice(T, size_t N)(Slice!(T*, N) slice) { import core.stdc.stdlib: free; + version (mir_secure_memory) + { + (cast(ubyte[])slice.field)[] = 0; + } slice._iterator.free; } /// -version(mir_test) +version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; @@ -888,13 +945,18 @@ auto stdcUninitAlignedSlice(T, size_t N)(size_t[N] lengths, uint alignment) @sys immutable len = lengths.lengthsProduct; import mir.internal.memory: alignedAllocate; auto arr = (cast(T*)alignedAllocate(len * T.sizeof, alignment))[0 .. len]; + version (mir_secure_memory) + { + (cast(ubyte[])arr)[] = 0; + } return arr.sliced(lengths); } /// -version(mir_test) +version(mir_ndslice_test) @system pure nothrow unittest { + import mir.ndslice.slice: Slice; auto tensor = stdcUninitAlignedSlice!double([5, 6, 7], 64); assert(tensor.length == 5); assert(tensor.elementCount == 5 * 6 * 7); @@ -913,5 +975,9 @@ See_also: void stdcFreeAlignedSlice(T, size_t N)(Slice!(T*, N) slice) { import mir.internal.memory: alignedFree; + version (mir_secure_memory) + { + (cast(ubyte[])slice.field)[] = 0; + } slice._iterator.alignedFree; } diff --git a/source/mir/ndslice/chunks.d b/source/mir/ndslice/chunks.d index a37a21ae..09e865e4 100644 --- a/source/mir/ndslice/chunks.d +++ b/source/mir/ndslice/chunks.d @@ -6,9 +6,9 @@ $(LREF Chunks) structure is multidimensional random access range with slicing. $(SUBREF slice, slicedField), $(SUBREF slice, slicedNdField) can be used to construct ndslice view on top of $(LREF Chunks). -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -17,7 +17,7 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) module mir.ndslice.chunks; import mir.internal.utility; -import mir.math.common: optmath; +import mir.math.common: fmamath; import mir.ndslice.internal; import mir.ndslice.iterator: IotaIterator; import mir.ndslice.slice; @@ -30,7 +30,7 @@ Creates $(LREF Chunks). Params: Dimensions = compile time list of dimensions to chunk - + See_also: $(SUBREF topology, blocks) $(SUBREF fuse, fuseCells) +/ template chunks(Dimensions...) @@ -72,7 +72,7 @@ Chunks!([0], Iterator, N, kind) chunks(Iterator, size_t N, SliceKind kind)(Slice /// 1Dx1D -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.chunks: chunks, isChunks; import mir.ndslice.topology: iota; @@ -82,7 +82,7 @@ Chunks!([0], Iterator, N, kind) chunks(Iterator, size_t N, SliceKind kind)(Slice // 0 1 2 | 3 4 5 | 6 7 8 | 9 10 auto ch = sl.chunks(3); - static assert(isChunks!(typeof(ch)) == [0]); // isChunks returns dimension indexes + static assert(isChunks!(typeof(ch)) == [0]); // isChunks returns dimension indices assert(ch.length == 4); assert(ch.shape == cast(size_t[1])[4]); @@ -112,7 +112,7 @@ Chunks!([0], Iterator, N, kind) chunks(Iterator, size_t N, SliceKind kind)(Slice /// 2Dx2D @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.chunks: chunks, isChunks; import mir.ndslice.topology: iota; @@ -130,7 +130,7 @@ version(mir_test) unittest // 100 101 102 103 104 105 106 107 108 109 auto sl = iota(11, 10); // [0, 1, .. 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 | @@ -145,7 +145,7 @@ version(mir_test) unittest // |----------------| |----------------| |--------| // | 90 91 92 93 | | 94 95 96 97 | | 98 99 | // |100 101 102 103 | |104 105 106 107 | |108 109 | - // ---------------- ---------------- -------- + // ---------------- ---------------- -------- // Chunk columns first, then blocks rows. auto ch = sl.chunks!(1, 0)(4, 3); @@ -160,7 +160,7 @@ version(mir_test) unittest assert (ch[$ - 1, $ - 1] == [[98, 99], [108, 109]]); - static assert(isChunks!(typeof(ch)) == [1, 0]); // isChunks returns dimension indexes + static assert(isChunks!(typeof(ch)) == [1, 0]); // isChunks returns dimension indices assert(ch.length == 3); assert(ch.length!1 == 4); @@ -179,7 +179,7 @@ version(mir_test) unittest } /// 1Dx2D -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.chunks: chunks, isChunks; import mir.ndslice.topology: iota; @@ -190,12 +190,12 @@ version(mir_test) unittest // 30 31 32 33 34 35 36 37 38 39 auto sl = iota(4, 10); // [0, 1, .. 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 | // | 30 31 32 33 | | 34 35 36 37 | | 38 39 | - // ---------------- ---------------- -------- + // ---------------- ---------------- -------- // Chunk columns auto ch = sl.chunks!1(4); @@ -209,7 +209,7 @@ version(mir_test) unittest } // conversion to ndslice -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.slice : slicedField; import mir.ndslice.chunks: chunks; @@ -229,7 +229,7 @@ version(mir_test) unittest +/ struct Chunks(size_t[] dimensions, Iterator, size_t N = 1, SliceKind kind = Contiguous) { -@optmath: +@fmamath: /++ Chunk shape. @@ -385,7 +385,7 @@ struct Chunks(size_t[] dimensions, Iterator, size_t N = 1, SliceKind kind = Cont assert(j <= length!dimensionIndex, "Chunks.opSlice!" ~ dimensionIndex.stringof ~ errorMsg); } - body + do { return typeof(return)(j - i, typeof(return).Iterator(i)); } @@ -401,7 +401,7 @@ struct Chunks(size_t[] dimensions, Iterator, size_t N = 1, SliceKind kind = Cont assert(j <= length!dimensionIndex, "Chunks.opSlice!" ~ dimensionIndex.stringof ~ errorMsg); } - body + do { return typeof(return)(i, j); } @@ -519,7 +519,7 @@ struct Chunks(size_t[] dimensions, Iterator, size_t N = 1, SliceKind kind = Cont assert(sl.i <= _slice._lengths[dimension]); assert(sl.chunkLength == _chunkLengths[dimensionIndex]); assert(sl.length == _slice._lengths[dimension]); - + enum dimension = dimensions[dimensionIndex]; auto chl = _chunkLengths[dimensionIndex]; auto len = sl.i * chl; @@ -554,7 +554,7 @@ struct ChunksDollar() /++ Checks if T is $(LREF Chunks) type. Returns: - array of dimension indexes. + array of dimension indices. +/ template isChunks(T) { @@ -565,7 +565,7 @@ template isChunks(T) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.chunks: chunks, isChunks; import mir.ndslice.topology: iota; @@ -593,7 +593,7 @@ in assert(follower._chunkLengths[dimmensionIndex] == master._chunkLengths[dimmensionIndex]); } } -body +do { master._slice.popFrontExactly!(isChunks!Master[dimmensionIndex])(master._chunkLengths[dimmensionIndex]); foreach (i, ref follower; followers) @@ -613,7 +613,7 @@ body } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.chunks: chunks; import mir.ndslice.topology: iota; diff --git a/source/mir/ndslice/concatenation.d b/source/mir/ndslice/concatenation.d index 401efbdd..396e8200 100644 --- a/source/mir/ndslice/concatenation.d +++ b/source/mir/ndslice/concatenation.d @@ -19,9 +19,9 @@ $(T2 padWrap, Pads with the wrap of the slice along the axis. The first values a ) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2017-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki See_also: $(SUBMODULE fuse) submodule. @@ -36,16 +36,16 @@ import std.traits; import std.meta; import mir.internal.utility; -import mir.math.common: optmath; +import mir.math.common: fmamath; import mir.ndslice.internal; import mir.ndslice.slice; import mir.primitives; -@optmath: +@fmamath: private template _expose(size_t maxN, size_t dim) { - static @optmath auto _expose(S)(S s) + static @fmamath auto _expose(S)(S s) { static if (s.N == maxN) { @@ -79,7 +79,7 @@ private template _Expose(size_t maxN, size_t dim) /++ Creates a $(LREF Concatenation) view of multiple slices. -Can be used in combination with itself, $(LREF until), $(SUBREF, allocation, slice), +Can be used in combination with itself, $(LREF until), $(SUBREF allocation, slice), and $(SUBREF slice, Slice) assignment. Params: @@ -89,29 +89,39 @@ Returns: $(LREF Concatenation). +/ auto concatenation(size_t dim = 0, Slices...)(Slices slices) { - import mir.algorithm.iteration: reduce; - import mir.utility: min, max; - enum NOf(S) = S.N; - enum NArray = [staticMap!(NOf, Slices)]; - enum minN = size_t.max.reduce!min(NArray); - enum maxN = size_t.min.reduce!max(NArray); - static if (minN == maxN) + static if (allSatisfy!(templateOr!(isSlice, isConcatenation), Slices)) { - return Concatenation!(dim, Slices)(slices); + import mir.algorithm.iteration: reduce; + import mir.utility: min, max; + enum NOf(S) = S.N; + enum NArray = [staticMap!(NOf, Slices)]; + enum minN = size_t.max.reduce!min(NArray); + enum maxN = size_t.min.reduce!max(NArray); + static if (minN == maxN) + { + import core.lifetime: forward; + return Concatenation!(dim, Slices)(forward!slices); + } + else + { + import core.lifetime: move; + static assert(minN + 1 == maxN); + alias S = staticMap!(_Expose!(maxN, dim), Slices); + Concatenation!(dim, S) ret; + foreach (i, ref e; ret._slices) + e = _expose!(maxN, dim)(move(slices[i])); + return ret; + } } else { - static assert(minN + 1 == maxN); - alias S = staticMap!(_Expose!(maxN, dim), Slices); - S s; - foreach (i, ref e; s) - e = _expose!(maxN, dim)(slices[i]); - return Concatenation!(dim, S)(s); + import core.lifetime: forward; + return .concatenation(toSlices!(forward!slices)); } } /// Concatenation of slices with different dimmensions. -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: repeat, iota; @@ -123,21 +133,30 @@ version(mir_test) unittest // 4 5 6 auto matrix = iota([2, 3], 1); - assert(concatenation(vector, matrix).slice == [ + auto c0 = concatenation(vector, matrix); + + assert(c0.slice == [ [0, 0, 0], [1, 2, 3], [4, 5, 6], ]); vector.popFront; - assert(concatenation!1(vector, matrix).slice == [ + auto c1 = concatenation!1(vector, matrix); + assert(c1.slice == [ [0, 1, 2, 3], [0, 4, 5, 6], ]); + + auto opIndexCompiles0 = c0[]; + auto opIndexCompiles1 = c1[]; + + auto opIndexCompilesForConst0 = (cast(const)c0)[]; + auto opIndexCompilesForConst1 = (cast(const)c1)[]; } /// Multidimensional -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -185,7 +204,7 @@ version(mir_test) unittest } /// 1D -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -211,28 +230,6 @@ version(mir_test) unittest assert(s.slicedNdField == s.length.iota); } -template frontOf(size_t N) -{ - static if (N == 0) - enum frontOf = ""; - else - { - enum i = N - 1; - enum frontOf = frontOf!i ~ "slices[" ~ i.stringof ~ "].front!d, "; - } -} - -template frontOfSt(size_t N) -{ - static if (N == 0) - enum frontOfSt = ""; - else - { - enum i = N - 1; - enum frontOfSt = frontOfSt!i ~ "st._slices[" ~ i.stringof ~ "].front!d, "; - } -} - /// enum bool isConcatenation(T) = is(T : Concatenation!(dim, Slices), size_t dim, Slices...); /// @@ -242,7 +239,17 @@ enum size_t concatenationDimension(T : Concatenation!(dim, Slices), size_t dim, struct Concatenation(size_t dim, Slices...) if (Slices.length > 1) { - @optmath: + @fmamath: + + + /// Slices and sub-concatenations + Slices _slices; + + package enum N = typeof(Slices[0].shape).length; + + static assert(dim < N); + + alias DeepElement = CommonType!(staticMap!(DeepElementType, Slices)); /// auto lightConst()() const @property @@ -262,17 +269,8 @@ struct Concatenation(size_t dim, Slices...) return mixin("Concatenation!(dim, staticMap!(LightImmutableOf, Slices))(%(_slices[%s].lightImmutable,%)].lightImmutable)".format(_slices.length.iota)); } - /// Slices and sub-concatenations - Slices _slices; - - package enum N = typeof(Slices[0].shape).length; - - static assert(dim < N); - - alias DeepElement = CommonType!(staticMap!(DeepElementType, Slices)); - /// Length primitive - size_t length(size_t d = 0)() const @property + size_t length(size_t d = 0)() scope const @property { static if (d == dim) { @@ -288,7 +286,7 @@ struct Concatenation(size_t dim, Slices...) } /// Total elements count in the concatenation. - size_t elementCount()() const @property + size_t elementCount()() scope const @property { size_t count = 1; foreach(i; Iota!N) @@ -296,11 +294,8 @@ struct Concatenation(size_t dim, Slices...) return count; } - deprecated("use elementCount instead") - alias elementsCount = elementCount; - /// Shape of the concatenation. - size_t[N] shape()() const @property + size_t[N] shape()() scope const @property { typeof(return) ret; foreach(i; Iota!N) @@ -309,14 +304,14 @@ struct Concatenation(size_t dim, Slices...) } /// Multidimensional input range primitives - bool empty(size_t d = 0)() const @property + bool empty(size_t d = 0)() scope const @property { static if (d == dim) { foreach(ref slice; _slices) - if (slice.empty!d) - return true; - return false; + if (!slice.empty!d) + return false; + return true; } else { @@ -325,7 +320,7 @@ struct Concatenation(size_t dim, Slices...) } /// ditto - void popFront(size_t d = 0)() + void popFront(size_t d = 0)() scope { static if (d == dim) { @@ -345,7 +340,7 @@ struct Concatenation(size_t dim, Slices...) } /// ditto - auto front(size_t d = 0)() + auto ref front(size_t d = 0)() return scope { static if (d == dim) { @@ -359,24 +354,39 @@ struct Concatenation(size_t dim, Slices...) } else { + import mir.ndslice.internal: frontOfDim; enum elemDim = d < dim ? dim - 1 : dim; - alias slices = _slices; - return mixin(`concatenation!elemDim(` ~ frontOf!(Slices.length) ~ `)`); + return concatenation!elemDim(frontOfDim!(d, _slices)); } } /// Simplest multidimensional random access primitive - auto opIndex()(size_t[N] indexes...) + auto opIndex()(size_t[N] indices...) { foreach(i, ref slice; _slices[0 .. $-1]) { - ptrdiff_t diff = indexes[dim] - slice.length!dim; + ptrdiff_t diff = indices[dim] - slice.length!dim; if (diff < 0) - return slice[indexes]; - indexes[dim] = diff; + return slice[indices]; + indices[dim] = diff; } - assert(indexes[dim] < _slices[$-1].length!dim); - return _slices[$-1][indexes]; + assert(indices[dim] < _slices[$-1].length!dim); + return _slices[$-1][indices]; + } + + ref opIndex()() scope return + { + return this; + } + + auto opIndex()() const return scope + { + import mir.ndslice.topology: iota; + import mir.qualifier: LightConstOf, lightConst; + import std.format: format; + alias Ret = .Concatenation!(dim, staticMap!(LightConstOf, Slices)); + enum ret = "Ret(%(lightConst(_slices[%s]),%)]))".format(_slices.length.iota); + return mixin(ret); } } @@ -402,8 +412,10 @@ auto applyFront(size_t d = 0, alias fun, size_t dim, Slices...)(Concatenation!(d } else { + import mir.ndslice.internal: frontOfDim; enum elemDim = d < dim ? dim - 1 : dim; - return fun(mixin(`concatenation!elemDim(` ~ frontOfSt!(Slices.length) ~ `)`)); + auto slices = st._slices; + return fun(concatenation!elemDim(frontOfDim!(d, slices))); } } @@ -428,7 +440,7 @@ auto pad(string direction = "both", S, T, size_t N)(S s, T value, size_t[N] leng } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -441,7 +453,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -476,7 +488,7 @@ See_also: $(LREF ._concatenation) examples. template pad(size_t[] dimensions, string[] directions) if (dimensions.length && dimensions.length == directions.length) { - @optmath: + @fmamath: /++ Params: @@ -522,7 +534,7 @@ template pad(size_t[] dimensions, string[] directions) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -537,7 +549,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -574,7 +586,7 @@ auto padWrap(string direction = "both", Iterator, size_t N, SliceKind kind)(Slic } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -587,7 +599,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -622,7 +634,7 @@ See_also: $(LREF ._concatenation) examples. template padWrap(size_t[] dimensions, string[] directions) if (dimensions.length && dimensions.length == directions.length) { - @optmath: + @fmamath: /++ Params: @@ -690,7 +702,7 @@ template padWrap(size_t[] dimensions, string[] directions) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -705,7 +717,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -742,7 +754,7 @@ auto padSymmetric(string direction = "both", Iterator, size_t N, SliceKind kind) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -755,7 +767,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -790,7 +802,7 @@ See_also: $(LREF ._concatenation) examples. template padSymmetric(size_t[] dimensions, string[] directions) if (dimensions.length && dimensions.length == directions.length) { - @optmath: + @fmamath: /++ Params: @@ -876,7 +888,7 @@ template padSymmetric(size_t[] dimensions, string[] directions) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -891,7 +903,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -928,7 +940,7 @@ auto padEdge(string direction = "both", Iterator, size_t N, SliceKind kind)(Slic } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -941,7 +953,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -976,7 +988,7 @@ See_also: $(LREF ._concatenation) examples. template padEdge(size_t[] dimensions, string[] directions) if (dimensions.length && dimensions.length == directions.length) { - @optmath: + @fmamath: /++ Params: @@ -1052,7 +1064,7 @@ template padEdge(size_t[] dimensions, string[] directions) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -1070,7 +1082,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: iota; @@ -1097,7 +1109,7 @@ See_also: $(LREF ._concatenation) examples. +/ template forEachFragment(alias pred) { - @optmath: + @fmamath: import mir.functional: naryFun; static if (__traits(isSame, naryFun!pred, pred)) @@ -1167,7 +1179,7 @@ See_also: $(LREF ._concatenation) examples. +/ template until(alias pred) { - @optmath: + @fmamath: import mir.functional: naryFun; static if (__traits(isSame, naryFun!pred, pred)) diff --git a/source/mir/ndslice/connect/cpython.d b/source/mir/ndslice/connect/cpython.d index c68f7c76..8c943141 100644 --- a/source/mir/ndslice/connect/cpython.d +++ b/source/mir/ndslice/connect/cpython.d @@ -1,9 +1,9 @@ /++ Utilities for $(LINK2 https://docs.python.org/3/c-api/buffer.html, Python Buffer Protocol). -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2017-, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -75,15 +75,15 @@ PythonBufferErrorCode fromPythonBuffer(T, size_t N, SliceKind kind)(ref Slice!(T } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.slice: Slice; auto bar(ref const Py_buffer view) { - Slice!(const(double)*, 2) mat = void; + Slice!(const(double)*, 2) mat; if (auto error = mat.fromPythonBuffer(view)) { - mat = mat.init; // has null pointer + // has null pointer } return mat; } @@ -213,9 +213,9 @@ PythonBufferErrorCode toPythonBuffer(T, size_t N, SliceKind kind)(Slice!(T*, N, } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { - import mir.ndslice.slice : Slice, Structure, Universal, Contiguous; + import mir.ndslice.slice : Slice, Structure, Universal, Contiguous, SliceKind; Py_buffer bar(SliceKind kind)(Slice!(double*, 2, kind) slice) { import core.stdc.stdlib; @@ -224,7 +224,7 @@ version(mir_test) unittest auto structurePtr = cast(Structure!N*) Structure!N.sizeof.malloc; if (!structurePtr) assert(0); - Py_buffer view = void; + Py_buffer view; if (auto error = slice.toPythonBuffer(view, PyBuf_records_ro, *structurePtr)) { @@ -351,46 +351,79 @@ template pythonBufferFormat(T) static if (is(T == struct) && __traits(identifier, A) == "PyObject") enum pythonBufferFormat = "O"; else - enum pythonBufferFormat = null; + static if (is(Unqual!T == short)) + enum pythonBufferFormat = "h"; + else + static if (is(Unqual!T == ushort)) + enum pythonBufferFormat = "H"; + else + static if (is(Unqual!T == int)) + enum pythonBufferFormat = "i"; + else + static if (is(Unqual!T == uint)) + enum pythonBufferFormat = "I"; + else + static if (is(Unqual!T == float)) + enum pythonBufferFormat = "f"; + else + static if (is(Unqual!T == double)) + enum pythonBufferFormat = "d"; + else + static if (is(Unqual!T == long)) + enum pythonBufferFormat = "q"; + else + static if (is(Unqual!T == ulong)) + enum pythonBufferFormat = "Q"; + else + static if (is(Unqual!T == ubyte)) + enum pythonBufferFormat = "B"; + else + static if (is(Unqual!T == byte)) + enum pythonBufferFormat = "b"; + else + static if (is(Unqual!T == char)) + enum pythonBufferFormat = "c"; + else + static if (is(Unqual!T == char*)) + enum pythonBufferFormat = "z"; + else + static if (is(Unqual!T == void*)) + enum pythonBufferFormat = "P"; + else + static if (is(Unqual!T == bool)) + enum pythonBufferFormat = "?"; + else + static if (is(Unqual!T == wchar*)) + enum pythonBufferFormat = "Z"; + else + static if (is(Unqual!T == wchar)) + enum pythonBufferFormat = "u"; + else + { + static if (is(cpp_long)) + { + static if (is(Unqual!T == cpp_long)) + enum pythonBufferFormat = "l"; + else + enum pythonBufferFormat = null; + } + else + static if (is(cpp_ulong)) + { + static if (is(Unqual!T == cpp_ulong)) + enum pythonBufferFormat = "L"; + else + enum pythonBufferFormat = null; + } + else + static if (is(c_long_double)) + { + static if (is(Unqual!T == c_long_double)) + enum pythonBufferFormat = "g"; + else + enum pythonBufferFormat = null; + } + else + enum pythonBufferFormat = null; + } } -/// ditto -enum pythonBufferFormat(T : short) = "h"; -/// ditto -enum pythonBufferFormat(T : ushort) = "H"; -/// ditto -static if (is(cpp_long)) -enum pythonBufferFormat(T : cpp_long) = "l"; -/// ditto -static if (is(cpp_ulong)) -enum pythonBufferFormat(T : cpp_ulong) = "L"; -/// ditto -enum pythonBufferFormat(T : int) = "i"; -/// ditto -enum pythonBufferFormat(T : uint) = "I"; -/// ditto -enum pythonBufferFormat(T : float) = "f"; -/// ditto -enum pythonBufferFormat(T : double) = "d"; -/// ditto -static if (is(c_long_double)) -enum pythonBufferFormat(T : c_long_double) = "g"; -/// ditto -enum pythonBufferFormat(T : long) = "q"; -/// ditto -enum pythonBufferFormat(T : ulong) = "Q"; -/// ditto -enum pythonBufferFormat(T : ubyte) = "B"; -/// ditto -enum pythonBufferFormat(T : byte) = "b"; -/// ditto -enum pythonBufferFormat(T : char) = "c"; -/// ditto -enum pythonBufferFormat(T : char*) = "z"; -/// ditto -enum pythonBufferFormat(T : void*) = "P"; -/// ditto -enum pythonBufferFormat(T : bool) = "?"; -/// ditto -enum pythonBufferFormat(T : wchar*) = "Z"; -/// ditto -enum pythonBufferFormat(T : wchar) = "u"; diff --git a/source/mir/ndslice/dynamic.d b/source/mir/ndslice/dynamic.d index b071a1cf..c7f6e20c 100644 --- a/source/mir/ndslice/dynamic.d +++ b/source/mir/ndslice/dynamic.d @@ -64,11 +64,11 @@ is identical to that of $(LREF strided). Bifacial interface of $(LREF dropOne) and $(LREF dropBackOne) is identical to that of $(LREF reversed). -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) -Copyright: Copyright © 2016, Ilya Yaroshenko +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments -Authors: Ilya Yaroshenko +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -81,13 +81,13 @@ module mir.ndslice.dynamic; import std.traits; import std.meta; -import mir.math.common: optmath; +import mir.math.common: fmamath; import mir.internal.utility: Iota; import mir.ndslice.internal; import mir.ndslice.slice; import mir.utility; -@optmath: +@fmamath: /++ Reverses iteration order for dimensions with negative strides, they become not negative; @@ -131,7 +131,7 @@ bool normalizeStructure(Iterator, size_t N, SliceKind kind)(ref Slice!(Iterator, } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; @@ -180,7 +180,7 @@ See_also: $(LREF everted), $(LREF transposed) template swapped(size_t dimensionA, size_t dimensionB) { /// - @optmath auto swapped(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) + @fmamath auto swapped(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) { static if (kind == Universal || kind == Canonical && dimensionA + 1 < N && dimensionB + 1 < N) { @@ -233,7 +233,7 @@ Slice!(Iterator, 2, Universal) swapped(Iterator, SliceKind kind)(Slice!(Iterator } /// Template -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -248,7 +248,7 @@ Slice!(Iterator, 2, Universal) swapped(Iterator, SliceKind kind)(Slice!(Iterator } /// Function -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -263,7 +263,7 @@ Slice!(Iterator, 2, Universal) swapped(Iterator, SliceKind kind)(Slice!(Iterator } /// 2D -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -312,7 +312,7 @@ Returns: template rotated(size_t dimensionA, size_t dimensionB) { /// - @optmath auto rotated(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, sizediff_t k = 1) + @fmamath auto rotated(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, sizediff_t k = 1) { static if (kind == Universal || kind == Canonical && dimensionA + 1 < N && dimensionB + 1 < N) { @@ -365,7 +365,7 @@ Slice!(Iterator, 2, Universal) rotated(Iterator, SliceKind kind)(Slice!(Iterator } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -431,7 +431,7 @@ auto everted(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slic } /// -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -480,8 +480,8 @@ N-dimensional transpose operator. Brings selected dimensions to the first position. Params: slice = input slice - Dimensions = indexes of dimensions to be brought to the first position - dimensions = indexes of dimensions to be brought to the first position + Dimensions = indices of dimensions to be brought to the first position + dimensions = indices of dimensions to be brought to the first position Returns: n-dimensional slice See_also: $(LREF swapped), $(LREF everted) @@ -489,47 +489,56 @@ See_also: $(LREF swapped), $(LREF everted) template transposed(Dimensions...) if (Dimensions.length) { - static if (!allSatisfy!(isSize_t, Dimensions)) - alias transposed = .transposed!(staticMap!(toSize_t, Dimensions)); - else + static if (allSatisfy!(isSize_t, Dimensions)) /// - @optmath auto transposed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) + @fmamath auto transposed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) { import mir.algorithm.iteration: any; enum s = N; - enum hasRowStride = [Dimensions].sliced.any!(a => a + 1 == s); - static if (kind == Universal || kind == Canonical && !hasRowStride) + static if ([Dimensions] == [Iota!(Dimensions.length)]) { - alias slice = _slice; + return _slice; } else - static if (hasRowStride) { - import mir.ndslice.topology: universal; - auto slice = _slice.universal; + import core.lifetime: move; + enum hasRowStride = [Dimensions].any!(a => a + 1 == s); + static if (kind == Universal || kind == Canonical && !hasRowStride) + { + alias slice = _slice; + } + else + static if (hasRowStride) + { + import mir.ndslice.topology: universal; + auto slice = _slice.move.universal; + } + else + { + import mir.ndslice.topology: canonical; + auto slice = _slice.move.canonical; + } + mixin DimensionsCountCTError; + foreach (i, dimension; Dimensions) + mixin DimensionCTError; + static assert(isValidPartialPermutation!(N)([Dimensions]), + "Failed to complete permutation of dimensions " ~ Dimensions.stringof + ~ tailErrorMessage!()); + enum perm = completeTranspose!(N)([Dimensions]); + static assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error."); + mixin (_transposedCode); } - else - { - import mir.ndslice.topology: canonical; - auto slice = _slice.canonical; - } - mixin DimensionsCountCTError; - foreach (i, dimension; Dimensions) - mixin DimensionCTError; - static assert(isValidPartialPermutation!(N)([Dimensions]), - "Failed to complete permutation of dimensions " ~ Dimensions.stringof - ~ tailErrorMessage!()); - enum perm = completeTranspose!(N)([Dimensions]); - static assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error."); - mixin (_transposedCode); } + else + alias transposed = .transposed!(staticMap!(toSize_t, Dimensions)); } ///ditto auto transposed(Iterator, size_t N, SliceKind kind, size_t M)(Slice!(Iterator, N, kind) _slice, size_t[M] dimensions...) { + import core.lifetime: move; import mir.ndslice.topology: universal; - auto slice = _slice.universal; + auto slice = _slice.move.universal; mixin (DimensionsCountRTError); foreach (dimension; dimensions) @@ -549,7 +558,7 @@ Slice!(Iterator, 2, Universal) transposed(Iterator, SliceKind kind)(Slice!(Itera } /// Template -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -564,7 +573,7 @@ Slice!(Iterator, 2, Universal) transposed(Iterator, SliceKind kind)(Slice!(Itera } /// Function -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -579,7 +588,7 @@ Slice!(Iterator, 2, Universal) transposed(Iterator, SliceKind kind)(Slice!(Itera } /// Single-argument function -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -594,7 +603,7 @@ Slice!(Iterator, 2, Universal) transposed(Iterator, SliceKind kind)(Slice!(Itera } /// _2-dimensional transpose -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota; @@ -621,8 +630,9 @@ Returns: +/ Slice!(Iterator, N, Universal) allReversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) { + import core.lifetime: move; import mir.ndslice.topology: universal; - auto slice = _slice.universal; + auto slice = _slice.move.universal; foreach (dimension; Iota!N) { mixin (_reversedCode); @@ -632,7 +642,7 @@ Slice!(Iterator, N, Universal) allReversed(Iterator, size_t N, SliceKind kind)(S /// @safe @nogc pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.topology: iota, retro; @@ -644,19 +654,17 @@ Reverses the direction of iteration for selected dimensions. Params: _slice = input slice - Dimensions = indexes of dimensions to reverse order of iteration - dimensions = indexes of dimensions to reverse order of iteration + Dimensions = indices of dimensions to reverse order of iteration + dimensions = indices of dimensions to reverse order of iteration Returns: n-dimensional slice +/ template reversed(Dimensions...) if (Dimensions.length) { - static if (!allSatisfy!(isSize_t, Dimensions)) - alias reversed = .reversed!(staticMap!(toSize_t, Dimensions)); - else + static if (allSatisfy!(isSize_t, Dimensions)) /// - @optmath auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) @trusted + @fmamath auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) @trusted { import mir.algorithm.iteration: any; enum s = N; @@ -683,6 +691,8 @@ template reversed(Dimensions...) } return slice; } + else + alias reversed = .reversed!(staticMap!(toSize_t, Dimensions)); } ///ditto @@ -709,7 +719,7 @@ auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slic } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; @@ -737,7 +747,7 @@ auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slic } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, canonical; auto slice = iota([2, 2], 1).canonical; @@ -752,31 +762,31 @@ auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slic assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]); } -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { + import mir.algorithm.iteration : equal; + import mir.ndslice.concatenation : concatenation; import mir.ndslice.slice; import mir.ndslice.topology; - import std.algorithm.comparison : equal; - import std.range : chain; auto i0 = iota([4], 0); auto r0 = i0.retro; auto i1 = iota([4], 4); auto r1 = i1.retro; auto i2 = iota([4], 8); auto r2 = i2.retro; auto slice = iota(3, 4).universal; - assert(slice .flattened.equal(chain(i0, i1, i2))); + assert(slice .flattened.equal(concatenation(i0, i1, i2))); // Template - assert(slice.reversed!(0) .flattened.equal(chain(i2, i1, i0))); - assert(slice.reversed!(1) .flattened.equal(chain(r0, r1, r2))); - assert(slice.reversed!(0, 1) .flattened.equal(chain(r2, r1, r0))); - assert(slice.reversed!(1, 0) .flattened.equal(chain(r2, r1, r0))); - assert(slice.reversed!(1, 1) .flattened.equal(chain(i0, i1, i2))); - assert(slice.reversed!(0, 0, 0).flattened.equal(chain(i2, i1, i0))); + assert(slice.reversed!(0) .flattened.equal(concatenation(i2, i1, i0))); + assert(slice.reversed!(1) .flattened.equal(concatenation(r0, r1, r2))); + assert(slice.reversed!(0, 1) .flattened.equal(concatenation(r2, r1, r0))); + assert(slice.reversed!(1, 0) .flattened.equal(concatenation(r2, r1, r0))); + assert(slice.reversed!(1, 1) .flattened.equal(concatenation(i0, i1, i2))); + assert(slice.reversed!(0, 0, 0).flattened.equal(concatenation(i2, i1, i0))); // Function - assert(slice.reversed (0) .flattened.equal(chain(i2, i1, i0))); - assert(slice.reversed (1) .flattened.equal(chain(r0, r1, r2))); - assert(slice.reversed (0, 1) .flattened.equal(chain(r2, r1, r0))); - assert(slice.reversed (1, 0) .flattened.equal(chain(r2, r1, r0))); - assert(slice.reversed (1, 1) .flattened.equal(chain(i0, i1, i2))); - assert(slice.reversed (0, 0, 0).flattened.equal(chain(i2, i1, i0))); + assert(slice.reversed (0) .flattened.equal(concatenation(i2, i1, i0))); + assert(slice.reversed (1) .flattened.equal(concatenation(r0, r1, r2))); + assert(slice.reversed (0, 1) .flattened.equal(concatenation(r2, r1, r0))); + assert(slice.reversed (1, 0) .flattened.equal(concatenation(r2, r1, r0))); + assert(slice.reversed (1, 1) .flattened.equal(concatenation(i0, i1, i2))); + assert(slice.reversed (0, 0, 0).flattened.equal(concatenation(i2, i1, i0))); } private enum _stridedCode = q{ @@ -794,24 +804,40 @@ private enum _stridedCode = q{ Multiplies the stride of the selected dimension by a factor. Params: - Dimensions = indexes of dimensions to be strided + Dimensions = indices of dimensions to be strided dimension = indexe of a dimension to be strided factor = step extension factors Returns: n-dimensional slice +/ +auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice, ptrdiff_t factor) +{ + import core.lifetime: move; + import std.meta: Repeat; + return move(slice).strided!(Iota!N)(Repeat!(N, factor)); +} + +/// +@safe pure nothrow version(mir_ndslice_test) unittest +{ + import mir.ndslice.topology: iota; + // 0 1 2 3 + // 4 5 6 7 + // 8 9 10 11 + assert(iota(3, 4).strided(2) == [[0, 2], [8, 10]]); +} + +/// ditto template strided(Dimensions...) if (Dimensions.length) { - static if (!allSatisfy!(isSize_t, Dimensions)) - alias strided = .strided!(staticMap!(toSize_t, Dimensions)); - else + static if (allSatisfy!(isSize_t, Dimensions)) /++ Params: - slice = input slice + _slice = input slice factors = list of step extension factors +/ - @optmath auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, Repeat!(Dimensions.length, ptrdiff_t) factors) + @fmamath auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, Repeat!(Dimensions.length, ptrdiff_t) factors) { import mir.algorithm.iteration: any; enum s = N; @@ -839,6 +865,8 @@ template strided(Dimensions...) } return slice; } + else + alias strided = .strided!(staticMap!(toSize_t, Dimensions)); } ///ditto @@ -852,7 +880,7 @@ Slice!(Iterator, N, Universal) strided(Iterator, size_t N, SliceKind kind)(Slice } /// -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; auto slice = iota(3, 4); @@ -882,7 +910,7 @@ pure nothrow version(mir_test) unittest } /// -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, universal; static assert(iota(13, 40).universal.strided!(0, 1)(2, 5).shape == [7, 8]); @@ -890,7 +918,7 @@ pure nothrow version(mir_test) unittest } /// -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, canonical; auto slice = iota(3, 4).canonical; @@ -907,25 +935,25 @@ pure nothrow version(mir_test) unittest == [[0,1,2,3], [8,9,10,11]]); } -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { - import mir.ndslice.slice; - import mir.ndslice.topology; - import std.algorithm.comparison : equal; + import mir.ndslice; + import mir.algorithm.iteration : equal; + import std.range : chain; auto i0 = iota([4], 0); auto s0 = stride(i0, 3); auto i1 = iota([4], 4); auto s1 = stride(i1, 3); auto i2 = iota([4], 8); auto s2 = stride(i2, 3); auto slice = iota(3, 4).universal; - assert(slice .flattened.equal(chain(i0, i1, i2))); + assert(slice .flattened.equal(concatenation(i0, i1, i2))); // Template - assert(slice.strided!0(2) .flattened.equal(chain(i0, i2))); - assert(slice.strided!1(3) .flattened.equal(chain(s0, s1, s2))); - assert(slice.strided!(0, 1)(2, 3).flattened.equal(chain(s0, s2))); + assert(slice.strided!0(2) .flattened.equal(concatenation(i0, i2))); + assert(slice.strided!1(3) .flattened.equal(concatenation(s0, s1, s2))); + assert(slice.strided!(0, 1)(2, 3).flattened.equal(concatenation(s0, s2))); // Function - assert(slice.strided(0, 2).flattened.equal(chain(i0, i2))); - assert(slice.strided(1, 3).flattened.equal(chain(s0, s1, s2))); - assert(slice.strided(0, 2).strided(1, 3).flattened.equal(chain(s0, s2))); + assert(slice.strided(0, 2).flattened.equal(concatenation(i0, i2))); + assert(slice.strided(1, 3).flattened.equal(concatenation(s0, s1, s2))); + assert(slice.strided(0, 2).strided(1, 3).flattened.equal(concatenation(s0, s2))); } /++ @@ -937,8 +965,8 @@ Returns: n-dimensional slice +/ Slice!(Iterator, N, kind) dropToHypercube(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) - if (kind == Canonical || kind == Universal) -body + if (kind == Canonical || kind == Universal || N == 1) +do { size_t length = slice._lengths[0]; foreach (i; Iota!(1, N)) @@ -951,13 +979,14 @@ body /// ditto Slice!(Iterator, N, Canonical) dropToHypercube(Iterator, size_t N)(Slice!(Iterator, N) slice) + if (N > 1) { import mir.ndslice.topology: canonical; return slice.canonical.dropToHypercube; } /// -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, canonical, universal; @@ -969,4 +998,6 @@ Slice!(Iterator, N, Canonical) dropToHypercube(Iterator, size_t N)(Slice!(Iterat .universal .dropToHypercube .shape == cast(size_t[4])[3, 3, 3, 3]); + + assert(4.iota.dropToHypercube == 4.iota); } diff --git a/source/mir/ndslice/field.d b/source/mir/ndslice/field.d index e5d3dfee..eb540e4f 100644 --- a/source/mir/ndslice/field.d +++ b/source/mir/ndslice/field.d @@ -21,9 +21,9 @@ $(T2 SparseField, Used for mutable DOK sparse matrixes ) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -32,11 +32,11 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) module mir.ndslice.field; import mir.internal.utility: Iota; -import mir.math.common: optmath; +import mir.math.common: fmamath; import mir.ndslice.internal; import mir.qualifier; -@optmath: +@fmamath: package template ZeroShiftField(T) { @@ -62,8 +62,9 @@ package auto applyAssumeZeroShift(Types...)() auto MapField__map(Field, alias fun, alias fun1)(ref MapField!(Field, fun) f) { + import core.lifetime: move; import mir.functional: pipe; - return MapField!(Field, pipe!(fun, fun1))(f._field); + return MapField!(Field, pipe!(fun, fun1))(move(f._field)); } @@ -72,7 +73,7 @@ auto MapField__map(Field, alias fun, alias fun1)(ref MapField!(Field, fun) f) +/ struct MapField(Field, alias _fun) { -@optmath: +@fmamath: /// Field _field; @@ -95,8 +96,8 @@ struct MapField(Field, alias _fun) auto ref opIndex(T...)(auto ref T index) { - import mir.functional: RefTuple, unref; - static if (is(typeof(_field[index]) : RefTuple!K, K...)) + import mir.functional: Tuple, unref; + static if (is(typeof(_field[index]) : Tuple!K, K...)) { auto t = _field[index]; return mixin("_fun(" ~ _iotaArgs!(K.length, "t.expand[", "].unref, ") ~ ")"); @@ -136,7 +137,7 @@ struct MapField(Field, alias _fun) +/ struct VmapField(Field, Fun) { -@optmath: +@fmamath: /// Field _field; /// @@ -156,8 +157,8 @@ struct VmapField(Field, Fun) auto ref opIndex(T...)(auto ref T index) { - import mir.functional: RefTuple, unref; - static if (is(typeof(_field[index]) : RefTuple!K, K...)) + import mir.functional: Tuple, unref; + static if (is(typeof(_field[index]) : Tuple!K, K...)) { auto t = _field[index]; return mixin("_fun(" ~ _iotaArgs!(K.length, "t.expand[", "].unref, ") ~ ")"); @@ -237,8 +238,8 @@ Iterates multiple fields in lockstep. struct ZipField(Fields...) if (Fields.length > 1) { -@optmath: - import mir.functional: RefTuple, Ref, _ref; +@fmamath: + import mir.functional: Tuple, Ref, _ref; import std.meta: anySatisfy; /// @@ -267,10 +268,10 @@ struct ZipField(Fields...) alias Iterators = Fields; alias _iterators = _fields; import mir.ndslice.iterator: _zip_types, _zip_index; - return mixin("RefTuple!(_zip_types!Fields)(" ~ _zip_index!Fields ~ ")"); + return mixin("Tuple!(_zip_types!Fields)(" ~ _zip_index!Fields ~ ")"); } - auto opIndexAssign(Types...)(RefTuple!(Types) value, ptrdiff_t index) + auto opIndexAssign(Types...)(Tuple!(Types) value, ptrdiff_t index) if (Types.length == Fields.length) { foreach(i, ref val; value.expand) @@ -296,7 +297,7 @@ struct RepeatField(T) { import std.traits: Unqual; -@optmath: +@fmamath: alias UT = Unqual!T; /// @@ -324,7 +325,7 @@ struct RepeatField(T) struct BitField(Field, I = typeof(cast()Field.init[size_t.init])) if (__traits(isUnsigned, I)) { -@optmath: +@fmamath: import mir.bitop: ctlz; package(mir) alias E = I; package(mir) enum shift = ctlz(I.sizeof) + 3; @@ -391,7 +392,7 @@ struct BitField(Field, I = typeof(cast()Field.init[size_t.init])) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.iterator: FieldIterator; ushort[10] data; @@ -403,16 +404,17 @@ version(mir_test) unittest auto BitField__map(Field, I, alias fun)(BitField!(Field, I) field) { + import core.lifetime: move; import mir.functional: naryFun; - static if (__traits(isSame, fun, naryFun!"~a")) + static if (__traits(isSame, fun, naryFun!"~a") || __traits(isSame, fun, naryFun!"!a")) { import mir.ndslice.topology: bitwiseField; - auto f = _mapField!fun(field._field); + auto f = _mapField!(naryFun!"~a")(move(field._field)); return f.bitwiseField!(typeof(f), I); } else { - return field; + return MapField!(BitField!(Field, I), fun)(move(field)); } } @@ -423,7 +425,7 @@ struct BitpackField(Field, uint pack, I = typeof(cast()Field.init[size_t.init])) if (__traits(isUnsigned, I)) { //static assert(); -@optmath: +@fmamath: package(mir) alias E = I; package(mir) enum mask = (I(1) << pack) - 1; package(mir) enum bits = I.sizeof * 8; @@ -484,6 +486,7 @@ struct BitpackField(Field, uint pack, I = typeof(cast()Field.init[size_t.init])) } /// +version(mir_ndslice_test) unittest { import mir.ndslice.iterator: FieldIterator; @@ -513,6 +516,7 @@ unittest assert(f[11] == 0); } +version(mir_ndslice_test) unittest { import mir.ndslice.slice; @@ -535,7 +539,7 @@ struct OrthogonalReduceField(FieldsIterator, alias fun, T) { import mir.ndslice.slice: Slice; -@optmath: +@fmamath: /// non empty slice Slice!FieldsIterator _fields; @@ -578,7 +582,7 @@ struct CycleField(Field) { import mir.ndslice.slice: Slice; -@optmath: +@fmamath: /// Cycle length size_t _length; /// @@ -626,7 +630,7 @@ struct CycleField(Field, size_t length) { import mir.ndslice.slice: Slice; -@optmath: +@fmamath: /// Cycle length enum _length = length; /// @@ -675,7 +679,7 @@ struct CycleField(Field, size_t length) struct ndIotaField(size_t N) if (N) { -@optmath: +@fmamath: /// size_t[N - 1] _lengths; @@ -694,14 +698,14 @@ struct ndIotaField(size_t N) /// size_t[N] opIndex()(size_t index) const { - size_t[N] indexes; + size_t[N] indices; foreach_reverse (i; Iota!(N - 1)) { - indexes[i + 1] = index % _lengths[i]; + indices[i + 1] = index % _lengths[i]; index /= _lengths[i]; } - indexes[0] = index; - return indexes; + indices[0] = index; + return indices; } } @@ -742,7 +746,7 @@ struct LinspaceField(T) return a + b; } -@optmath: +@fmamath: /// size_t length(size_t dimension = 0)() scope const @property @@ -763,7 +767,7 @@ Magic square field. +/ struct MagicField { -@optmath: +@fmamath: @safe pure nothrow @nogc: /++ diff --git a/source/mir/ndslice/filling.d b/source/mir/ndslice/filling.d new file mode 100644 index 00000000..77e9a67a --- /dev/null +++ b/source/mir/ndslice/filling.d @@ -0,0 +1,59 @@ +/++ +This is a submodule of $(MREF mir,ndslice). + +Initialisation routines. + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki + +Macros: +SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) ++/ +module mir.ndslice.filling; + +import mir.ndslice.slice: Slice, SliceKind; + +/++ +Fills a matrix with the terms of a geometric progression in each row. +Params: + matrix = `m × n` matrix to fill + vec = vector of progression coefficients length of `m` +See_also: $(LINK2 https://en.wikipedia.org/wiki/Vandermonde_matrix, Vandermonde matrix) ++/ +void fillVandermonde(F, SliceKind matrixKind, SliceKind kind)(Slice!(F*, 2, matrixKind) matrix, Slice!(const(F)*, 1, kind) vec) +in { + assert(matrix.length == vec.length); +} +do { + import mir.conv: to; + + foreach (v; matrix) + { + F a = vec.front; + vec.popFront; + F x = to!F(1); + foreach (ref e; v) + { + e = x; + x *= a; + } + } +} + +/// +@safe pure nothrow version(mir_ndslice_test) unittest +{ + import mir.ndslice.slice: sliced; + import mir.ndslice.allocation: uninitSlice; + auto x = [1.0, 2, 3, 4, 5].sliced; + auto v = uninitSlice!double(x.length, x.length); + v.fillVandermonde(x); + assert(v == + [[ 1.0, 1, 1, 1, 1], + [ 1.0, 2, 4, 8, 16], + [ 1.0, 3, 9, 27, 81], + [ 1.0, 4, 16, 64, 256], + [ 1.0, 5, 25, 125, 625]]); +} diff --git a/source/mir/ndslice/fuse.d b/source/mir/ndslice/fuse.d index bfe2143f..52fbb4c5 100644 --- a/source/mir/ndslice/fuse.d +++ b/source/mir/ndslice/fuse.d @@ -3,9 +3,9 @@ This is a submodule of $(MREF mir,ndslice). Allocation routines that construct ndslices from ndranges. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2018-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki See_also: $(SUBMODULE concatenation) submodule. @@ -16,95 +16,33 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.ndslice.fuse; +import mir.internal.utility; import mir.ndslice.slice; import mir.primitives; -import mir.internal.utility; -import std.traits; +import mir.qualifier; import std.meta; - -import mir.math.common: optmath; - -@optmath: +import std.traits; /++ -Fuses ndrange `r` into GC-allocated ndslice. Can be used to join rows or columns into a matrix. +Fuses ndrange `r` into GC-allocated ($(LREF fuse)) or RC-allocated ($(LREF rcfuse)) ndslice. +Can be used to join rows or columns into a matrix. Params: - Dimensions = (optional) indexes of dimensions to be brought to the first position + Dimensions = (optional) indices of dimensions to be brought to the first position Returns: ndslice +/ -template fuse(Dimensions...) -{ - import mir.ndslice.internal: isSize_t, toSize_t; - static if (!allSatisfy!(isSize_t, Dimensions)) - alias fuse = .fuse!(staticMap!(toSize_t, Dimensions)); - else - /++ - Params: - r = parallelotope (ndrange) with length/shape and input range primitives. - +/ - @optmath Slice!(FuseElementType!NDRange*, fuseDimensionCount!NDRange) fuse(NDRange)(NDRange r) - if (hasShape!NDRange) - { - import mir.conv: emplaceRef; - import mir.algorithm.iteration: each; - import mir.ndslice.allocation; - auto shape = fuseShape(r); - alias T = FuseElementType!NDRange; - alias UT = Unqual!T; - alias R = typeof(return); - Slice!(UT*, fuseDimensionCount!NDRange) ret; - static if (Dimensions.length) - { - import mir.ndslice.topology: iota; - import mir.ndslice.dynamic: transposed, completeTranspose; - enum perm = completeTranspose!(shape.length)([Dimensions]); - size_t[shape.length] shapep; - foreach(i; Iota!(shape.length)) - shapep[i] = shape[perm[i]]; - // enum iperm = perm.length.iota[completeTranspose!(shape.length)([Dimensions])[].sliced].slice; - alias InverseDimensions = aliasSeqOf!( - (size_t[] perm){ - auto ar = new size_t[perm.length]; - ar.sliced[perm.sliced] = perm.length.iota; - return ar; - }(perm) - ); - if (__ctfe) - { - ret = shapep.slice!UT; - ret.transposed!InverseDimensions.each!"a = b"(r); - } - else - { - ret = shapep.uninitSlice!UT; - ret.transposed!InverseDimensions.each!(emplaceRef!T)(r); - } - } - else - { - if (__ctfe) - { - ret = shape.slice!UT; - ret.each!"a = b"(r); - } - else - { - ret = shape.uninitSlice!UT; - ret.each!(emplaceRef!T)(r); - } - } - return R(ret._structure, (() @trusted => cast(T*)ret._iterator)()); - } -} +alias fuse(Dimensions...) = fuseImpl!(false, void, Dimensions); +/// ditto +alias rcfuse(Dimensions...) = fuseImpl!(true, void, Dimensions); /// -unittest +@safe pure version(mir_ndslice_test) unittest { import mir.ndslice.fuse; - import mir.ndslice.topology: iota; import mir.ndslice.slice : Contiguous, Slice; + import mir.ndslice.topology: iota, map, as; + import mir.rc.array: RCI; enum ror = [ [0, 1, 2, 3], @@ -116,14 +54,24 @@ unittest // 8 9 10 11 auto matrix = ror.fuse; + auto rcmatrix = ror.rcfuse; // nogc version + assert(matrix == [3, 4].iota); + assert(rcmatrix == [3, 4].iota); static assert(ror.fuse == [3, 4].iota); // CTFE-able + // matrix is contiguos static assert(is(typeof(matrix) == Slice!(int*, 2))); + static assert(is(typeof(rcmatrix) == Slice!(RCI!int, 2))); + + /// also works with strings + auto strMatrix = ror.map!(as!string).fuse; + static assert(is(typeof(strMatrix) == Slice!(string*, 2))); + assert(strMatrix.as!int == matrix); } /// Transposed -unittest +@safe pure version(mir_ndslice_test) unittest { import mir.ndslice.fuse; import mir.ndslice.topology: iota; @@ -150,9 +98,8 @@ unittest static assert(is(typeof(matrix) == Slice!(int*, 2))); } - /// 3D -unittest +@safe pure version(mir_ndslice_test) unittest { import mir.ndslice.fuse; import mir.ndslice.topology: iota; @@ -172,44 +119,312 @@ unittest assert(ror.fuse!(2, 1) == nd.transposed!(2, 1)); } +/// Work with RC Arrays of RC Arrays +@safe pure version(mir_ndslice_test) unittest +{ + import mir.ndslice.fuse; + import mir.ndslice.slice; + import mir.ndslice.topology: map; + import mir.rc.array; + + Slice!(const(double)*, 2) conv(RCArray!(const RCArray!(const double)) a) + { + return a[].map!"a[]".fuse; + } +} + +/++ +Fuses ndrange `r` into GC-allocated ($(LREF fuseAs)) or RC-allocated ($(LREF rcfuseAs)) ndslice. +Can be used to join rows or columns into a matrix. + +Params: + T = output type of ndslice elements + Dimensions = (optional) indices of dimensions to be brought to the first position +Returns: + ndslice ++/ +alias fuseAs(T, Dimensions...) = fuseImpl!(false, T, Dimensions); +/// ditto +alias rcfuseAs(T, Dimensions...) = fuseImpl!(true, T, Dimensions); + +/// +@safe pure version(mir_ndslice_test) unittest +{ + import mir.ndslice.fuse; + import mir.ndslice.slice : Contiguous, Slice; + import mir.ndslice.topology: iota; + import mir.rc.array: RCI; + + enum ror = [ + [0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9,10,11]]; + + // 0 1 2 3 + // 4 5 6 7 + // 8 9 10 11 + auto matrix = ror.fuseAs!double; + + auto rcmatrix = ror.rcfuseAs!double; // nogc version + + assert(matrix == [3, 4].iota); + assert(rcmatrix == [3, 4].iota); + static assert(ror.fuseAs!double == [3, 4].iota); // CTFE-able + + // matrix is contiguos + static assert(is(typeof(matrix) == Slice!(double*, 2))); + static assert(is(typeof(rcmatrix) == Slice!(RCI!double, 2))); +} + +/// +template fuseImpl(bool RC, T_, Dimensions...) +{ + import mir.ndslice.internal: isSize_t, toSize_t; + static if (allSatisfy!(isSize_t, Dimensions)) + /++ + Params: + r = parallelotope (ndrange) with length/shape and input range primitives. + +/ + auto fuseImpl(NDRange)(NDRange r) + if (isFusable!NDRange) + { + import mir.conv: emplaceRef; + import mir.algorithm.iteration: each; + import mir.ndslice.allocation; + auto shape = fuseShape(r); + static if (is(T_ == void)) + alias T = FuseElementType!NDRange; + else + alias T = T_; + alias UT = Unqual!T; + static if (RC) + { + import mir.rc.array: RCI; + alias R = Slice!(RCI!T, fuseDimensionCount!NDRange); + Slice!(RCI!UT, fuseDimensionCount!NDRange) ret; + } + else + { + alias R = Slice!(T*, fuseDimensionCount!NDRange); + Slice!(UT*, fuseDimensionCount!NDRange) ret; + } + static if (Dimensions.length) + { + import mir.ndslice.topology: iota; + import mir.ndslice.dynamic: transposed, completeTranspose; + enum perm = completeTranspose!(shape.length)([Dimensions]); + size_t[shape.length] shapep; + foreach(i; Iota!(shape.length)) + shapep[i] = shape[perm[i]]; + // enum iperm = perm.length.iota[completeTranspose!(shape.length)([Dimensions])[].sliced].slice; + alias InverseDimensions = aliasSeqOf!( + (size_t[] perm){ + auto ar = new size_t[perm.length]; + ar.sliced[perm.sliced] = perm.length.iota; + return ar; + }(perm) + ); + static if (RC) + { + ret = shapep.uninitRcslice!UT; + ret.lightScope.transposed!InverseDimensions.each!(emplaceRef!T)(r); + } + else + { + if (__ctfe) + { + ret = shapep.slice!UT; + ret.transposed!InverseDimensions.each!"a = b"(r); + } + else () @trusted + { + ret = shapep.uninitSlice!UT; + ret.transposed!InverseDimensions.each!(emplaceRef!T)(r); + } (); + + } + } + else + { + static if (RC) + { + ret = shape.uninitRCslice!UT; + ret.lightScope.each!(emplaceRef!T)(r); + } + else + { + if (__ctfe) + { + ret = shape.slice!UT; + ret.each!"a = b"(r); + } + else () @trusted + { + ret = shape.uninitSlice!UT; + ret.each!(emplaceRef!T)(r); + } (); + } + } + static if (RC) + { + import core.lifetime: move; + return move(*(() @trusted => cast(R*)&ret)()); + } + else + { + return *(() @trusted => cast(R*)&ret)(); + } + } + else + alias fuseImpl = .fuseImpl!(RC, T_, staticMap!(toSize_t, Dimensions)); +} + private template fuseDimensionCount(R) { + static if (!isFusable!R) + enum size_t fuseDimensionCount = 0; + else static if (is(typeof(R.init.shape) : size_t[N], size_t N) && (isDynamicArray!R || __traits(hasMember, R, "front"))) { - import mir.ndslice.topology: repeat; - enum size_t fuseDimensionCount = N + fuseDimensionCount!(typeof(mixin("R.init" ~ ".front".repeat(N).fuseCells.field))); + static if (N == 1) + static if (isSomeChar!(typeof(R.init.front))) + enum size_t fuseDimensionCount = 0; + else + enum size_t fuseDimensionCount = N + fuseDimensionCount!(DeepElementType!R); + else + enum size_t fuseDimensionCount = N + fuseDimensionCount!(DeepElementType!R); } else enum size_t fuseDimensionCount = 0; } +/// +@safe pure version(mir_ndslice_test) unittest +{ + import mir.ndslice; + + auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); + static assert(fuseDimensionCount!(typeof(m)) == 3); + + auto p = m.pack!1; + static assert(fuseDimensionCount!(typeof(p)) == 3); + + auto q = m.pack!2; + static assert(fuseDimensionCount!(typeof(q)) == 3); +} + +private static immutable shapeExceptionMsg = "fuseShape Exception: elements have different shapes/lengths"; + +version(D_Exceptions) + static immutable shapeException = new Exception(shapeExceptionMsg); + +private template isFusable(Range) +{ + static if (hasShape!Range) + { + enum isFusable = !isSomeChar!(typeof(Range.init.front)); + } + else + { + enum isFusable = false; + } +} + /+ TODO docs +/ size_t[fuseDimensionCount!Range] fuseShape(Range)(Range r) - if (hasShape!Range) + if (isFusable!Range) { - // auto outerShape = r.shape; enum N = r.shape.length; - static if (N == typeof(return).length) + enum RN = typeof(return).length; + static if (RN == N) { return r.shape; } else { - import mir.ndslice.topology: repeat; typeof(return) ret; ret[0 .. N] = r.shape; if (!ret[0 .. N].anyEmptyShape) - ret[N .. $] = fuseShape(mixin("r" ~ ".front".repeat(N).fuseCells.field)); + { + static if (isSlice!Range) + { + ret[N .. $] = fuseShape(r.first); + } + else static if (isArray!Range) + { + bool next; + foreach (ref elem; r) + { + const elemShape = fuseShape(elem); + if (next) + { + if (elemShape != ret[N .. $]) + { + version (D_Exceptions) + { import mir.exception : toMutable; throw shapeException.toMutable; } + else + assert(0, shapeExceptionMsg); + } + } + else + { + ret[N .. $] = elemShape; + next = true; + } + } + } + else + static assert(false); + } return ret; } } +/// basic +@safe pure version(mir_ndslice_test) unittest +{ + import mir.ndslice; + + auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); + auto s = fuseShape(m); + assert(s == [2, 3, 2]); +} + +/// pack!1 +@safe pure version(mir_ndslice_test) unittest +{ + import mir.ndslice; + + auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); + auto p = m.pack!1; + auto s = fuseShape(p); + assert(s == [2, 3, 2]); +} + +/// pack!2 +@safe pure version(mir_ndslice_test) unittest +{ + import mir.ndslice; + + auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); + auto p = m.pack!2; + auto s = fuseShape(p); + assert(s == [2, 3, 2]); +} + private template FuseElementType(NDRange) + if (isFusable!NDRange) { - import mir.ndslice.topology: repeat; - alias FuseElementType = typeof(mixin("NDRange.init" ~ ".front".repeat(fuseDimensionCount!NDRange).fuseCells.field)); + static assert (fuseDimensionCount!NDRange); + static if (fuseDimensionCount!NDRange == 1) + alias FuseElementType = typeof(NDRange.init.front); + else + static if (isFusable!(typeof(NDRange.init.front))) + alias FuseElementType = FuseElementType!(typeof(NDRange.init.front)); + else + alias FuseElementType = typeof(NDRange.init.front); } /++ @@ -237,19 +452,19 @@ auto fuseCells(S)(S cells) else return ret; } - else + else return () @trusted { import mir.ndslice.allocation: uninitSlice; import mir.conv; auto ret = cells.fuseCellsShape.uninitSlice!UT; ret.fuseCellsAssign!(emplaceRef!T) = cells; alias R = Slice!(T*, ret.N); - return R(ret._structure, (() @trusted => cast(T*)ret._iterator)()); - } + return R(ret._structure, cast(T*)ret._iterator); + } (); } /// 1D -@safe pure nothrow version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; enum ar = [[0, 1], [], [2, 3, 4, 5], [6], [7, 8, 9]]; @@ -258,7 +473,7 @@ auto fuseCells(S)(S cells) } /// 2D -@safe pure nothrow version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; import mir.ndslice.chunks; diff --git a/source/mir/ndslice/internal.d b/source/mir/ndslice/internal.d index 3fec9be8..fb191c72 100644 --- a/source/mir/ndslice/internal.d +++ b/source/mir/ndslice/internal.d @@ -1,14 +1,14 @@ module mir.ndslice.internal; import mir.internal.utility : isFloatingPoint, Iota; -import mir.math.common: optmath; +import mir.math.common: fmamath; import mir.ndslice.iterator: IotaIterator; import mir.ndslice.slice; import mir.primitives; import std.meta; import std.traits; -@optmath: +@fmamath: template ConstIfPointer(T) { @@ -18,6 +18,7 @@ template ConstIfPointer(T) alias ConstIfPointer = T; } +/// public import mir.utility: _expect; struct RightOp(string op, T) @@ -152,10 +153,8 @@ size_t[] reverse()(size_t[] ar) return ar; } -enum indexError(size_t pos, size_t N) = - "index at position " ~ pos.stringof - ~ " from the range [0 .." ~ N.stringof ~ ")" - ~ " must be less than corresponding length."; +enum indexError(DeepElement, int pos, int N) = + N.stringof ~ "D slice of " ~ DeepElement.stringof ~ ": bounds check failed at " ~ (pos + 1).stringof ~ " dimension"; enum string tailErrorMessage( string fun = __FUNCTION__, @@ -259,13 +258,13 @@ template DynamicArrayDimensionsCount(T) enum size_t DynamicArrayDimensionsCount = 0; } -bool isPermutation(size_t N)(auto ref in size_t[N] perm) +bool isPermutation(size_t N)(auto ref const scope size_t[N] perm) { int[N] mask; return isValidPartialPermutationImpl(perm, mask); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { assert(isPermutation([0, 1])); // all numbers 0..N-1 need to be part of the permutation @@ -300,8 +299,15 @@ private bool isValidPartialPermutationImpl(size_t N)(in size_t[] perm, ref int[N return true; } +template ShiftNegativeWith(size_t N) +{ + enum ShiftNegativeWith(sizediff_t i) = i < 0 ? i + N : i; +} + enum toSize_t(size_t i) = i; +enum toSizediff_t(sizediff_t i) = i; enum isSize_t(alias i) = is(typeof(i) == size_t); +enum isSizediff_t(alias i) = is(typeof(i) == sizediff_t); enum isIndex(I) = is(I : size_t); template is_Slice(S) { @@ -318,12 +324,10 @@ private enum isReference(P) = || isFunctionPointer!P || is(P == interface); -enum hasReference(T) = anySatisfy!(isReference, RepresentationTypeTuple!T); - alias ImplicitlyUnqual(T) = Select!(isImplicitlyConvertible!(T, Unqual!T), Unqual!T, T); alias ImplicitlyUnqual(T : T*) = T*; -size_t lengthsProduct(size_t N)(auto ref in size_t[N] lengths) +size_t lengthsProduct(size_t N)(auto ref const scope size_t[N] lengths) { size_t length = lengths[0]; foreach (i; Iota!(1, N)) @@ -331,9 +335,171 @@ size_t lengthsProduct(size_t N)(auto ref in size_t[N] lengths) return length; } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { const size_t[3] lengths = [3, 4, 5]; assert(lengthsProduct(lengths) == 60); assert(lengthsProduct([3, 4, 5]) == 60); } + +package(mir) template strideOf(args...) +{ + static if (args.length == 0) + enum strideOf = args; + else + { + @fmamath @property auto ref ls()() + { + import mir.ndslice.topology: stride; + return stride(args[0]); + } + alias strideOf = AliasSeq!(ls, strideOf!(args[1..$])); + } +} + +package(mir) template frontOf(size_t n) +{ + enum frontOf = () { + string ret; + static foreach (i; 0 .. n) + { + if (i) + ret ~= `, `; + ret ~= "slices[" ~ i.stringof ~ `].front`; + } + return ret; + } (); +} + +package(mir) template frontOf2(args...) +{ + static if (args.length == 0) + enum frontOf2 = args; + else + { + @fmamath @property auto frontOf2Mod()() + { + return args[0].front; + } + alias frontOf2 = AliasSeq!(frontOf2Mod, frontOf2!(args[1..$])); + } +} + +package(mir) template backOf(args...) +{ + static if (args.length == 0) + enum backOf = args; + else + { + @fmamath @property auto ref backOfMod()() + { + return args[0].back; + } + alias backOf = AliasSeq!(backOfMod, backOf!(args[1..$])); + } +} + +package(mir) template frontOfD(size_t dimension, args...) +{ + static if (args.length == 0) + enum frontOfD = args; + else + { + @fmamath @property auto ref frontOfDMod()() + { + return args[0].front!dimension; + } + alias frontOfD = AliasSeq!(frontOfDMod, frontOfD!(dimension, args[1..$])); + } +} + +package(mir) template backOfD(size_t dimension, args...) +{ + static if (args.length == 0) + enum backOfD = args; + else + { + @fmamath @property auto ref backOfDMod()() + { + return args[0].back!dimension; + } + alias backOfD = AliasSeq!(backOfDMod, backOfD!(dimension, args[1..$])); + } +} + +package(mir) template frontOfDim(size_t dim, args...) +{ + static if (args.length == 0) + enum frontOfDim = args; + else + { + alias arg = args[0]; + @fmamath @property auto ref frontOfDimMod() + { + return arg.front!dim; + } + alias frontOfDim = AliasSeq!(frontOfDimMod, frontOfDim!(dim, args[1..$])); + } +} + +package(mir) template selectFrontOf(alias input, args...) +{ + static if (args.length == 0) + enum selectFrontOf = args; + else + { + alias arg = args[0]; + @fmamath @property auto ref selectFrontOfMod()() + { + return arg.lightScope.selectFront!0(input); + } + alias selectFrontOf = AliasSeq!(selectFrontOfMod, selectFrontOf!(input, args[1..$])); + } +} + +package(mir) template selectBackOf(alias input, args...) +{ + static if (args.length == 0) + enum selectBackOf = args; + else + { + alias arg = args[0]; + @fmamath @property auto ref selectBackOfMod()() + { + return arg.selectBack!0(input); + } + alias selectBackOf = AliasSeq!(selectBackOfMod, selectBackOf!(input, args[1..$])); + } +} + +package(mir) template frontSelectFrontOf(alias input, args...) +{ + static if (args.length == 0) + enum frontSelectFrontOf = args; + else + { + alias arg = args[0]; + @fmamath @property auto ref frontSelectFrontOfMod()() + { + return arg.lightScope.front.selectFront!0(input); + } + alias frontSelectFrontOf = AliasSeq!(frontSelectFrontOfMod, frontSelectFrontOf!(input, args[1..$])); + } +} + +package(mir) template frontSelectBackOf(alias input, args...) +{ + static if (args.length == 0) + enum frontSelectBackOf = args; + else + { + alias arg = args[0]; + @fmamath @property auto ref frontSelectBackOfMod + ()() + { + return arg.lightScope.front.selectBack!0(input); + } + alias frontSelectBackOf = AliasSeq!(frontSelectBackOfMod + , frontSelectBackOf!(input, args[1..$])); + } +} diff --git a/source/mir/ndslice/iterator.d b/source/mir/ndslice/iterator.d index 30a7fdea..38c4ee07 100644 --- a/source/mir/ndslice/iterator.d +++ b/source/mir/ndslice/iterator.d @@ -15,6 +15,7 @@ $(T2 IndexIterator, $(SUBREF topology, indexed)) $(T2 IotaIterator, $(SUBREF topology, iota)) $(T2 MapIterator, $(SUBREF topology, map)) $(T2 MemberIterator, $(SUBREF topology, member)) +$(T2 NeighboursIterator, $(SUBREF topology, withNeighboursSum)) $(T2 RetroIterator, $(SUBREF topology, retro)) $(T2 SliceIterator, $(SUBREF topology, map) in composition with $(LREF MapIterator) for packed slices.) $(T2 SlideIterator, $(SUBREF topology, diff), $(SUBREF topology, pairwise), and $(SUBREF topology, slide).) @@ -25,9 +26,9 @@ $(T2 TripletIterator, $(SUBREF topology, triplets)) $(T2 ZipIterator, $(SUBREF topology, zip)) ) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -36,7 +37,7 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) module mir.ndslice.iterator; import mir.internal.utility: Iota; -import mir.math.common: optmath; +import mir.math.common: fmamath; import mir.ndslice.field; import mir.ndslice.internal; import mir.ndslice.slice: SliceKind, Slice, Universal, Canonical, Contiguous, isSlice; @@ -48,7 +49,7 @@ private static immutable assumeZeroShiftExceptionMsg = "*.assumeFieldsHaveZeroSh version(D_Exceptions) private static immutable assumeZeroShiftException = new Exception(assumeZeroShiftExceptionMsg); -@optmath: +@fmamath: enum std_ops = q{ void opUnary(string op)() scope @@ -90,7 +91,7 @@ Step counter. struct IotaIterator(I) if (isIntegral!I || isPointer!I) { -@optmath: +@fmamath: /// I _index; @@ -115,17 +116,19 @@ struct IotaIterator(I) return IotaIterator!(LightImmutableOf!I)(_index); } +pure: + I opUnary(string op : "*")() { return _index; } - void opUnary(string op)() scope + void opUnary(string op)() if (op == "--" || op == "++") { mixin(op ~ `_index;`); } - I opIndex()(ptrdiff_t index) scope const + I opIndex()(ptrdiff_t index) const { return cast(I)(_index + index); } - void opOpAssign(string op)(ptrdiff_t index) scope + void opOpAssign(string op)(ptrdiff_t index) if (op == `+` || op == `-`) { mixin(`_index ` ~ op ~ `= index;`); } @@ -137,18 +140,18 @@ struct IotaIterator(I) return ret; } - ptrdiff_t opBinary(string op : "-")(const typeof(this) right) scope const + ptrdiff_t opBinary(string op : "-")(const typeof(this) right) const { return cast(ptrdiff_t)(this._index - right._index); } - bool opEquals()(const typeof(this) right) scope const + bool opEquals()(const typeof(this) right) const { return this._index == right._index; } - auto opCmp()(const typeof(this) right) scope const + auto opCmp()(const typeof(this) right) const { return this._index - right._index; } } /// -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { IotaIterator!int iota; assert(*iota == 0); @@ -174,7 +177,7 @@ struct IotaIterator(I) } /// -pure nothrow @nogc version(mir_test) unittest +pure nothrow @nogc version(mir_ndslice_test) unittest { int[32] data; auto iota = IotaIterator!(int*)(data.ptr); @@ -206,7 +209,7 @@ auto RetroIterator__map(Iterator, alias fun)(ref RetroIterator!Iterator it) return RetroIterator!(typeof(iterator))(iterator); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology; import mir.ndslice.allocation; @@ -222,7 +225,7 @@ Reverse directions for an iterator. +/ struct RetroIterator(Iterator) { -@optmath: +@fmamath: /// Iterator _iterator; @@ -247,7 +250,7 @@ struct RetroIterator(Iterator) void opUnary(string op : "--")() { ++_iterator; } - void opUnary(string op : "++")() + void opUnary(string op : "++")() pure { --_iterator; } auto ref opIndex()(ptrdiff_t index) @@ -283,7 +286,7 @@ struct RetroIterator(Iterator) } /// -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { IotaIterator!int iota; RetroIterator!(IotaIterator!int) retro; @@ -323,7 +326,7 @@ auto StrideIterator__map(Iterator, alias fun)(StrideIterator!Iterator it) return StrideIterator!(typeof(iterator))(it._stride, iterator); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology; import mir.ndslice.allocation; @@ -339,7 +342,7 @@ Iterates an iterator with a fixed strides. +/ struct StrideIterator(Iterator) { -@optmath: +@fmamath: /// ptrdiff_t _stride; /// @@ -399,7 +402,7 @@ struct StrideIterator(Iterator) } /// -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { IotaIterator!int iota; StrideIterator!(IotaIterator!int) stride; @@ -434,6 +437,110 @@ struct StrideIterator(Iterator) assert(*stride == *iota); } +auto StrideIterator__map(Iterator, size_t factor, alias fun)(StrideIterator!(Iterator, factor) it) +{ + auto iterator = it._iterator._mapIterator!fun; + return StrideIterator!(typeof(iterator), factor)(iterator); +} + +/++ +Iterates an iterator with a fixed strides. + +`StrideIterator` is used by $(SUBREF topology, stride). ++/ +struct StrideIterator(Iterator, ptrdiff_t factor) +{ +@fmamath: + /// + enum _stride = factor; + + /// + Iterator _iterator; + + /// + auto lightConst()() const @property + { + return StrideIterator!(LightConstOf!Iterator, _stride)(.lightConst(_iterator)); + } + + /// + auto lightImmutable()() immutable @property + { + return StrideIterator!(LightImmutableOf!Iterator, _stride)(.lightImmutable(_iterator)); + } + + /// + static alias __map(alias fun) = StrideIterator__map!(Iterator, _stride, fun); + + auto ref opUnary(string op : "*")() + { return *_iterator; } + + void opUnary(string op)() scope + if (op == "--" || op == "++") + { mixin("_iterator " ~ op[0] ~ "= _stride;"); } + + auto ref opIndex()(ptrdiff_t index) + { return _iterator[index * _stride]; } + + void opOpAssign(string op)(ptrdiff_t index) scope + if (op == "-" || op == "+") + { mixin("_iterator " ~ op ~ "= index * _stride;"); } + + auto opBinary(string op)(ptrdiff_t index) + if (op == "+" || op == "-") + { + auto ret = this; + mixin(`ret ` ~ op ~ `= index;`); + return ret; + } + + ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const + { return (this._iterator - right._iterator) / _stride; } + + bool opEquals()(scope ref const typeof(this) right) scope const + { return this._iterator == right._iterator; } + + ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const + { + static if (isPointer!Iterator) + ptrdiff_t ret = this._iterator - right._iterator; + else + ptrdiff_t ret = this._iterator.opCmp(right._iterator); + return _stride >= 0 ? ret : -ret; + } +} + +/// +@safe pure nothrow @nogc version(mir_ndslice_test) unittest +{ + IotaIterator!int iota; + StrideIterator!(IotaIterator!int, -3) stride; + + iota -= stride._stride; + --stride; + assert(*stride == *iota); + + iota += stride._stride; + ++stride; + assert(*stride == *iota); + + assert(stride[7] == iota[7 * stride._stride]); + + iota -= 100 * stride._stride; + stride -= 100; + assert(*stride == *iota); + + iota += 100 * stride._stride; + stride += 100; + assert(*stride == *iota); + + assert(*(stride + 10) == *(iota + 10 * stride._stride)); + + assert(stride - 1 < stride); + + assert((stride - 5) - stride == -5); +} + package template _zip_types(Iterators...) { alias AliasSeq(T...) = T; @@ -489,17 +596,17 @@ Iterates multiple iterators in lockstep. struct ZipIterator(Iterators...) if (Iterators.length > 1) { -@optmath: +@fmamath: import std.traits: ConstOf, ImmutableOf; import std.meta: staticMap; - import mir.functional: RefTuple, Ref, _ref; + import mir.functional: Tuple, Ref, _ref; /// Iterators _iterators; /// auto lightConst()() const @property { - import std.format; + import std.format: format; import mir.ndslice.topology: iota; import std.meta: staticMap; alias Ret = ZipIterator!(staticMap!(LightConstOf, Iterators)); @@ -510,7 +617,7 @@ struct ZipIterator(Iterators...) /// auto lightImmutable()() immutable @property { - import std.format; + import std.format: format; import mir.ndslice.topology: iota; import std.meta: staticMap; alias Ret = ZipIterator!(staticMap!(LightImmutableOf, Iterators)); @@ -519,14 +626,14 @@ struct ZipIterator(Iterators...) } auto opUnary(string op : "*")() - { return mixin("RefTuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } + { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } auto opUnary(string op : "*")() const - { return mixin("RefTuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } + { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } auto opUnary(string op : "*")() immutable - { return mixin("RefTuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } + { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); } void opUnary(string op)() scope if (op == "++" || op == "--") @@ -536,14 +643,15 @@ struct ZipIterator(Iterators...) } auto opIndex()(ptrdiff_t index) - { return mixin("RefTuple!(_zip_types!Iterators)(" ~ _zip_index!Iterators ~ ")"); } + { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_index!Iterators ~ ")"); } - auto opIndexAssign(Types...)(RefTuple!(Types) value, ptrdiff_t index) + auto opIndexAssign(Types...)(Tuple!(Types) value, ptrdiff_t index) if (Types.length == Iterators.length) { foreach(i, ref val; value.expand) { - _iterators[i][index] = val; + import mir.functional: unref; + _iterators[i][index] = unref(val); } return opIndex(index); } @@ -589,7 +697,7 @@ struct ZipIterator(Iterators...) } /// -pure nothrow @nogc version(mir_test) unittest +pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.traits: isIterator; @@ -638,10 +746,10 @@ struct CachedIterator(Iterator, CacheIterator, FlagIterator) /// FlagIterator _flags; -@optmath: +@fmamath: /// - auto lightScope()() scope @property + auto lightScope()() return scope @property { return CachedIterator!(LightScopeOf!Iterator, LightScopeOf!CacheIterator, LightScopeOf!FlagIterator)( .lightScope(_iterator), @@ -651,13 +759,13 @@ struct CachedIterator(Iterator, CacheIterator, FlagIterator) } /// - auto lightScope()() scope const @property + auto lightScope()() return scope const @property { return lightConst.lightScope; } /// - auto lightScope()() scope immutable @property + auto lightScope()() return scope immutable @property { return lightImmutable.lightScope; } @@ -752,14 +860,14 @@ struct CachedIterator(Iterator, CacheIterator, FlagIterator) private enum map_primitives = q{ - import mir.functional: RefTuple, unref; + import mir.functional: Tuple, autoExpandAndForward; auto ref opUnary(string op : "*")() { - static if (is(typeof(*_iterator) : RefTuple!T, T...)) + static if (is(typeof(*_iterator) : Tuple!T, T...)) { auto t = *_iterator; - return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); + return _fun(autoExpandAndForward!t); } else return _fun(*_iterator); @@ -767,10 +875,10 @@ private enum map_primitives = q{ auto ref opIndex(ptrdiff_t index) scope { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = _iterator[index]; - return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); + return _fun(autoExpandAndForward!t); } else return _fun(_iterator[index]); @@ -780,10 +888,10 @@ private enum map_primitives = q{ { auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = _iterator[index]; - return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ") = value"); + return _fun(autoExpandAndForward!t) = value; } else return _fun(_iterator[index]) = value; @@ -791,10 +899,10 @@ private enum map_primitives = q{ auto ref opIndexUnary(string op)(ptrdiff_t index) { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = _iterator[index]; - return mixin(op ~ "_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")"); + return _fun(autoExpandAndForward!t); } else return mixin(op ~ "_fun(_iterator[index])"); @@ -802,10 +910,10 @@ private enum map_primitives = q{ auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = _iterator[index]; - return mixin("_fun(" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ ")" ~ op ~ "= value"); + return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value"); } else return mixin("_fun(_iterator[index])" ~ op ~ "= value"); @@ -818,7 +926,7 @@ private enum map_primitives = q{ +/ struct VmapIterator(Iterator, Fun) { -@optmath: +@fmamath: /// Iterator _iterator; @@ -837,7 +945,66 @@ struct VmapIterator(Iterator, Fun) return VmapIterator!(LightImmutableOf!Iterator, LightImmutableOf!Fun)(.lightImmutable(_iterator), .lightImmutable(_fun)); } - mixin(map_primitives); + import mir.functional: Tuple, autoExpandAndForward; + + auto ref opUnary(string op : "*")() + { + static if (is(typeof(*_iterator) : Tuple!T, T...)) + { + auto t = *_iterator; + return _fun(autoExpandAndForward!t); + } + else + return _fun(*_iterator); + } + + auto ref opIndex(ptrdiff_t index) scope + { + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) + { + auto t = _iterator[index]; + return _fun(autoExpandAndForward!t); + } + else + return _fun(_iterator[index]); + } + + static if (!__traits(compiles, &opIndex(ptrdiff_t.init))) + { + auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope + { + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) + { + auto t = _iterator[index]; + return _fun(autoExpandAndForward!t) = value; + } + else + return _fun(_iterator[index]) = value; + } + + auto ref opIndexUnary(string op)(ptrdiff_t index) + { + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) + { + auto t = _iterator[index]; + return mixin(op ~ "_fun(autoExpandAndForward!t)"); + } + else + return mixin(op ~ "_fun(_iterator[index])"); + } + + auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) + { + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) + { + auto t = _iterator[index]; + return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value"); + } + else + return mixin("_fun(_iterator[index])" ~ op ~ "= value"); + } + } + mixin(std_ops); static if (hasZeroShiftFieldMember!Iterator) @@ -858,7 +1025,7 @@ auto MapIterator__map(Iterator, alias fun0, alias fun)(ref MapIterator!(Iterator +/ struct MapIterator(Iterator, alias _fun) { -@optmath: +@fmamath: /// Iterator _iterator; @@ -874,11 +1041,71 @@ struct MapIterator(Iterator, alias _fun) return MapIterator!(LightImmutableOf!Iterator, _fun)(.lightImmutable(_iterator)); } - import mir.functional: pipe; + import mir.functional: pipe, autoExpandAndForward; /// static alias __map(alias fun1) = MapIterator__map!(Iterator, _fun, pipe!(_fun, fun1)); - mixin(map_primitives); + import mir.functional: Tuple, autoExpandAndForward; + + auto ref opUnary(string op : "*")() + { + static if (is(typeof(*_iterator) : Tuple!T, T...)) + { + auto t = *_iterator; + return _fun(autoExpandAndForward!t); + } + else + return _fun(*_iterator); + } + + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) + auto ref opIndex(ptrdiff_t index) + { + auto t = _iterator[index]; + return _fun(autoExpandAndForward!t); + } + else + auto ref opIndex(ptrdiff_t index) scope + { + return _fun(_iterator[index]); + } + + static if (!__traits(compiles, &opIndex(ptrdiff_t.init))) + { + auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope + { + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) + { + auto t = _iterator[index]; + return _fun(autoExpandAndForward!t) = value; + } + else + return _fun(_iterator[index]) = value; + } + + auto ref opIndexUnary(string op)(ptrdiff_t index) + { + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) + { + auto t = _iterator[index]; + return mixin(op ~ "_fun(autoExpandAndForward!t)"); + } + else + return mixin(op ~ "_fun(_iterator[index])"); + } + + auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) + { + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) + { + auto t = _iterator[index]; + return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value"); + } + else + return mixin("_fun(_iterator[index])" ~ op ~ "= value"); + } + } + mixin(std_ops); static if (hasZeroShiftFieldMember!Iterator) @@ -894,6 +1121,7 @@ Creates a mapped iterator. Uses `__map` if possible. +/ auto _mapIterator(alias fun, Iterator)(Iterator iterator) { + import core.lifetime: move; static if (__traits(hasMember, Iterator, "__map")) { static if (is(Iterator : MapIterator!(Iter0, fun0), Iter0, alias fun0) @@ -901,13 +1129,13 @@ auto _mapIterator(alias fun, Iterator)(Iterator iterator) { // https://github.com/libmir/mir-algorithm/issues/111 debug(mir) pragma(msg, __FUNCTION__~" not coalescing chained map calls into a single lambda, possibly because of multiple embedded context pointers"); - return MapIterator!(Iterator, fun)(iterator); + return MapIterator!(Iterator, fun)(move(iterator)); } else return Iterator.__map!fun(iterator); } else - return MapIterator!(Iterator, fun)(iterator); + return MapIterator!(Iterator, fun)(move(iterator)); } @@ -922,7 +1150,7 @@ auto _vmapIterator(Iterator, Fun)(Iterator iterator, Fun fun) return MapIterator!(Iterator, fun)(iterator); } -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { // https://github.com/libmir/mir-algorithm/issues/111 import mir.ndslice.topology : iota, map; @@ -948,12 +1176,123 @@ auto _vmapIterator(Iterator, Fun)(Iterator iterator, Fun fun) assert(y == result); } +/++ +`NeighboursIterator` is used by $(SUBREF topology, map). ++/ +struct NeighboursIterator(Iterator, size_t N, alias _fun, bool around) +{ + import std.meta: AliasSeq; +@fmamath: + /// + Iterator _iterator; + static if (N) + Iterator[2][N] _neighbours; + else alias _neighbours = AliasSeq!(); + + /// + auto lightConst()() const @property + { + LightConstOf!Iterator[2][N] neighbours; + foreach (i; 0 .. N) + { + neighbours[i][0] = .lightConst(_neighbours[i][0]); + neighbours[i][1] = .lightConst(_neighbours[i][1]); + } + return NeighboursIterator!(LightConstOf!Iterator, N, _fun, around)(.lightConst(_iterator), neighbours); + } + + /// + auto lightImmutable()() immutable @property + { + LightImmutableOf!Iterator[2][N] neighbours; + foreach (i; 0 .. N) + { + neighbours[i][0] = .lightImmutable(_neighbours[i][0]); + neighbours[i][1] = .lightImmutable(_neighbours[i][1]); + } + return NeighboursIterator!(LightImmutableOf!Iterator, N, _fun, around)(.lightImmutable(_iterator), neighbours); + } + + import mir.functional: Tuple, _ref; + + private alias RA = Unqual!(typeof(_fun(_iterator[-1], _iterator[+1]))); + private alias Result = Tuple!(_zip_types!Iterator, RA); + + auto ref opUnary(string op : "*")() + { + return opIndex(0); + } + + auto ref opIndex(ptrdiff_t index) scope + { + static if (around) + RA result = _fun(_iterator[index - 1], _iterator[index + 1]); + + foreach (i; Iota!N) + { + static if (i == 0 && !around) + RA result = _fun(_neighbours[i][0][index], _neighbours[i][1][index]); + else + result = _fun(result, _fun(_neighbours[i][0][index], _neighbours[i][1][index])); + } + static if (__traits(compiles, &_iterator[index])) + return Result(_ref(_iterator[index]), result); + else + return Result(_iterator[index], result); + } + + void opUnary(string op)() scope + if (op == "--" || op == "++") + { + mixin(op ~ "_iterator;"); + foreach (i; Iota!N) + { + mixin(op ~ "_neighbours[i][0];"); + mixin(op ~ "_neighbours[i][1];"); + } + } + + void opOpAssign(string op)(ptrdiff_t index) scope + if (op == "-" || op == "+") + { + + mixin("_iterator " ~ op ~ "= index;"); + foreach (i; Iota!N) + { + mixin("_neighbours[i][0] " ~ op ~ "= index;"); + mixin("_neighbours[i][1] " ~ op ~ "= index;"); + } + } + + auto opBinary(string op)(ptrdiff_t index) + if (op == "+" || op == "-") + { + auto ret = this; + mixin(`ret ` ~ op ~ `= index;`); + return ret; + } + + ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const + { return this._iterator - right._iterator; } + + bool opEquals()(scope ref const typeof(this) right) scope const + { return this._iterator == right._iterator; } + + ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const + { + static if (isPointer!Iterator) + return this._iterator - right._iterator; + else + return this._iterator.opCmp(right._iterator); + } +} + /++ `MemberIterator` is used by $(SUBREF topology, member). +/ struct MemberIterator(Iterator, string member) { -@optmath: +@fmamath: /// Iterator _iterator; @@ -1006,7 +1345,7 @@ struct MemberIterator(Iterator, string member) struct BytegroupIterator(Iterator, size_t count, DestinationType) if (count) { -@optmath: +@fmamath: /// Iterator _iterator; @@ -1110,7 +1449,7 @@ auto SlideIterator__map(Iterator, size_t params, alias fun0, alias fun)(SlideIte struct SlideIterator(Iterator, size_t params, alias fun) if (params > 1) { -@optmath: +@fmamath: /// Iterator _iterator; @@ -1144,7 +1483,7 @@ struct SlideIterator(Iterator, size_t params, alias fun) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.functional: naryFun; auto data = [1, 3, 8, 18]; @@ -1160,7 +1499,7 @@ auto IndexIterator__map(Iterator, Field, alias fun)(ref IndexIterator!(Iterator, return IndexIterator!(Iterator, typeof(field))(it._iterator, field); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology; import mir.ndslice.allocation; @@ -1178,9 +1517,9 @@ Iterates a field using an iterator. +/ struct IndexIterator(Iterator, Field) { - import mir.functional: RefTuple, unref; + import mir.functional: Tuple, autoExpandAndForward; -@optmath: +@fmamath: /// Iterator _iterator; /// @@ -1203,10 +1542,10 @@ struct IndexIterator(Iterator, Field) auto ref opUnary(string op : "*")() { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = *_iterator; - return mixin("_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "]"); + return _field[autoExpandAndForward!t]; } else return _field[*_iterator]; @@ -1214,10 +1553,10 @@ struct IndexIterator(Iterator, Field) auto ref opIndex()(ptrdiff_t index) { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = _iterator[index]; - return mixin("_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "]"); + return _field[autoExpandAndForward!t]; } else return _field[_iterator[index]]; @@ -1227,10 +1566,10 @@ struct IndexIterator(Iterator, Field) { auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = _iterator[index]; - return mixin("_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "] = value"); + return _field[autoExpandAndForward!t] = value; } else return _field[_iterator[index]] = value; @@ -1238,10 +1577,10 @@ struct IndexIterator(Iterator, Field) auto ref opIndexUnary(string op)(ptrdiff_t index) { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = _iterator[index]; - return mixin(op ~ "_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "]"); + return mixin(op ~ "_field[autoExpandAndForward!t]"); } else return mixin(op ~ "_field[_iterator[index]]"); @@ -1249,10 +1588,10 @@ struct IndexIterator(Iterator, Field) auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index) { - static if (is(typeof(_iterator[0]) : RefTuple!T, T...)) + static if (is(typeof(_iterator[0]) : Tuple!T, T...)) { auto t = _iterator[index]; - return mixin("_field[" ~ _iotaArgs!(T.length, "t.expand[", "].unref, ") ~ "]" ~ op ~ "= value"); + return mixin("_field[autoExpandAndForward!t]" ~ op ~ "= value"); } else return mixin("_field[_iterator[index]]" ~ op ~ "= value"); @@ -1263,7 +1602,7 @@ struct IndexIterator(Iterator, Field) } /++ -Iterates chunks in a sliceable using an iterator composed of indexes. +Iterates chunks in a sliceable using an iterator composed of indices. Definition: ---- @@ -1273,7 +1612,7 @@ auto elem = sliceable[index[0] .. index[1]]; +/ struct SubSliceIterator(Iterator, Sliceable) { -@optmath: +@fmamath: /// Iterator _iterator; /// @@ -1307,7 +1646,7 @@ struct SubSliceIterator(Iterator, Sliceable) } /++ -Iterates chunks in a sliceable using an iterator composed of indexes stored consequently. +Iterates chunks in a sliceable using an iterator composed of indices stored consequently. Definition: ---- @@ -1316,7 +1655,7 @@ auto elem = _sliceable[_iterator[index] .. _iterator[index + 1]]; +/ struct ChopIterator(Iterator, Sliceable) { -@optmath: +@fmamath: /// Iterator _iterator; /// @@ -1355,7 +1694,7 @@ as a multidimensional window at the current position. +/ struct SliceIterator(Iterator, size_t N = 1, SliceKind kind = Contiguous) { -@optmath: +@fmamath: /// alias Element = Slice!(Iterator, N, kind); /// @@ -1395,7 +1734,7 @@ public auto FieldIterator__map(Field, alias fun)(FieldIterator!(Field) it) return FieldIterator!(typeof(field))(it._index, field); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology; import mir.ndslice.allocation; @@ -1411,7 +1750,7 @@ Creates an iterator on top of a field. +/ struct FieldIterator(Field) { -@optmath: +@fmamath: /// ptrdiff_t _index; /// @@ -1498,7 +1837,7 @@ struct FieldIterator(Field) if (_expect(_index != 0, false)) { version (D_Exceptions) - throw assumeZeroShiftException; + { import mir.exception : toMutable; throw assumeZeroShiftException.toMutable; } else assert(0, assumeZeroShiftExceptionMsg); } @@ -1513,10 +1852,10 @@ auto FlattenedIterator__map(Iterator, size_t N, SliceKind kind, alias fun)(Flatt { import mir.ndslice.topology: map; auto slice = it._slice.map!fun; - return FlattenedIterator!(TemplateArgsOf!(typeof(slice)))(it._indexes, slice); + return FlattenedIterator!(TemplateArgsOf!(typeof(slice)))(it._indices, slice); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology; import mir.ndslice.allocation; @@ -1533,22 +1872,22 @@ Creates an iterator on top of all elements in a slice. struct FlattenedIterator(Iterator, size_t N, SliceKind kind) if (N > 1 && (kind == Universal || kind == Canonical)) { -@optmath: +@fmamath: /// - ptrdiff_t[N] _indexes; + ptrdiff_t[N] _indices; /// Slice!(Iterator, N, kind) _slice; /// auto lightConst()() const @property { - return FlattenedIterator!(LightConstOf!Iterator, N, kind)(_indexes, _slice.lightConst); + return FlattenedIterator!(LightConstOf!Iterator, N, kind)(_indices, _slice.lightConst); } /// auto lightImmutable()() immutable @property { - return FlattenedIterator!(LightImmutableOf!Iterator, N, kind)(_indexes, _slice.lightImmutable); + return FlattenedIterator!(LightImmutableOf!Iterator, N, kind)(_indices, _slice.lightImmutable); } /// @@ -1557,18 +1896,18 @@ struct FlattenedIterator(Iterator, size_t N, SliceKind kind) private ptrdiff_t getShift()(ptrdiff_t n) { ptrdiff_t _shift; - n += _indexes[$ - 1]; + n += _indices[$ - 1]; foreach_reverse (i; Iota!(1, N)) { immutable v = n / ptrdiff_t(_slice._lengths[i]); n %= ptrdiff_t(_slice._lengths[i]); static if (i == _slice.S) - _shift += (n - _indexes[i]); + _shift += (n - _indices[i]); else - _shift += (n - _indexes[i]) * _slice._strides[i]; - n = _indexes[i - 1] + v; + _shift += (n - _indices[i]) * _slice._strides[i]; + n = _indices[i - 1] + v; } - _shift += (n - _indexes[0]) * _slice._strides[0]; + _shift += (n - _indices[0]) * _slice._strides[0]; return _shift; } @@ -1586,28 +1925,28 @@ struct FlattenedIterator(Iterator, size_t N, SliceKind kind) mixin(op ~ `_slice._iterator;`); else mixin(`_slice._iterator ` ~ op[0] ~ `= _slice._strides[i];`); - mixin (op ~ `_indexes[i];`); + mixin (op ~ `_indices[i];`); static if (i) { static if (op == "++") { - if (_indexes[i] < _slice._lengths[i]) + if (_indices[i] < _slice._lengths[i]) return; static if (i == _slice.S) _slice._iterator -= _slice._lengths[i]; else _slice._iterator -= _slice._lengths[i] * _slice._strides[i]; - _indexes[i] = 0; + _indices[i] = 0; } else { - if (_indexes[i] >= 0) + if (_indices[i] >= 0) return; static if (i == _slice.S) _slice._iterator += _slice._lengths[i]; else _slice._iterator += _slice._lengths[i] * _slice._strides[i]; - _indexes[i] = _slice._lengths[i] - 1; + _indices[i] = _slice._lengths[i] - 1; } } } @@ -1620,7 +1959,7 @@ struct FlattenedIterator(Iterator, size_t N, SliceKind kind) static if (isMutable!(_slice.DeepElement) && !_slice.hasAccessByRef) /// - auto ref opIndexAssign(E)(scope ref E elem, size_t index) scope return + auto ref opIndexAssign(E)(scope ref E elem, size_t index) return scope { return _slice._iterator[getShift(index)] = elem; } @@ -1628,26 +1967,26 @@ struct FlattenedIterator(Iterator, size_t N, SliceKind kind) void opOpAssign(string op : "+")(ptrdiff_t n) scope { ptrdiff_t _shift; - n += _indexes[$ - 1]; + n += _indices[$ - 1]; foreach_reverse (i; Iota!(1, N)) { immutable v = n / ptrdiff_t(_slice._lengths[i]); n %= ptrdiff_t(_slice._lengths[i]); static if (i == _slice.S) - _shift += (n - _indexes[i]); + _shift += (n - _indices[i]); else - _shift += (n - _indexes[i]) * _slice._strides[i]; - _indexes[i] = n; - n = _indexes[i - 1] + v; + _shift += (n - _indices[i]) * _slice._strides[i]; + _indices[i] = n; + n = _indices[i - 1] + v; } - _shift += (n - _indexes[0]) * _slice._strides[0]; - _indexes[0] = n; + _shift += (n - _indices[0]) * _slice._strides[0]; + _indices[0] = n; foreach_reverse (i; Iota!(1, N)) { - if (_indexes[i] >= 0) + if (_indices[i] >= 0) break; - _indexes[i] += _slice._lengths[i]; - _indexes[i - 1]--; + _indices[i] += _slice._lengths[i]; + _indices[i - 1]--; } _slice._iterator += _shift; } @@ -1665,11 +2004,11 @@ struct FlattenedIterator(Iterator, size_t N, SliceKind kind) ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const { - ptrdiff_t ret = this._indexes[0] - right._indexes[0]; + ptrdiff_t ret = this._indices[0] - right._indices[0]; foreach (i; Iota!(1, N)) { ret *= _slice._lengths[i]; - ret += this._indexes[i] - right._indexes[i]; + ret += this._indices[i] - right._indices[i]; } return ret; } @@ -1677,7 +2016,7 @@ struct FlattenedIterator(Iterator, size_t N, SliceKind kind) bool opEquals()(scope ref const typeof(this) right) scope const { foreach_reverse (i; Iota!N) - if (this._indexes[i] != right._indexes[i]) + if (this._indices[i] != right._indices[i]) return false; return true; } @@ -1685,13 +2024,13 @@ struct FlattenedIterator(Iterator, size_t N, SliceKind kind) ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const { foreach (i; Iota!(N - 1)) - if (auto ret = this._indexes[i] - right._indexes[i]) + if (auto ret = this._indices[i] - right._indices[i]) return ret; - return this._indexes[$ - 1] - right._indexes[$ - 1]; + return this._indices[$ - 1] - right._indices[$ - 1]; } } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology; import mir.ndslice.slice; @@ -1778,7 +2117,7 @@ struct StairsIterator(Iterator, string direction) return StairsIterator!(LightImmutableOf!Iterator, direction)(_length, .lightImmutable(_iterator)); } -@optmath: +@fmamath: /// Slice!Iterator opUnary(string op : "*")() @@ -1870,7 +2209,7 @@ struct StairsIterator(Iterator, string direction) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { // 0 // 1 2 @@ -1895,7 +2234,7 @@ version(mir_test) unittest } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { // [0, 1, 2, 3, 4], // [5, 6, 7, 8], @@ -1925,7 +2264,7 @@ Element type of $(LREF TripletIterator). +/ struct Triplet(Iterator, SliceKind kind = Contiguous) { -@optmath: +@fmamath: /// size_t _iterator; /// @@ -1975,7 +2314,7 @@ Iterates triplets position in a slice. +/ struct TripletIterator(Iterator, SliceKind kind = Contiguous) { -@optmath: +@fmamath: /// size_t _iterator; diff --git a/source/mir/ndslice/mutation.d b/source/mir/ndslice/mutation.d index 85fbf7df..0f77f738 100644 --- a/source/mir/ndslice/mutation.d +++ b/source/mir/ndslice/mutation.d @@ -7,12 +7,73 @@ $(BOOKTABLE $(H2 Function), $(TR $(TH Function Name) $(TH Description)) ) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.ndslice.mutation; + +import mir.ndslice.slice: Slice, SliceKind; + +/++ +Copies n-dimensional minor. ++/ +void copyMinor(size_t N, IteratorFrom, SliceKind KindFrom, IteratorTo, SliceKind KindTo, IndexIterator)( + Slice!(IteratorFrom, N, KindFrom) from, + Slice!(IteratorTo, N, KindTo) to, + Slice!IndexIterator[N] indices... +) +in { + import mir.internal.utility: Iota; + static foreach (i; Iota!N) + assert(indices[i].length == to.length!i); +} +do { + static if (N == 1) + to[] = from[indices[0]]; + else + foreach (i; 0 .. indices[0].length) + { + copyMinor!(N - 1)(from[indices[0][i]], to[i], indices[1 .. N]); + } +} + +/// +version(mir_ndslice_test) +@safe pure nothrow +unittest +{ + import mir.ndslice; + // 0 1 2 3 + // 4 5 6 7 + // 8 9 10 11 + auto a = iota!int(3, 4); + auto b = slice!int(2, 2); + copyMinor(a, b, [2, 1].sliced, [0, 3].sliced); + assert(b == [[8, 11], [4, 7]]); +} + +/++ +Reverses data in the 1D slice. ++/ +void reverseInPlace(Iterator)(Slice!Iterator slice) +{ + import mir.utility : swap; + foreach (i; 0 .. slice.length / 2) + swap(slice[i], slice[$ - (i + 1)]); +} + +/// +version(mir_ndslice_test) +@safe pure nothrow +unittest +{ + import mir.ndslice; + auto s = 5.iota.slice; + s.reverseInPlace; + assert([4, 3, 2, 1, 0]); +} diff --git a/source/mir/ndslice/ndfield.d b/source/mir/ndslice/ndfield.d index 35b2539e..8e0e86cd 100644 --- a/source/mir/ndslice/ndfield.d +++ b/source/mir/ndslice/ndfield.d @@ -12,9 +12,9 @@ $(T2 Kronecker, $(SUBREF topology, kronecker)) See_also: $(SUBREF concatenation, concatenation). -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -29,28 +29,28 @@ import mir.ndslice.slice; import mir.primitives; import std.meta; -private template _indexes(NdFields...) +private template _indices(NdFields...) { static if (NdFields.length == 0) - enum _indexes = ""; + enum _indices = ""; else { alias Next = NdFields[0 .. $ - 1]; enum i = Next.length; - enum _indexes = ._indexes!Next ~ - "_fields[" ~ i.stringof ~ "][" ~ _indexes_range!([staticMap!(DimensionCount, Next)].sum, DimensionCount!(NdFields[$ - 1])) ~ "], "; + enum _indices = ._indices!Next ~ + "_fields[" ~ i.stringof ~ "][" ~ _indices_range!([staticMap!(DimensionCount, Next)].sum, DimensionCount!(NdFields[$ - 1])) ~ "], "; } } -private template _indexes_range(size_t begin, size_t count) +private template _indices_range(size_t begin, size_t count) { static if (count == 0) - enum _indexes_range = ""; + enum _indices_range = ""; else { enum next = count - 1; enum elem = begin + next; - enum _indexes_range = ._indexes_range!(begin, next) ~ "indexes[" ~ elem.stringof ~ "], "; + enum _indices_range = ._indices_range!(begin, next) ~ "indices[" ~ elem.stringof ~ "], "; } } @@ -124,21 +124,21 @@ struct Cartesian(NdFields...) } /// - auto opIndex(size_t[N] indexes...) + auto opIndex(size_t[N] indices...) { - import mir.functional : refTuple; - return mixin("refTuple(" ~ _indexes!(NdFields) ~ ")"); + import mir.functional : tuple; + return mixin("tuple(" ~ _indices!(NdFields) ~ ")"); } } -private template _kr_indexes(size_t n) +private template _kr_indices(size_t n) { static if (n == 0) - enum _kr_indexes = ""; + enum _kr_indices = ""; else { enum i = n - 1; - enum _kr_indexes = ._kr_indexes!i ~ "_fields[" ~ i.stringof ~ "][ind[" ~ i.stringof ~ "]], "; + enum _kr_indices = ._kr_indices!i ~ "_fields[" ~ i.stringof ~ "][ind[" ~ i.stringof ~ "]], "; } } @@ -219,7 +219,7 @@ struct Kronecker(alias fun, NdFields...) } /// - auto ref opIndex()(size_t[N] indexes...) + auto ref opIndex()(size_t[N] indices...) { static if (N > 1) size_t[N][NdFields.length] ind; @@ -242,14 +242,14 @@ struct Kronecker(alias fun, NdFields...) { foreach(i; Iota!N) { - ind[f][i] = indexes[i] % s[i]; - indexes[i] /= s[i]; + ind[f][i] = indices[i] % s[i]; + indices[i] /= s[i]; } } else { - ind[f] = indexes[0] % s[0]; - indexes[0] /= s[0]; + ind[f] = indices[0] % s[0]; + indices[0] /= s[0]; } } else @@ -257,14 +257,14 @@ struct Kronecker(alias fun, NdFields...) static if (N > 1) { foreach(i; Iota!N) - ind[f][i] = indexes[i]; + ind[f][i] = indices[i]; } else { - ind[f] = indexes[0]; + ind[f] = indices[0]; } } } - return mixin("fun(" ~ _kr_indexes!(ind.length) ~ ")"); + return mixin("fun(" ~ _kr_indices!(ind.length) ~ ")"); } } diff --git a/source/mir/ndslice/package.d b/source/mir/ndslice/package.d index a2491fdc..0ef1e315 100644 --- a/source/mir/ndslice/package.d +++ b/source/mir/ndslice/package.d @@ -19,7 +19,7 @@ 3. `mixin template`s may be used for pretty error message formatting. -4. `Exception`s/`enforce`s should no be used to check indexes and lengths. +4. `Exception`s/`enforce`s should no be used to check indices and lengths. Exceptions are only allowed for algorithms where validation of input data is too complicated for the user. `reshape` function is a good example of a case where Exceptions are required. @@ -38,7 +38,7 @@ 8. Memory allocation and algorithm logic should be separated whenever possible. -9. CTFE version(mir_test) unittests should be added to new functions. +9. CTFE version(mir_ndslice_test) unittests should be added to new functions. +/ /** @@ -126,9 +126,13 @@ $(TR $(TDNW $(SUBMODULE topology) $(BR) $(BR) Advanced constructors $(BR) SliceKind conversion utilities)) $(TD + $(SUBREF topology, alongDim) $(SUBREF topology, as) + $(SUBREF topology, asKindOf) $(SUBREF topology, assumeCanonical) $(SUBREF topology, assumeContiguous) + $(SUBREF topology, assumeHypercube) + $(SUBREF topology, assumeSameShape) $(SUBREF topology, bitpack) $(SUBREF topology, bitwise) $(SUBREF topology, blocks) @@ -142,6 +146,7 @@ $(TR $(TDNW $(SUBMODULE topology) $(BR) $(SUBREF topology, cycle) $(SUBREF topology, diagonal) $(SUBREF topology, diff) + $(SUBREF topology, dropBorders) $(SUBREF topology, evertPack) $(SUBREF topology, flattened) $(SUBREF topology, indexed) @@ -161,23 +166,37 @@ $(TR $(TDNW $(SUBMODULE topology) $(BR) $(SUBREF topology, ReshapeError) $(SUBREF topology, retro) $(SUBREF topology, slide) + $(SUBREF topology, slideAlong) + $(SUBREF topology, squeeze) $(SUBREF topology, stairs) $(SUBREF topology, stride) $(SUBREF topology, subSlices) $(SUBREF topology, triplets) $(SUBREF topology, universal) + $(SUBREF topology, unsqueeze) $(SUBREF topology, unzip) + $(SUBREF topology, vmap) $(SUBREF topology, windows) $(SUBREF topology, zip) ) ) +$(TR $(TDNW $(SUBMODULE filling) $(BR) + $(SMALL Specialized initialisation routines)) + $(TD + $(SUBREF filling, fillVandermonde) + ) +) + $(TR $(TDNW $(SUBMODULE fuse) $(BR) $(SMALL Data fusing (stacking) $(BR) See also $(SUBMODULE concatenation) submodule. )) $(TD $(SUBREF fuse, fuse) + $(SUBREF fuse, fuseAs) + $(SUBREF fuse, rcfuse) + $(SUBREF fuse, rcfuseAs) $(SUBREF fuse, fuseCells) ) ) @@ -223,6 +242,14 @@ $(TR $(TDNW $(SUBMODULE sorting) ) ) +$(TR $(TDNW $(SUBMODULE mutation) + $(BR) $(SMALL Mutation utilities)) + $(TD + $(SUBREF mutation, copyMinor) + $(SUBREF mutation, reverseInPlace) + ) +) + $(TR $(TDNW $(SUBMODULE iterator) $(BR) $(SMALL Declarations)) $(TD @@ -333,8 +360,8 @@ Returns: where с is the number of channels in the image. Dense data layout is guaranteed. +/ -Slice!(ubyte*, 3) movingWindowByChannel -(Slice!(Universal, [3], ubyte*) image, size_t nr, size_t nc, ubyte delegate(Slice!(Universal, [2], ubyte*)) filter) +Slice!(C*, 3) movingWindowByChannel(alias filter, C) +(Slice!(C*, 3, Universal) image, size_t nr, size_t nc) { // 0. 3D // The last dimension represents the color channel. @@ -347,10 +374,12 @@ Slice!(ubyte*, 3) movingWindowByChannel .windows(nr, nc) // 3. 5D // Unpacks the windows. - .unpack - .transposed!(0, 1, 4) + .unpack.unpack // 4. 5D // Brings the color channel dimension to the third position. + .transposed!(0, 1, 4) + // 5. 3D Composed of 2D + // Packs the last two dimensions. .pack!2 // 2D to pixel lazy conversion. .map!filter @@ -370,12 +399,15 @@ Params: Returns: median value over the range `r` +/ -T median(Range, T)(Slice!(Universal, [2], Range) sl, T[] buf) +T median(Iterator, SliceKind kind, T)(Slice!(Iterator, 2, kind) sl, T[] buf) { import std.algorithm.sorting : topN; // copy sl to the buffer auto retPtr = reduce!( - (ptr, elem) { *ptr = elem; return ptr + 1;} )(buf.ptr, sl); + (ptr, elem) { + *ptr = elem; + return ptr + 1; + } )(buf.ptr, sl); auto n = retPtr - buf.ptr; buf[0 .. n].topN(n / 2); return buf[n / 2]; @@ -387,9 +419,9 @@ The `main` function: ------- void main(string[] args) { - import std.conv : to; - import std.getopt : getopt, defaultGetoptPrinter; - import std.path : stripExtension; + import std.conv: to; + import std.getopt: getopt, defaultGetoptPrinter; + import std.path: stripExtension; uint nr, nc, def = 3; auto helpInformation = args.getopt( @@ -407,6 +439,12 @@ void main(string[] args) auto buf = new ubyte[nr * nc]; + if (args.length == 1) + { + import std.stdio: writeln; + writeln("No input file given"); + } + foreach (name; args[1 .. $]) { import imageformats; // can be found at code.dlang.org @@ -415,6 +453,7 @@ void main(string[] args) auto ret = image.pixels .sliced(cast(size_t)image.h, cast(size_t)image.w, cast(size_t)image.c) + .universal .movingWindowByChannel !(window => median(window, buf)) (nr, nc); @@ -423,7 +462,7 @@ void main(string[] args) name.stripExtension ~ "_filtered.png", ret.length!1, ret.length!0, - (&ret[0, 0, 0])[0 .. ret.elementCount]); + ret.field); } } ------- @@ -455,9 +494,9 @@ whole set of standard D library, so the functions he creates will be as efficient as if they were written in C. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Acknowledgements: John Loughran Colvin Macros: @@ -470,19 +509,21 @@ module mir.ndslice; public import mir.algorithm.iteration; public import mir.ndslice.allocation; -public import mir.ndslice.concatenation; public import mir.ndslice.chunks; +public import mir.ndslice.concatenation; public import mir.ndslice.dynamic; public import mir.ndslice.field; +public import mir.ndslice.filling; public import mir.ndslice.fuse; public import mir.ndslice.iterator; +public import mir.ndslice.mutation; public import mir.ndslice.ndfield; public import mir.ndslice.slice; public import mir.ndslice.topology; public import mir.ndslice.traits; -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto matrix = new double[12].sliced(3, 4); matrix[] = 0; @@ -495,7 +536,7 @@ version(mir_test) unittest } // relaxed example -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.qualifier; @@ -556,7 +597,7 @@ version(mir_test) unittest } } -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { immutable r = 1000.iota; @@ -583,10 +624,9 @@ version(mir_test) unittest assert(t1 == iota([6], 12)); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { - import std.algorithm.comparison : equal; - import std.range : iota; + import mir.ndslice.topology : iota; import mir.array.allocation : array; auto r = 1000.iota.array; @@ -611,7 +651,7 @@ pure nothrow version(mir_test) unittest t1.popBack(); assert(t1.back == 17); - assert(t1.equal(iota(12, 18))); + assert(t1 == iota([6], 12)); t1.front = 13; assert(t1.front == 13); @@ -675,7 +715,7 @@ pure nothrow version(mir_test) unittest assert(&t1[$ - 1] is &(t1.back())); } -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import std.range : iota; auto r = (10_000L * 2 * 3 * 4).iota; @@ -688,14 +728,14 @@ pure nothrow version(mir_test) unittest assert(t0.length!3 == 40); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto tensor = new int[3 * 4 * 8].sliced(3, 4, 8); assert(&(tensor.back.back.back()) is &tensor[2, 3, 7]); assert(&(tensor.front.front.front()) is &tensor[0, 0, 0]); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto slice = new int[24].sliced(2, 3, 4); auto r0 = slice.pack!1[1, 2]; @@ -704,7 +744,7 @@ pure nothrow version(mir_test) unittest assert(slice[1, 2, 3] == 4); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto ar = new int[3 * 8 * 9]; @@ -723,3 +763,17 @@ pure nothrow version(mir_test) unittest assert(matrix[0, 2] == tensor[0, 1, 2]); assert(&matrix[0, 2] is &tensor[0, 1, 2]); } + +version(mir_ndslice_test) +pure nothrow +unittest +{ + auto x = iota(2, 3); + auto y = iota([2, 3], 1); + auto combine1 = x.zip(y).map!"b"; + auto combine2 = x.zip(y).map!("b", "a * b").map!(a => a[0]); + + assert(combine1[0, 0] == 1); + assert(combine2[0, 0] == 1); + static assert(is(typeof(combine2[0, 0]) == sizediff_t)); +} diff --git a/source/mir/ndslice/slice.d b/source/mir/ndslice/slice.d index 2c1aba77..1221105f 100644 --- a/source/mir/ndslice/slice.d +++ b/source/mir/ndslice/slice.d @@ -5,9 +5,9 @@ Safety_note: User-defined iterators should care about their safety except bounds checks. Bounds are checked in ndslice code. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2016-, Ilya Yaroshenko -Authors: Ilya Yaroshenko +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki $(BOOKTABLE $(H2 Definitions), $(TR $(TH Name) $(TH Description)) @@ -20,7 +20,7 @@ $(T2 sliced, Creates a slice on top of an iterator, a pointer, or an array's poi $(T2 slicedField, Creates a slice on top of a field, a random access range, or an array.) $(T2 slicedNdField, Creates a slice on top of an ndField.) $(T2 kindOf, Extracts $(LREF SliceKind).) -$(T2 isSlice, Extracts dimension count from a type. Extracts `null` if the template argument is not a `Slice`.) +$(T2 isSlice, Checks if the type is `Slice` instance.) $(T2 Structure, A tuple of lengths and strides.) ) @@ -33,7 +33,7 @@ STD = $(TD $(SMALL $0)) module mir.ndslice.slice; import mir.internal.utility : Iota; -import mir.math.common : optmath; +import mir.math.common : fmamath; import mir.ndslice.concatenation; import mir.ndslice.field; import mir.ndslice.internal; @@ -45,11 +45,12 @@ import mir.utility; import std.meta; import std.traits; +/// public import mir.primitives: DeepElementType; /++ Checks if type T has asSlice property and its returns a slices. -Aliases itself to a dimension count +Aliases itself to a dimension count +/ template hasAsSlice(T) { @@ -60,7 +61,7 @@ template hasAsSlice(T) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.series; static assert(!hasAsSlice!(int[])); @@ -73,7 +74,7 @@ Check if $(LREF toConst) function can be called with type T. enum isConvertibleToSlice(T) = isSlice!T || isDynamicArray!T || hasAsSlice!T; /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.series: SeriesMap; static assert(isConvertibleToSlice!(immutable int[])); @@ -89,7 +90,8 @@ See_also: $(LREF isConvertibleToSlice). +/ auto toSlice(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) val) { - return val; + import core.lifetime: move; + return val.move; } /// ditto @@ -112,6 +114,16 @@ auto toSlice(T)(T[] val) /// ditto auto toSlice(T)(T val) + if (hasAsSlice!T || __traits(hasMember, T, "moveToSlice")) +{ + static if (__traits(hasMember, T, "moveToSlice")) + return val.moveToSlice; + else + return val.asSlice; +} + +/// ditto +auto toSlice(T)(ref T val) if (hasAsSlice!T) { return val.asSlice; @@ -123,7 +135,11 @@ template toSlices(args...) static if (args.length) { alias arg = args[0]; - @optmath @property auto ref slc()() + alias Arg = typeof(arg); + static if (isMutable!Arg && isSlice!Arg) + alias slc = arg; + else + @fmamath @property auto ref slc()() { return toSlice(arg); } @@ -133,18 +149,14 @@ template toSlices(args...) alias toSlices = AliasSeq!(); } -/// -template isSlice(T) -{ - static if (is(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind)) - enum bool isSlice = true; - else - enum bool isSlice = false; -} +/++ +Checks if the type is `Slice` instance. ++/ +enum isSlice(T) = is(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind); /// @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { alias A = uint[]; alias S = Slice!(int*); @@ -161,8 +173,6 @@ See_also: $(SUBREF topology, assumeCanonical), $(SUBREF topology, assumeContiguous). +/ -alias SliceKind = mir_slice_kind; -/// ditto enum mir_slice_kind { /// A slice has strides for all dimensions. @@ -172,6 +182,8 @@ enum mir_slice_kind /// A slice is a flat contiguous data without strides. contiguous, } +/// ditto +alias SliceKind = mir_slice_kind; /++ Alias for $(LREF .SliceKind.universal). @@ -200,7 +212,7 @@ enum kindOf(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind) = /// @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { static assert(kindOf!(Slice!(int*, 1, Universal)) == Universal); } @@ -227,8 +239,8 @@ Params: Returns: n-dimensional slice +/ -auto sliced(size_t N, Iterator)(Iterator iterator, size_t[N] lengths...) - if (!isStaticArray!Iterator && N +auto sliced(size_t N, Iterator)(return scope Iterator iterator, size_t[N] lengths...) + if (!__traits(isStaticArray, Iterator) && N && !is(Iterator : Slice!(_Iterator, _N, kind), _Iterator, size_t _N, SliceKind kind)) { alias C = ImplicitlyUnqual!(typeof(iterator)); @@ -253,37 +265,13 @@ auto sliced(size_t N, Iterator)(Iterator iterator, size_t[N] lengths...) iterator += 34; iterator -= 34; } - return Slice!(C, N)(_lengths, iterator); - } -} - -/// $(LINK2 https://en.wikipedia.org/wiki/Vandermonde_matrix, Vandermonde matrix) -@safe pure nothrow version(mir_test) unittest -{ - auto vandermondeMatrix(Slice!(double*) x) - @safe nothrow pure - { - import mir.ndslice.allocation: slice; - auto ret = slice!double(x.length, x.length); - foreach (i; 0 .. x.length) - foreach (j; 0 .. x.length) - ret[i][j] = x[i] ^^ j; - return ret; + import core.lifetime: move; + return Slice!(C, N)(_lengths, iterator.move); } - - import mir.ndslice.topology: universal; - auto x = [1.0, 2, 3, 4, 5].sliced; - auto v = vandermondeMatrix(x); - assert(v == - [[ 1.0, 1, 1, 1, 1], - [ 1.0, 2, 4, 8, 16], - [ 1.0, 3, 9, 27, 81], - [ 1.0, 4, 16, 64, 256], - [ 1.0, 5, 25, 125, 625]]); } /// Random access range primitives for slices over user defined types -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { struct MyIota { @@ -296,6 +284,7 @@ auto sliced(size_t N, Iterator)(Iterator iterator, size_t[N] lengths...) auto lightConst()() const @property { return MyIota(); } auto lightImmutable()() immutable @property { return MyIota(); } } + import mir.ndslice.iterator: FieldIterator; alias Iterator = FieldIterator!MyIota; alias S = Slice!(Iterator, 2); @@ -324,7 +313,7 @@ Slice!(T*) sliced(T)(T[] array) @trusted } /// Creates a slice from an array. -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { auto slice = new int[10].sliced; assert(slice.length == 10); @@ -352,11 +341,12 @@ Slice!(Iterator, N, kind) import mir.ndslice.topology: iota; structure[1] = structure[0].iota.strides; } - return typeof(return)(structure, slice._iterator); + import core.lifetime: move; + return typeof(return)(structure, slice._iterator.move); } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto data = new int[24]; @@ -377,7 +367,7 @@ Creates an n-dimensional slice-shell over a field. Params: field = A field. The length of the array should be equal to or less then the product of - lengths. + lengths. lengths = A list of lengths for each dimension. Returns: n-dimensional slice @@ -399,7 +389,7 @@ auto slicedField(Field)(Field field) } /// Creates an 1-dimensional slice over a field, array, or random access range. -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto slice = 10.iota.slicedField; @@ -448,17 +438,17 @@ struct CoordinateValue(T, size_t N = 1) T value; /// - sizediff_t opCmp()(scope auto ref const typeof(this) rht) const + int opCmp()(scope auto ref const typeof(this) rht) const { return cmpCoo(this.index, rht.index); } } -private sizediff_t cmpCoo(size_t N)(scope const auto ref size_t[N] a, scope const auto ref size_t[N] b) +private int cmpCoo(size_t N)(scope const auto ref size_t[N] a, scope const auto ref size_t[N] b) { foreach (i; Iota!(0, N)) - if (auto d = a[i] - b[i]) - return d; + if (a[i] != b[i]) + return a[i] > b[i] ? 1 : -1; return 0; } @@ -482,20 +472,19 @@ package(mir) template allLightScope(args...) { static if (args.length) { - alias arg = args[0]; - alias Arg = typeof(arg); + alias Arg = typeof(args[0]); static if(!isDynamicArray!Arg) { static if(!is(LightScopeOf!Arg == Arg)) - @optmath @property ls()() + @fmamath @property auto allLightScopeMod()() { import mir.qualifier: lightScope; - return lightScope(arg); + return args[0].lightScope; } - else alias ls = arg; + else alias allLightScopeMod = args[0]; } - else alias ls = arg; - alias allLightScope = AliasSeq!(ls, allLightScope!(args[1..$])); + else alias allLightScopeMod = args[0]; + alias allLightScope = AliasSeq!(allLightScopeMod, allLightScope!(args[1..$])); } else alias allLightScope = AliasSeq!(); @@ -530,13 +519,13 @@ $(TR $(TD An $(B interval) is a part of a sequence of type `i .. j`.) $(TR $(TD An $(B index) is a part of a sequence of type `i`.) $(STD `3`, `$-1`)) $(TR $(TD A $(B partially defined slice) is a sequence composed of - $(B intervals) and $(B indexes) with an overall length strictly less than `N`.) + $(B intervals) and $(B indices) with an overall length strictly less than `N`.) $(STD `[3]`, `[0..$]`, `[3, 3]`, `[0..$,0..3]`, `[0..$,2]`)) $(TR $(TD A $(B fully defined index) is a sequence - composed only of $(B indexes) with an overall length equal to `N`.) + composed only of $(B indices) with an overall length equal to `N`.) $(STD `[2,3,1]`)) $(TR $(TD A $(B fully defined slice) is an empty sequence - or a sequence composed of $(B indexes) and at least one + or a sequence composed of $(B indices) and at least one $(B interval) with an overall length equal to `N`.) $(STD `[]`, `[3..$,0..3,0..$-1]`, `[2,0..$,1]`)) $(TR $(TD An $(B indexed slice) is syntax sugar for $(SUBREF topology, indexed) and $(SUBREF topology, cartesian).) @@ -559,6 +548,24 @@ package move or copy data. The operations are only carried out on lengths, strid and pointers. If a slice is defined over a range, only the shift of the initial element changes instead of the range. +Mir n-dimensional Slices can be one of the three kinds. + +$(H4 Contiguous slice) + +Contiguous in memory (or in a user-defined iterator's field) row-major tensor that doesn't store strides because they can be computed on the fly using lengths. +The row stride is always equaled 1. + +$(H4 Canonical slice) + +Canonical slice as contiguous in memory (or in a user-defined iterator's field) rows of a row-major tensor, it doesn't store the stride for row dimension because it is always equaled 1. +BLAS/LAPACK matrices are Canonical but originally have column-major order. +In the same time you can use 2D Canonical Slices with LAPACK assuming that rows are columns and columns are rows. + +$(H4 Universal slice) + +A row-major tensor that stores the strides for all dimensions. +NumPy strides are Universal. + $(H4 Internal Representation for Universal Slices) Type definition @@ -662,7 +669,7 @@ Slice!(Iterator, N, Contiguous) struct mir_slice(Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous, Labels_...) if (0 < N_ && N_ < 255 && !(kind_ == Canonical && N_ == 1) && Labels_.length <= N_ && isIterator!Iterator_) { -@optmath: +@fmamath: /// $(LREF SliceKind) enum SliceKind kind = kind_; @@ -685,6 +692,9 @@ struct mir_slice(Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous, Labels_ /// Data element type alias DeepElement = typeof(Iterator.init[size_t.init]); + /// + alias serdeKeysProxy = Unqual!DeepElement; + /// Label Iterators types alias Labels = Labels_; @@ -708,7 +718,7 @@ struct mir_slice(Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous, Labels_ package(mir): - enum doUnittest = is(Iterator == int*) && N == 1 && kind == Contiguous; + enum doUnittest = is(Iterator == int*) && (N == 1 || N == 2) && kind == Contiguous; enum hasAccessByRef = __traits(compiles, &_iterator[0]); @@ -739,7 +749,7 @@ package(mir): /// public alias _Structure = AliasSeq!(size_t[N], ptrdiff_t[S]); /// - _Structure _structure; + public _Structure _structure; /// public alias _lengths = _structure[0]; /// @@ -750,7 +760,7 @@ package(mir): /// public alias _Structure = AliasSeq!(size_t[N]); /// - _Structure _structure; + public _Structure _structure; /// public alias _lengths = _structure[0]; /// @@ -768,47 +778,47 @@ package(mir): return _stride!dimension * (_lengths[dimension] - 1); } - size_t indexStride(size_t I)(size_t[I] _indexes) @safe scope const + size_t indexStride(size_t I)(size_t[I] _indices) @safe scope const { - static if (_indexes.length) + static if (_indices.length) { static if (kind == Contiguous) { enum E = I - 1; - assert(_indexes[E] < _lengths[E], indexError!(E, N)); + assert(_indices[E] < _lengths[E], indexError!(DeepElement, E, N)); ptrdiff_t ball = this._stride!E; - ptrdiff_t stride = _indexes[E] * ball; + ptrdiff_t stride = _indices[E] * ball; foreach_reverse (i; Iota!E) //static { ball *= _lengths[i + 1]; - assert(_indexes[i] < _lengths[i], indexError!(i, N)); - stride += ball * _indexes[i]; + assert(_indices[i] < _lengths[i], indexError!(DeepElement, i, N)); + stride += ball * _indices[i]; } } else static if (kind == Canonical) { enum E = I - 1; - assert(_indexes[E] < _lengths[E], indexError!(E, N)); + assert(_indices[E] < _lengths[E], indexError!(DeepElement, E, N)); static if (I == N) - size_t stride = _indexes[E]; + size_t stride = _indices[E]; else - size_t stride = _strides[E] * _indexes[E]; + size_t stride = _strides[E] * _indices[E]; foreach_reverse (i; Iota!E) //static { - assert(_indexes[i] < _lengths[i], indexError!(i, N)); - stride += _strides[i] * _indexes[i]; + assert(_indices[i] < _lengths[i], indexError!(DeepElement, i, N)); + stride += _strides[i] * _indices[i]; } } else { enum E = I - 1; - assert(_indexes[E] < _lengths[E], indexError!(E, N)); - size_t stride = _strides[E] * _indexes[E]; + assert(_indices[E] < _lengths[E], indexError!(DeepElement, E, N)); + size_t stride = _strides[E] * _indices[E]; foreach_reverse (i; Iota!E) //static { - assert(_indexes[i] < _lengths[i], indexError!(i, N)); - stride += _strides[i] * _indexes[i]; + assert(_indices[i] < _lengths[i], indexError!(DeepElement, i, N)); + stride += _strides[i] * _indices[i]; } } return stride; @@ -856,7 +866,7 @@ public: // this._lengths = lengths; // this._iterator = iterator; // } - // } + // } // version(LDC) // private enum classicConstructor = true; @@ -894,7 +904,7 @@ public: // static if (doUnittest) // /// - // @safe pure version(mir_test) unittest + // @safe pure version(mir_ndslice_test) unittest // { // import mir.ndslice.slice; // alias Array = Slice!(double*); @@ -905,14 +915,14 @@ public: // auto fun(Array a = null) // { - + // } // } static if (doUnittest) /// Creates a 2-dimentional slice with custom strides. nothrow pure - version(mir_test) unittest + version(mir_ndslice_test) unittest { uint[8] array = [1, 2, 3, 4, 5, 6, 7, 8]; auto slice = Slice!(uint*, 2, Universal)([2, 2], [4, 1], array.ptr); @@ -934,7 +944,7 @@ public: Returns: View with stripped out reference counted context. The lifetime of the result mustn't be longer then the lifetime of the original slice. +/ - auto lightScope()() scope return @property + auto lightScope()() return scope @property { auto ret = Slice!(LightScopeOf!Iterator, N, kind, staticMap!(LightScopeOf, Labels)) (_structure, .lightScope(_iterator)); @@ -944,7 +954,7 @@ public: } /// ditto - auto lightScope()() scope const return @property + auto lightScope()() @trusted return scope const @property { auto ret = Slice!(LightConstOf!(LightScopeOf!Iterator), N, kind, staticMap!(LightConstOfLightScopeOf, Labels)) (_structure, .lightScope(_iterator)); @@ -954,7 +964,7 @@ public: } /// ditto - auto lightScope()() scope immutable return @property + auto lightScope()() return scope immutable @property { auto ret = Slice!(LightImmutableOf!(LightScopeOf!Iterator), N, kind, staticMap!(LightImmutableOfLightConstOf(Labels))) (_structure, .lightScope(_iterator)); @@ -964,7 +974,7 @@ public: } /// Returns: Mutable slice over immutable data. - Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightImmutable()() scope return immutable @property + Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightImmutable()() return scope immutable @property { auto ret = typeof(return)(_structure, .lightImmutable(_iterator)); foreach(i; Iota!L) @@ -972,8 +982,30 @@ public: return ret; } + static if (doUnittest) + /// + @safe pure nothrow + version(mir_ndslice_test) unittest { + import mir.algorithm.iteration: equal; + + immutable Slice!(int*, 1) x = [1, 2].sliced; + auto y = x.lightImmutable; + // this._iterator is copied to the new slice (i.e. both point to the same underlying data) + assert(x._iterator == y._iterator); + assert(x[0] == 1); + assert(x[1] == 2); + assert(y[0] == 1); + assert(y[1] == 2); + // Outer immutable is moved to iteration type + static assert(is(typeof(y) == Slice!(immutable(int)*, 1))); + // meaning that y can be modified, even if its elements can't + y.popFront; + // even if x can't be modified + //x.popFront; //error + } + /// Returns: Mutable slice over const data. - Slice!(LightConstOf!Iterator, N, kind, staticMap!(LightConstOf, Labels)) lightConst()() scope return const @property @trusted + Slice!(LightConstOf!Iterator, N, kind, staticMap!(LightConstOf, Labels)) lightConst()() return scope const @property @trusted { auto ret = typeof(return)(_structure, .lightConst(_iterator)); foreach(i; Iota!L) @@ -982,11 +1014,31 @@ public: } /// ditto - Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightConst()() scope return immutable @property + Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightConst()() return scope immutable @property { return this.lightImmutable; } + static if (doUnittest) + /// + @safe pure nothrow + version(mir_ndslice_test) unittest { + import mir.algorithm.iteration: equal; + + const Slice!(int*, 1) x = [1, 2].sliced; + auto y = x.lightConst; + // this._iterator is copied to the new slice (i.e. both point to the same underlying data) + assert(x._iterator == y._iterator); + assert(x.equal([1, 2])); + assert(y.equal([1, 2])); + // Outer const is moved to iteration type + static assert(is(typeof(y) == Slice!(const(int)*, 1))); + // meaning that y can be modified, even if its elements can't + y.popFront; + // even if x can't be modified + //x.popFront; //error + } + /// Label for the dimensions 'd'. By default returns the row label. Slice!(Labels[d]) label(size_t d = 0)() @property @@ -1039,16 +1091,16 @@ public: } /// `opIndex` overload for const slice - auto ref opIndex(Indexes...)(Indexes indexes) const @trusted + auto ref opIndex(Indexes...)(Indexes indices) const @trusted if (isPureSlice!Indexes || isIndexedSlice!Indexes) { - return lightConst.opIndex(indexes); + return lightConst.opIndex(indices); } /// `opIndex` overload for immutable slice - auto ref opIndex(Indexes...)(Indexes indexes) immutable @trusted + auto ref opIndex(Indexes...)(Indexes indices) immutable @trusted if (isPureSlice!Indexes || isIndexedSlice!Indexes) { - return lightImmutable.opIndex(indexes); + return lightImmutable.opIndex(indices); } static if (allSatisfy!(isPointer, Iterator, Labels)) @@ -1059,14 +1111,14 @@ public: /++ Cast to const and immutable slices in case of underlying range is a pointer. +/ - auto toImmutable()() scope return immutable @trusted pure nothrow @nogc + auto toImmutable()() return scope immutable @trusted pure nothrow @nogc { return Slice!(ImmutableOfUnqualOfPointerTarget!Iterator, N, kind, staticMap!(ImmutableOfUnqualOfPointerTarget, Labels)) (_structure, _iterator, _labels); } /// ditto - auto toConst()() scope return const @trusted pure nothrow @nogc + auto toConst()() return scope const @trusted pure nothrow @nogc { version(LDC) pragma(inline, true); return Slice!(ConstOfUnqualOfPointerTarget!Iterator, N, kind, staticMap!(ConstOfUnqualOfPointerTarget, Labels)) @@ -1079,7 +1131,7 @@ public: static if (doUnittest) /// - version(mir_test) unittest + version(mir_ndslice_test) unittest { static struct Foo { @@ -1104,7 +1156,7 @@ public: static if (doUnittest) /// - version(mir_test) unittest + version(mir_ndslice_test) unittest { Slice!(double*, 2, Universal) nn; Slice!(immutable(double)*, 2, Universal) ni; @@ -1149,23 +1201,23 @@ public: /++ Iterator Returns: - Iterator (pointer) to the $(LREF Slice.first) element. + Iterator (pointer) to the $(LREF .Slice.first) element. +/ - auto iterator()() inout scope return @property + auto iterator()() inout return scope @property { return _iterator; } static if (kind == Contiguous && isPointer!Iterator) /++ - `ptr` alias is available only if the slice kind is $(LREF Contiguous) contiguous and the $(LREF Slice.iterator) is a pointers. + `ptr` alias is available only if the slice kind is $(LREF Contiguous) contiguous and the $(LREF .Slice.iterator) is a pointers. +/ alias ptr = iterator; else { import mir.rc.array: mir_rci; static if (kind == Contiguous && is(Iterator : mir_rci!ET, ET)) - auto ptr() scope return inout @property + auto ptr() return scope inout @property { return _iterator._iterator; } @@ -1178,7 +1230,7 @@ public: Constraints: Field is defined only for contiguous slices. +/ - auto field()() scope return @trusted @property + auto field()() return scope @trusted @property { static assert(kind == Contiguous, "Slice.field is defined only for contiguous slices. Slice kind is " ~ kind.stringof); static if (is(typeof(_iterator[size_t(0) .. elementCount]))) @@ -1193,20 +1245,20 @@ public: } /// ditto - auto field()() scope const return @trusted @property + auto field()() return scope const @trusted @property { return this.lightConst.field; } /// ditto - auto field()() scope immutable return @trusted @property + auto field()() return immutable scope @trusted @property { return this.lightImmutable.field; } static if (doUnittest) /// - @safe version(mir_test) unittest + @safe version(mir_ndslice_test) unittest { auto arr = [1, 2, 3, 4]; auto sl0 = arr.sliced; @@ -1236,7 +1288,7 @@ public: static if (doUnittest) /// Regular slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; assert(iota(3, 4, 5).shape == cast(size_t[3])[3, 4, 5]); @@ -1245,7 +1297,7 @@ public: static if (doUnittest) /// Packed slice @safe @nogc pure nothrow - version(mir_test) unittest + version(mir_ndslice_test) unittest { import mir.ndslice.topology : pack, iota; size_t[3] s = [3, 4, 5]; @@ -1282,7 +1334,7 @@ public: static if (doUnittest) /// Regular slice @safe @nogc pure nothrow - version(mir_test) unittest + version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; size_t[3] s = [20, 5, 1]; @@ -1291,7 +1343,7 @@ public: static if (doUnittest) /// Modified regular slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : pack, iota, universal; import mir.ndslice.dynamic : reversed, strided, transposed; @@ -1305,7 +1357,7 @@ public: static if (doUnittest) /// Packed slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : pack, iota; size_t[3] s = [20 * 42, 5 * 42, 1 * 42]; @@ -1325,7 +1377,7 @@ public: static if (doUnittest) /// Regular slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; assert(iota(3, 4, 5) @@ -1334,7 +1386,7 @@ public: static if (doUnittest) /// Modified regular slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : pack, iota, universal; import mir.ndslice.dynamic : reversed, strided, transposed; @@ -1348,7 +1400,7 @@ public: static if (doUnittest) /// Packed slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : pack, iota; assert(iota(3, 4, 5, 6, 7) @@ -1359,14 +1411,14 @@ public: /++ Save primitive. +/ - auto save()() scope return inout @property + auto save()() inout @property { return this; } static if (doUnittest) /// Save range - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto slice = iota(2, 3).save; @@ -1374,7 +1426,7 @@ public: static if (doUnittest) /// Pointer type. - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; //sl type is `Slice!(2, int*)` @@ -1394,7 +1446,7 @@ public: static if (doUnittest) /// - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto slice = iota(3, 4, 5); @@ -1435,7 +1487,7 @@ public: static if (doUnittest) /// Regular slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto slice = iota(3, 4, 5); @@ -1447,7 +1499,7 @@ public: static if (doUnittest) /// Modified regular slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.dynamic : reversed, strided, swapped; import mir.ndslice.topology : universal, iota; @@ -1468,47 +1520,84 @@ public: return _lengths[dimension] == 0; } - ///ditto static if (N == 1) - auto ref front(size_t dimension = 0)() scope return @trusted @property - if (dimension == 0) { - assert(!empty!dimension); - return *_iterator; + ///ditto + auto ref front(size_t dimension = 0)() return scope @trusted @property + if (dimension == 0) + { + assert(!empty!dimension); + return *_iterator; + } + + ///ditto + auto ref front(size_t dimension = 0)() return scope @trusted @property const + if (dimension == 0) + { + assert(!empty!dimension); + return *_iterator.lightScope; + } + + ///ditto + auto ref front(size_t dimension = 0)() return scope @trusted @property immutable + if (dimension == 0) + { + assert(!empty!dimension); + return *_iterator.lightScope; + } } else - Element!dimension front(size_t dimension = 0)() scope return @property - if (dimension < N) { - typeof(return)._Structure structure_ = typeof(return)._Structure.init; - - foreach (i; Iota!(typeof(return).N)) + /// ditto + Element!dimension front(size_t dimension = 0)() return scope @property + if (dimension < N) { - enum j = i >= dimension ? i + 1 : i; - structure_[0][i] = _lengths[j]; - } + typeof(return)._Structure structure_ = typeof(return)._Structure.init; - static if (!typeof(return).S || typeof(return).S + 1 == S) - alias s = _strides; - else - auto s = strides; + foreach (i; Iota!(typeof(return).N)) + { + enum j = i >= dimension ? i + 1 : i; + structure_[0][i] = _lengths[j]; + } - foreach (i; Iota!(typeof(return).S)) + static if (!typeof(return).S || typeof(return).S + 1 == S) + alias s = _strides; + else + auto s = strides; + + foreach (i; Iota!(typeof(return).S)) + { + enum j = i >= dimension ? i + 1 : i; + structure_[1][i] = s[j]; + } + + return typeof(return)(structure_, _iterator); + } + + ///ditto + auto front(size_t dimension = 0)() return scope @trusted @property const + if (dimension < N) { - enum j = i >= dimension ? i + 1 : i; - structure_[1][i] = s[j]; + assert(!empty!dimension); + return this.lightConst.front!dimension; } - return typeof(return)(structure_, _iterator); + ///ditto + auto front(size_t dimension = 0)() return scope @trusted @property immutable + if (dimension < N) + { + assert(!empty!dimension); + return this.lightImmutable.front!dimension; + } } static if (N == 1 && isMutable!DeepElement && !hasAccessByRef) { ///ditto - auto ref front(size_t dimension = 0, T)(T value) scope return @trusted @property + auto ref front(size_t dimension = 0, T)(T value) return scope @trusted @property if (dimension == 0) { - // check assign safety + // check assign safety static auto ref fun(ref DeepElement t, ref T v) @safe { return t = v; @@ -1524,7 +1613,7 @@ public: ///ditto static if (N == 1) auto ref Element!dimension - back(size_t dimension = 0)() scope return @trusted @property + back(size_t dimension = 0)() return scope @trusted @property if (dimension < N) { assert(!empty!dimension); @@ -1532,7 +1621,7 @@ public: } else auto ref Element!dimension - back(size_t dimension = 0)() scope return @trusted @property + back(size_t dimension = 0)() return scope @trusted @property if (dimension < N) { assert(!empty!dimension); @@ -1561,10 +1650,10 @@ public: static if (N == 1 && isMutable!DeepElement && !hasAccessByRef) { ///ditto - auto ref back(size_t dimension = 0, T)(T value) scope return @trusted @property + auto ref back(size_t dimension = 0, T)(T value) return scope @trusted @property if (dimension == 0) { - // check assign safety + // check assign safety static auto ref fun(ref DeepElement t, ref T v) @safe { return t = v; @@ -1632,7 +1721,7 @@ public: static if (doUnittest) /// - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import std.range.primitives; import mir.ndslice.topology : iota, canonical; @@ -1686,7 +1775,7 @@ public: static if (N > 1) { /// Accesses the first deep element of the slice. - auto ref first()() scope return @trusted @property + auto ref first()() return scope @trusted @property { assert(!anyEmpty); return *_iterator; @@ -1694,7 +1783,7 @@ public: static if (isMutable!DeepElement && !hasAccessByRef) ///ditto - auto ref first(T)(T value) scope return @trusted @property + auto ref first(T)(T value) return scope @trusted @property { assert(!anyEmpty); static if (__traits(compiles, *_iterator = value)) @@ -1705,7 +1794,7 @@ public: static if (doUnittest) /// - @safe pure nothrow @nogc version(mir_test) unittest + @safe pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, universal, canonical; auto f = 5; @@ -1713,7 +1802,7 @@ public: } /// Accesses the last deep element of the slice. - auto ref last()() @trusted scope return @property + auto ref last()() @trusted return scope @property { assert(!anyEmpty); return _iterator[lastIndex]; @@ -1721,7 +1810,7 @@ public: static if (isMutable!DeepElement && !hasAccessByRef) ///ditto - auto ref last(T)(T value) @trusted scope return @property + auto ref last(T)(T value) @trusted return scope @property { assert(!anyEmpty); return _iterator[lastIndex] = value; @@ -1729,23 +1818,63 @@ public: static if (doUnittest) /// - @safe pure nothrow @nogc version(mir_test) unittest + @safe pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; auto f = 5; assert([2, 3].iota(f).last == f + 2 * 3 - 1); } + + static if (kind_ != SliceKind.contiguous) + /// Peforms `popFrontAll` for all dimensions + void popFrontAll() + { + assert(!anyEmpty); + foreach(d; Iota!N_) + popFront!d; + } + + static if (doUnittest) + /// + @safe pure nothrow version(mir_ndslice_test) unittest + { + import mir.ndslice.topology: iota, canonical; + auto v = [2, 3].iota.canonical; + v.popFrontAll; + assert(v == [[4, 5]]); + } + + static if (kind_ != SliceKind.contiguous) + /// Peforms `popBackAll` for all dimensions + void popBackAll() + { + assert(!anyEmpty); + foreach(d; Iota!N_) + popBack!d; + } + + static if (doUnittest) + /// + @safe pure nothrow version(mir_ndslice_test) unittest + { + import mir.ndslice.topology: iota, canonical; + auto v = [2, 3].iota.canonical; + v.popBackAll; + assert(v == [[0, 1]]); + } } else { alias first = front; alias last = back; + alias popFrontAll = popFront; + alias popBackAll = popBack; } /+ Returns: `true` if for any dimension of completely unpacked slice the length equals to `0`, and `false` otherwise. +/ - private bool anyRUEmpty()() @trusted @property scope const + private bool anyRUEmpty()() @trusted scope const { static if (isInstanceOf!(SliceIterator, Iterator)) { @@ -1767,7 +1896,7 @@ public: static if (doUnittest) /// - @safe pure nothrow @nogc version(mir_test) unittest + @safe pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota, canonical; auto s = iota(2, 3).canonical; @@ -1781,7 +1910,7 @@ public: Returns: `this[$-index[0], $-index[1], ..., $-index[N-1]]` +/ - auto ref backward()(size_t[N] index) scope return + auto ref backward()(size_t[N] index) return scope { foreach (i; Iota!N) index[i] = _lengths[i] - index[i]; @@ -1789,20 +1918,20 @@ public: } /// ditto - auto ref backward()(size_t[N] index) scope return const + auto ref backward()(size_t[N] index) return scope const { return this.lightConst.backward(index); } /// ditto - auto ref backward()(size_t[N] index) scope return const + auto ref backward()(size_t[N] index) return scope const { return this.lightConst.backward(index); } static if (doUnittest) /// - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto s = iota(2, 3); @@ -1820,12 +1949,9 @@ public: return len; } - deprecated("use elementCount instead") - alias elementsCount = elementCount; - static if (doUnittest) /// Regular slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; assert(iota(3, 4, 5).elementCount == 60); @@ -1834,7 +1960,7 @@ public: static if (doUnittest) /// Packed slice - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : pack, evertPack, iota; auto slice = iota(3, 4, 5, 6, 7, 8); @@ -1851,12 +1977,12 @@ public: end = final index of the sub-slice (noninclusive) Returns: ndslice with `length!dimension` equal to `end - begin`. +/ - auto select(size_t dimension)(size_t begin, size_t end) scope return + auto select(size_t dimension)(size_t begin, size_t end) @trusted { static if (kind == Contiguous && dimension) { import mir.ndslice.topology: canonical; - auto ret = this.canonical; + auto ret = this.canonical; } else { @@ -1871,7 +1997,7 @@ public: static if (doUnittest) /// - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto sl = iota(3, 4); @@ -1885,12 +2011,12 @@ public: n = count of elements for the dimension Returns: ndslice with `length!dimension` equal to `n`. +/ - auto selectFront(size_t dimension)(size_t n) scope return + auto selectFront(size_t dimension)(size_t n) return scope { static if (kind == Contiguous && dimension) { import mir.ndslice.topology: canonical; - auto ret = this.canonical; + auto ret = this.canonical; } else { @@ -1903,7 +2029,7 @@ public: static if (doUnittest) /// - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto sl = iota(3, 4); @@ -1917,12 +2043,12 @@ public: n = count of elements for the dimension Returns: ndslice with `length!dimension` equal to `n`. +/ - auto selectBack(size_t dimension)(size_t n) scope return + auto selectBack(size_t dimension)(size_t n) return scope { static if (kind == Contiguous && dimension) { import mir.ndslice.topology: canonical; - auto ret = this.canonical; + auto ret = this.canonical; } else { @@ -1936,56 +2062,49 @@ public: static if (doUnittest) /// - @safe @nogc pure nothrow version(mir_test) unittest + @safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto sl = iota(3, 4); assert(sl.selectBack!1(2) == sl[0 .. $, $ - 2 .. $]); } - /++ - Overloading `==` and `!=` - +/ - bool opEquals(scope const ref typeof(this) rslice) @trusted scope const + ///ditto + bool opEquals(IteratorR, SliceKind rkind)(auto ref const Slice!(IteratorR, N, rkind) rslice) @trusted scope const { - static if (!hasReference!(typeof(this))) + static if ( + __traits(isPOD, Iterator) + && __traits(isPOD, IteratorR) + && __traits(compiles, this._iterator == rslice._iterator) + ) { if (this._lengths != rslice._lengths) return false; + if (anyEmpty) + return true; if (this._iterator == rslice._iterator) + { + auto ls = this.strides; + auto rs = rslice.strides; + foreach (i; Iota!N) + { + if (this._lengths[i] != 1 && ls[i] != rs[i]) + goto L; + } return true; + } } + L: - import mir.algorithm.iteration : equal; - static if (__traits(compiles, this.lightScope)) + static if (is(Iterator == IotaIterator!UL, UL) && is(IteratorR == Iterator)) { - auto slice1 = this.lightScope; - auto slice2 = rslice.lightScope; - foreach(i; Iota!(min(slice1.L, slice2.L))) - if(slice1.label!i != slice2.label!i) - return false; - return equal(slice1.values, slice2.values); + return false; } else - return equal(*cast(This*)&this, *cast(This*)&rslice); - } - - ///ditto - bool opEquals(IteratorR, SliceKind rkind)(auto ref const Slice!(IteratorR, N, rkind) rslice) @trusted scope const - { - static if ( - !hasReference!(typeof(this)) - && !hasReference!(typeof(rslice)) - && __traits(compiles, this._iterator == rslice._iterator) - ) { - if (this._lengths != rslice._lengths) - return false; - if (this._iterator == rslice._iterator) - return true; + import mir.algorithm.iteration : equal; + return equal(this.lightScope, rslice.lightScope); } - import mir.algorithm.iteration : equal; - return equal(this.lightScope, rslice.lightScope); } /// ditto @@ -2008,7 +2127,7 @@ public: static if (doUnittest) /// @safe pure nothrow - version(mir_test) unittest + version(mir_ndslice_test) unittest { auto a = [1, 2, 3, 4].sliced(2, 2); @@ -2023,7 +2142,7 @@ public: } static if (doUnittest) - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology : iota; @@ -2039,11 +2158,11 @@ public: { assert(i <= j, "Slice.opSlice!" ~ dimension.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); - enum errorMsg = ": right opSlice boundary must equal to the length of the given dimension."; + enum errorMsg = ": right opSlice boundary must be less than or equal to the length of the given dimension."; assert(j <= _lengths[dimension], "Slice.opSlice!" ~ dimension.stringof ~ errorMsg); } - body + do { return typeof(return)(j - i, typeof(return).Iterator(i)); } @@ -2051,61 +2170,61 @@ public: /++ $(BOLD Fully defined index) +/ - auto ref opIndex()(size_t[N] _indexes...) scope return @trusted + auto ref opIndex()(size_t[N] _indices...) return scope @trusted { - return _iterator[indexStride(_indexes)]; + return _iterator[indexStride(_indices)]; } /// ditto - auto ref opIndex()(size_t[N] _indexes...) scope return const @trusted + auto ref opIndex()(size_t[N] _indices...) return scope const @trusted { - static if (is(typeof(_iterator[indexStride(_indexes)]))) - return _iterator[indexStride(_indexes)]; + static if (is(typeof(_iterator[indexStride(_indices)]))) + return _iterator[indexStride(_indices)]; else - return .lightConst(.lightScope(_iterator))[indexStride(_indexes)]; + return .lightConst(.lightScope(_iterator))[indexStride(_indices)]; } /// ditto - auto ref opIndex()(size_t[N] _indexes...) scope return immutable @trusted + auto ref opIndex()(size_t[N] _indices...) return scope immutable @trusted { - static if (is(typeof(_iterator[indexStride(_indexes)]))) - return _iterator[indexStride(_indexes)]; + static if (is(typeof(_iterator[indexStride(_indices)]))) + return _iterator[indexStride(_indices)]; else - return .lightImmutable(.lightScope(_iterator))[indexStride(_indexes)]; + return .lightImmutable(.lightScope(_iterator))[indexStride(_indices)]; } /++ $(BOLD Partially defined index) +/ - auto opIndex(size_t I)(size_t[I] _indexes...) scope return @trusted + auto opIndex(size_t I)(size_t[I] _indices...) return scope @trusted if (I && I < N) { enum size_t diff = N - I; alias Ret = Slice!(Iterator, diff, diff == 1 && kind == Canonical ? Contiguous : kind); static if (I < S) - return Ret(_lengths[I .. N], _strides[I .. S], _iterator + indexStride(_indexes)); + return Ret(_lengths[I .. N], _strides[I .. S], _iterator + indexStride(_indices)); else - return Ret(_lengths[I .. N], _iterator + indexStride(_indexes)); + return Ret(_lengths[I .. N], _iterator + indexStride(_indices)); } /// ditto - auto opIndex(size_t I)(size_t[I] _indexes...) scope return const + auto opIndex(size_t I)(size_t[I] _indices...) return scope const if (I && I < N) { - return this.lightConst.opIndex(_indexes); + return this.lightConst.opIndex(_indices); } /// ditto - auto opIndex(size_t I)(size_t[I] _indexes...) scope return immutable + auto opIndex(size_t I)(size_t[I] _indices...) return scope immutable if (I && I < N) { - return this.lightImmutable.opIndex(_indexes); + return this.lightImmutable.opIndex(_indices); } /++ $(BOLD Partially or fully defined slice.) +/ - auto opIndex(Slices...)(Slices slices) scope return @trusted + auto opIndex(Slices...)(Slices slices) return scope @trusted if (isPureSlice!Slices) { static if (Slices.length) @@ -2200,7 +2319,7 @@ public: static if (doUnittest) /// - pure nothrow version(mir_test) unittest + pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto slice = slice!int(5, 3); @@ -2217,7 +2336,7 @@ public: /++ $(BOLD Indexed slice.) +/ - auto opIndex(Slices...)(scope return Slices slices) scope return + auto opIndex(Slices...)(return scope Slices slices) return scope if (isIndexedSlice!Slices) { import mir.ndslice.topology: indexed, cartesian; @@ -2230,7 +2349,7 @@ public: static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; auto sli = slice!int(4, 3); @@ -2265,7 +2384,7 @@ public: static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; import mir.ndslice.allocation: slice; @@ -2298,7 +2417,7 @@ public: Note: Does not allocate neither new slice nor a closure. +/ - auto opUnary(string op)() scope return + auto opUnary(string op)() if (op == "*" || op == "~" || op == "-" || op == "+") { import mir.ndslice.topology: map; @@ -2310,7 +2429,7 @@ public: static if (doUnittest) /// - version(mir_test) unittest + version(mir_ndslice_test) unittest { import mir.ndslice.topology; @@ -2334,7 +2453,7 @@ public: Note: Does not allocate neither new slice nor a closure. +/ - auto opBinary(string op, T)(scope return T value) scope return + auto opBinary(string op, T)(scope return T value) return scope if(!isSlice!T) { import mir.ndslice.topology: vmap; @@ -2342,7 +2461,7 @@ public: } /// ditto - auto opBinaryRight(string op, T)(scope return T value) scope return + auto opBinaryRight(string op, T)(scope return T value) return scope if(!isSlice!T) { import mir.ndslice.topology: vmap; @@ -2351,7 +2470,7 @@ public: static if (doUnittest) /// - @safe pure nothrow @nogc version(mir_test) unittest + @safe pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.topology; @@ -2365,7 +2484,7 @@ public: static if (doUnittest) /// - @safe pure nothrow @nogc version(mir_test) unittest + @safe pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.topology; @@ -2387,7 +2506,7 @@ public: Does not allocate neither new slice nor a closure. +/ auto opBinary(string op, RIterator, size_t RN, SliceKind rkind) - (scope return Slice!(RIterator, RN, rkind) rhs) scope return + (scope return Slice!(RIterator, RN, rkind) rhs) return scope if(N == RN && (kind == Contiguous && rkind == Contiguous || N == 1) && op != "~") { import mir.ndslice.topology: zip, map; @@ -2396,7 +2515,7 @@ public: static if (doUnittest) /// - @safe pure nothrow @nogc version(mir_test) unittest + @safe pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, map, zip; @@ -2408,10 +2527,10 @@ public: /++ Duplicates slice. Returns: GC-allocated Contiguous mutable slice. - See_also: $(LREF Slice.idup) + See_also: $(LREF .Slice.idup) +/ Slice!(Unqual!DeepElement*, N) - dup()() scope @property + dup()() scope @property @trusted { if (__ctfe) { @@ -2450,7 +2569,7 @@ public: static if (doUnittest) /// - @safe pure version(mir_test) unittest + @safe pure version(mir_ndslice_test) unittest { import mir.ndslice; auto x = 3.iota!int; @@ -2463,7 +2582,7 @@ public: /++ Duplicates slice. Returns: GC-allocated Contiguous immutable slice. - See_also: $(LREF Slice.dup) + See_also: $(LREF .Slice.dup) +/ Slice!(immutable(DeepElement)*, N) idup()() scope @property @@ -2505,7 +2624,7 @@ public: static if (doUnittest) /// - @safe pure version(mir_test) unittest + @safe pure version(mir_ndslice_test) unittest { import mir.ndslice; auto x = 3.iota!int; @@ -2515,10 +2634,74 @@ public: assert(mut == x); } + /++ + Provides the index location of a slice, taking into account + `Slice._strides`. Similar to `Slice.indexStride`, except the slice is + indexed by a value. Called by `Slice.accessFlat`. + + Params: + n = location in slice + Returns: + location in slice, adjusted for `Slice._strides` + See_also: + $(SUBREF topology, flattened), + $(LREF .Slice.indexStride), + $(LREF .Slice.accessFlat) + +/ + private + ptrdiff_t indexStrideValue(ptrdiff_t n) @safe scope const + { + assert(n < elementCount, "indexStrideValue: n must be less than elementCount"); + assert(n >= 0, "indexStrideValue: n must be greater than or equal to zero"); + + static if (kind == Contiguous) { + return n; + } else { + ptrdiff_t _shift; + foreach_reverse (i; Iota!(1, N)) + { + immutable v = n / ptrdiff_t(_lengths[i]); + n %= ptrdiff_t(_lengths[i]); + static if (i == S) + _shift += n; + else + _shift += n * _strides[i]; + n = v; + } + _shift += n * _strides[0]; + return _shift; + } + } + + /++ + Provides access to a slice as if it were `flattened`. + + Params: + index = location in slice + Returns: + value of flattened slice at `index` + See_also: $(SUBREF topology, flattened) + +/ + auto ref accessFlat(size_t index) return scope @trusted + { + return _iterator[indexStrideValue(index)]; + } + + /// + version(mir_ndslice_test) + @safe pure @nogc nothrow + unittest + { + import mir.ndslice.topology: iota, flattened; + + auto x = iota(2, 3, 4); + assert(x.accessFlat(9) == x.flattened[9]); + } + static if (isMutable!DeepElement) { private void opIndexOpAssignImplSlice(string op, RIterator, size_t RN, SliceKind rkind) - (scope Slice!(RIterator, RN, rkind) value) scope + (Slice!(RIterator, RN, rkind) value) scope { static if (N > 1 && RN == N && kind == Contiguous && rkind == Contiguous) { @@ -2574,7 +2757,7 @@ public: Assignment of a value of `Slice` type to a $(B fully defined slice). +/ void opIndexAssign(RIterator, size_t RN, SliceKind rkind, Slices...) - (scope Slice!(RIterator, RN, rkind) value, Slices slices) scope return + (Slice!(RIterator, RN, rkind) value, Slices slices) return scope if (isFullPureSlice!Slices || isIndexedSlice!Slices) { auto sl = this.lightScope.opIndex(slices); @@ -2585,7 +2768,7 @@ public: static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -2607,7 +2790,7 @@ public: static if (doUnittest) /// Left slice is packed - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : blocks, iota; import mir.ndslice.allocation : slice; @@ -2623,7 +2806,7 @@ public: static if (doUnittest) /// Both slices are packed - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : blocks, iota, pack; import mir.ndslice.allocation : slice; @@ -2698,7 +2881,7 @@ public: /++ Assignment of a regular multidimensional array to a $(B fully defined slice). +/ - void opIndexAssign(T, Slices...)(T[] value, Slices slices) scope return + void opIndexAssign(T, Slices...)(T[] value, Slices slices) return scope if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && (!isDynamicArray!DeepElement || isDynamicArray!T) && DynamicArrayDimensionsCount!(T[]) - DynamicArrayDimensionsCount!DeepElement <= typeof(this.opIndex(slices)).N) @@ -2709,7 +2892,7 @@ public: static if (doUnittest) /// - pure nothrow version(mir_test) unittest + pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -2733,7 +2916,7 @@ public: static if (doUnittest) /// Packed slices - pure nothrow version(mir_test) unittest + pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; import mir.ndslice.topology : blocks; @@ -2772,7 +2955,7 @@ public: sl[0 .. slice.length].opIndexAssign(slice); else sl[0 .. slice.length].opIndexOpAssign!op(slice); - + sl = sl[slice.length .. $]; } assert(sl.empty); @@ -2780,7 +2963,7 @@ public: } /// - void opIndexAssign(T, Slices...)(T concatenation, Slices slices) scope return + void opIndexAssign(T, Slices...)(T concatenation, Slices slices) return scope if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && isConcatenation!T) { auto sl = this.lightScope.opIndex(slices); @@ -2788,10 +2971,11 @@ public: sl.opIndexOpAssignImplConcatenation!""(concatenation); } + static if (!isNumeric!DeepElement) /++ Assignment of a value (e.g. a number) to a $(B fully defined slice). +/ - void opIndexAssign(T, Slices...)(T value, Slices slices) scope return + void opIndexAssign(T, Slices...)(T value, Slices slices) scope if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && (!isDynamicArray!T || isDynamicArray!DeepElement) && DynamicArrayDimensionsCount!T == DynamicArrayDimensionsCount!DeepElement @@ -2802,11 +2986,19 @@ public: if(!sl.anyRUEmpty) sl.opIndexOpAssignImplValue!""(value); } + else + void opIndexAssign(Slices...)(DeepElement value, Slices slices) scope + if (isFullPureSlice!Slices || isIndexedSlice!Slices) + { + auto sl = this.lightScope.opIndex(slices); + if(!sl.anyRUEmpty) + sl.opIndexOpAssignImplValue!""(value); + } static if (doUnittest) /// @safe pure nothrow - version(mir_test) unittest + version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -2833,7 +3025,7 @@ public: static if (doUnittest) /// Packed slices have the same behavior. - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.topology : pack; @@ -2846,19 +3038,30 @@ public: /++ Assignment of a value (e.g. a number) to a $(B fully defined index). +/ - auto ref opIndexAssign(T)(T value, size_t[N] _indexes...) scope return @trusted + auto ref opIndexAssign(T)(T value, size_t[N] _indices...) return scope @trusted { - // check assign safety + // check assign safety static auto ref fun(ref DeepElement t, ref T v) @safe { return t = v; } - return _iterator[indexStride(_indexes)] = value; + return _iterator[indexStride(_indices)] = value; + } + ///ditto + auto ref opIndexAssign()(DeepElement value, size_t[N] _indices...) return scope @trusted + { + import mir.functional: forward; + // check assign safety + static auto ref fun(ref DeepElement t, ref DeepElement v) @safe + { + return t = v; + } + return _iterator[indexStride(_indices)] = forward!value; } static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -2868,7 +3071,7 @@ public: } static if (doUnittest) - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].sliced(2, 3); @@ -2879,14 +3082,14 @@ public: /++ Op Assignment `op=` of a value (e.g. a number) to a $(B fully defined index). +/ - auto ref opIndexOpAssign(string op, T)(T value, size_t[N] _indexes...) scope return @trusted + auto ref opIndexOpAssign(string op, T)(T value, size_t[N] _indices...) return scope @trusted { - // check op safety + // check op safety static auto ref fun(ref DeepElement t, ref T v) @safe { return mixin(`t` ~ op ~ `= v`); } - auto str = indexStride(_indexes); + auto str = indexStride(_indices); static if (op == "^^" && isFloatingPoint!DeepElement && isFloatingPoint!(typeof(value))) { import mir.math.common: pow; @@ -2898,7 +3101,7 @@ public: static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -2908,7 +3111,7 @@ public: } static if (doUnittest) - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].sliced(2, 3); @@ -2920,7 +3123,7 @@ public: Op Assignment `op=` of a value of `Slice` type to a $(B fully defined slice). +/ void opIndexOpAssign(string op, RIterator, SliceKind rkind, size_t RN, Slices...) - (Slice!(RIterator, RN, rkind) value, Slices slices) scope return + (Slice!(RIterator, RN, rkind) value, Slices slices) return scope if (isFullPureSlice!Slices || isIndexedSlice!Slices) { auto sl = this.lightScope.opIndex(slices); @@ -2931,7 +3134,7 @@ public: static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -2952,7 +3155,7 @@ public: static if (doUnittest) /// Left slice is packed - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; import mir.ndslice.topology : blocks, iota; @@ -2968,7 +3171,7 @@ public: static if (doUnittest) /// Both slices are packed - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; import mir.ndslice.topology : blocks, iota, pack; @@ -2985,7 +3188,7 @@ public: /++ Op Assignment `op=` of a regular multidimensional array to a $(B fully defined slice). +/ - void opIndexOpAssign(string op, T, Slices...)(T[] value, Slices slices) scope return + void opIndexOpAssign(string op, T, Slices...)(T[] value, Slices slices) return scope if (isFullPureSlice!Slices && (!isDynamicArray!DeepElement || isDynamicArray!T) && DynamicArrayDimensionsCount!(T[]) - DynamicArrayDimensionsCount!DeepElement <= typeof(this.opIndex(slices)).N) @@ -2996,7 +3199,7 @@ public: static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; auto a = slice!int(2, 3); @@ -3016,13 +3219,13 @@ public: static if (doUnittest) /// Packed slices - @safe pure nothrow - version(mir_test) unittest + @safe pure nothrow + version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; import mir.ndslice.topology : blocks; auto a = slice!int(4, 4); - a.blocks(2, 2)[].opIndexOpAssign!"+"([[0, 1], [2, 3]]); + a.blocks(2, 2)[] += [[0, 1], [2, 3]]; assert(a == [[0, 0, 1, 1], @@ -3031,7 +3234,7 @@ public: [2, 2, 3, 3]]); } - private void opIndexOpAssignImplValue(string op, T)(T value) scope return + private void opIndexOpAssignImplValue(string op, T)(T value) return scope { static if (N > 1 && kind == Contiguous) { @@ -3061,7 +3264,7 @@ public: /++ Op Assignment `op=` of a value (e.g. a number) to a $(B fully defined slice). +/ - void opIndexOpAssign(string op, T, Slices...)(T value, Slices slices) scope return + void opIndexOpAssign(string op, T, Slices...)(T value, Slices slices) return scope if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && (!isDynamicArray!T || isDynamicArray!DeepElement) && DynamicArrayDimensionsCount!T == DynamicArrayDimensionsCount!DeepElement @@ -3075,7 +3278,7 @@ public: static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -3091,7 +3294,7 @@ public: } /// - void opIndexOpAssign(string op,T, Slices...)(T concatenation, Slices slices) scope return + void opIndexOpAssign(string op,T, Slices...)(T concatenation, Slices slices) return scope if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && isConcatenation!T) { auto sl = this.lightScope.opIndex(slices); @@ -3101,7 +3304,7 @@ public: static if (doUnittest) /// Packed slices have the same behavior. - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.topology : pack; @@ -3115,22 +3318,22 @@ public: /++ Increment `++` and Decrement `--` operators for a $(B fully defined index). +/ - auto ref opIndexUnary(string op)(size_t[N] _indexes...) scope return + auto ref opIndexUnary(string op)(size_t[N] _indices...) return scope @trusted // @@@workaround@@@ for Issue 16473 //if (op == `++` || op == `--`) { - // check op safety + // check op safety static auto ref fun(DeepElement t) @safe { return mixin(op ~ `t`); } - return mixin (op ~ `_iterator[indexStride(_indexes)]`); + return mixin (op ~ `_iterator[indexStride(_indices)]`); } static if (doUnittest) /// - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -3141,7 +3344,7 @@ public: // Issue 16473 static if (doUnittest) - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto sl = slice!double(2, 5); @@ -3149,7 +3352,7 @@ public: } static if (doUnittest) - @safe pure nothrow version(mir_test) unittest + @safe pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].sliced(2, 3); @@ -3179,7 +3382,7 @@ public: /++ Increment `++` and Decrement `--` operators for a $(B fully defined slice). +/ - void opIndexUnary(string op, Slices...)(Slices slices) scope return + void opIndexUnary(string op, Slices...)(Slices slices) return scope if (isFullPureSlice!Slices && (op == `++` || op == `--`)) { auto sl = this.lightScope.opIndex(slices); @@ -3190,7 +3393,7 @@ public: static if (doUnittest) /// @safe pure nothrow - version(mir_test) unittest + version(mir_ndslice_test) unittest { import mir.ndslice.allocation; auto a = slice!int(2, 3); @@ -3211,7 +3414,7 @@ alias Slice = mir_slice; /++ Slicing, indexing, and arithmetic operations. +/ -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.dynamic : transposed; @@ -3230,7 +3433,7 @@ pure nothrow version(mir_test) unittest ++tensor[]; tensor[] -= 1; - // `opIndexAssing` accepts only fully defined indexes and slices. + // `opIndexAssing` accepts only fully defined indices and slices. // Use an additional empty slice `[]`. static assert(!__traits(compiles, tensor[0 .. 2] *= 2)); @@ -3245,7 +3448,7 @@ pure nothrow version(mir_test) unittest /++ Operations with rvalue slices. +/ -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.topology: universal; @@ -3279,24 +3482,33 @@ pure nothrow version(mir_test) unittest Creating a slice from text. See also $(MREF std, format). +/ -version(mir_test) unittest +version(mir_ndslice_test) unittest { + import mir.algorithm.iteration: filter, all; + import mir.array.allocation; + import mir.exception; + import mir.functional: not; import mir.ndslice.allocation; - import std.algorithm, std.conv, std.exception, std.format, - std.functional, std.string, std.range; + import mir.parse; + import mir.primitives: empty; + + import std.algorithm: map; + import std.string: lineSplitter, split; + + // std.functional, std.string, std.range; Slice!(int*, 2) toMatrix(string str) { string[][] data = str.lineSplitter.filter!(not!empty).map!split.array; - size_t rows = data .length.enforce("empty input"); - size_t columns = data[0].length.enforce("empty first row"); + size_t rows = data .length.enforce!"empty input"; + size_t columns = data[0].length.enforce!"empty first row"; - data.each!(a => enforce(a.length == columns, "rows have different lengths")); + data.all!(a => a.length == columns).enforce!"rows have different lengths"; auto slice = slice!int(rows, columns); foreach (i, line; data) foreach (j, num; line) - slice[i, j] = num.to!int; + slice[i, j] = num.fromString!int; return slice; } @@ -3306,12 +3518,13 @@ version(mir_test) unittest assert(matrix == [[1, 2, 3], [4, 5, 6]]); // back to text + import std.format; auto text2 = format("%(%(%s %)\n%)\n", matrix); assert(text2 == "1 2 3\n4 5 6\n"); } // Slicing -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; auto a = iota(10, 20, 30, 40); @@ -3324,7 +3537,7 @@ version(mir_test) unittest } // Operator overloading. # 1 -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.topology : iota; @@ -3344,12 +3557,11 @@ pure nothrow version(mir_test) unittest } // Operator overloading. # 2 -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { - import std.algorithm.iteration : map; + import mir.ndslice.topology: map, iota; import mir.array.allocation : array; //import std.bigint; - import std.range : iota; auto matrix = 72 .iota @@ -3367,7 +3579,7 @@ pure nothrow version(mir_test) unittest } // Operator overloading. # 3 -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.topology : iota; @@ -3389,7 +3601,7 @@ pure nothrow version(mir_test) unittest } // Type deduction -version(mir_test) unittest +version(mir_ndslice_test) unittest { // Arrays foreach (T; AliasSeq!(int, const int, immutable int)) @@ -3403,13 +3615,12 @@ version(mir_test) unittest } // Test for map #1 -version(mir_test) unittest +version(mir_ndslice_test) unittest { - import std.algorithm.iteration : map; - import std.range.primitives; + import mir.ndslice.topology: map, byDim; auto slice = [1, 2, 3, 4].sliced(2, 2); - auto r = slice.map!(a => a.map!(a => a * 6)); + auto r = slice.byDim!0.map!(a => a.map!(a => a * 6)); assert(r.front.front == 6); assert(r.front.back == 12); assert(r.back.front == 18); @@ -3418,19 +3629,19 @@ version(mir_test) unittest assert(r[0][1] == 12); assert(r[1][0] == 18); assert(r[1][1] == 24); + + import std.range.primitives; static assert(hasSlicing!(typeof(r))); static assert(isForwardRange!(typeof(r))); static assert(isRandomAccessRange!(typeof(r))); } // Test for map #2 -version(mir_test) unittest +version(mir_ndslice_test) unittest { - import std.algorithm.iteration : map; + import mir.ndslice.topology: map, byDim; import std.range.primitives; - auto data = [1, 2, 3, 4] - //.map!(a => a * 2) - ; + auto data = [1, 2, 3, 4]; static assert(hasSlicing!(typeof(data))); static assert(isForwardRange!(typeof(data))); static assert(isRandomAccessRange!(typeof(data))); @@ -3438,7 +3649,7 @@ version(mir_test) unittest static assert(hasSlicing!(typeof(slice))); static assert(isForwardRange!(typeof(slice))); static assert(isRandomAccessRange!(typeof(slice))); - auto r = slice.map!(a => a.map!(a => a * 6)); + auto r = slice.byDim!0.map!(a => a.map!(a => a * 6)); static assert(hasSlicing!(typeof(r))); static assert(isForwardRange!(typeof(r))); static assert(isRandomAccessRange!(typeof(r))); @@ -3464,8 +3675,8 @@ private bool _checkAssignLengths( size_t LN, size_t RN, SliceKind lkind, SliceKind rkind, ) - (scope Slice!(LIterator, LN, lkind) ls, - scope Slice!(RIterator, RN, rkind) rs) + (Slice!(LIterator, LN, lkind) ls, + Slice!(RIterator, RN, rkind) rs) { static if (isInstanceOf!(SliceIterator, LIterator)) { @@ -3487,7 +3698,7 @@ private bool _checkAssignLengths( } } -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; @@ -3497,7 +3708,7 @@ private bool _checkAssignLengths( assert(!_checkAssignLengths(iota(2, 2), iota(3, 3))); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto slice = new int[15].slicedField(5, 3); @@ -3510,7 +3721,7 @@ pure nothrow version(mir_test) unittest auto col = slice[0..$, 1]; } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); auto b = [1, 2, 3, 4].sliced(2, 2); @@ -3528,7 +3739,7 @@ pure nothrow version(mir_test) unittest assert(a[1] == [1, 2, 0]); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); auto b = [[1, 2], [3, 4]]; @@ -3549,7 +3760,7 @@ pure nothrow version(mir_test) unittest assert(a[1] == [3, 4, 6]); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3572,7 +3783,7 @@ pure nothrow version(mir_test) unittest //assert(a[1] == [5, 5, 9]); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3580,7 +3791,7 @@ pure nothrow version(mir_test) unittest assert(a[1, 2] == 3); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3588,7 +3799,7 @@ pure nothrow version(mir_test) unittest assert(a[[1, 2]] == 3); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3596,7 +3807,7 @@ pure nothrow version(mir_test) unittest assert(a[1, 2] == 3); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3604,7 +3815,7 @@ pure nothrow version(mir_test) unittest assert(a[[1, 2]] == 3); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); auto b = [1, 2, 3, 4].sliced(2, 2); @@ -3622,7 +3833,7 @@ pure nothrow version(mir_test) unittest assert(a[1] == [8, 12, 0]); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3639,7 +3850,7 @@ pure nothrow version(mir_test) unittest assert(a[1] == [8, 12, 0]); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3653,7 +3864,7 @@ pure nothrow version(mir_test) unittest assert(a[1] == [6, 6, 1]); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3661,7 +3872,7 @@ pure nothrow version(mir_test) unittest assert(a[1, 2] == 1); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3669,7 +3880,7 @@ pure nothrow version(mir_test) unittest assert(a[[1, 2]] == 1); } -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { auto a = new int[6].slicedField(2, 3); @@ -3680,7 +3891,7 @@ pure nothrow version(mir_test) unittest assert(a[1] == [0, 0, 1]); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, universal; @@ -3688,13 +3899,13 @@ version(mir_test) unittest assert(sl[0 .. $] == sl); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: canonical, iota; static assert(kindOf!(typeof(iota([1, 2]).canonical[1])) == Contiguous); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; auto s = iota(2, 3); @@ -3728,15 +3939,15 @@ auto ndassign(string op = "", L, R)(L lside, auto ref R rside) @property } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota; import mir.ndslice.allocation: slice; auto scalar = 3; - auto vector = 3.iota.slice; // [0, 1, 2] + auto vector = 3.iota.slice; // [0, 1, 2] // scalar = 5; - scalar.ndassign = 5; + scalar.ndassign = 5; assert(scalar == 5); // vector[] = vector * 2; @@ -3748,7 +3959,7 @@ version(mir_test) unittest assert(vector == [5, 7, 9]); } -version(mir_test) pure nothrow unittest +version(mir_ndslice_test) pure nothrow unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: universal; @@ -3777,7 +3988,7 @@ version(mir_test) pure nothrow unittest assert(immdf2.label!1[1] == 2); } -version(mir_test) pure nothrow unittest +version(mir_ndslice_test) pure nothrow unittest { import mir.ndslice.allocation: slice; import mir.ndslice.topology: universal; @@ -3792,3 +4003,156 @@ version(mir_test) pure nothrow unittest Slice!(LightImmutableOf!(double*), 2, Universal) immvalues = (cast(immutable)df).values; assert(immvalues[0][0] == 5); } + +version(mir_ndslice_test) @safe unittest +{ + import mir.ndslice.allocation; + auto a = rcslice!double([2, 3], 0); + auto b = rcslice!double([2, 3], 0); + a[1, 2] = 3; + b[] = a; + assert(a == b); +} + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.topology: iota, flattened; + + auto m = iota(2, 3, 4); // Contiguous Matrix + auto mFlat = m.flattened; + + for (size_t i = 0; i < m.elementCount; i++) { + assert(m.accessFlat(i) == mFlat[i]); + } +} + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.topology: iota, flattened; + + auto m = iota(3, 4); // Contiguous Matrix + auto x = m.front; // Contiguous Vector + + for (size_t i = 0; i < x.elementCount; i++) { + assert(x.accessFlat(i) == m[0, i]); + } +} + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.topology: iota, flattened; + + auto m = iota(3, 4); // Contiguous Matrix + auto x = m[0 .. $, 0 .. $ - 1]; // Canonical Matrix + auto xFlat = x.flattened; + + for (size_t i = 0; i < x.elementCount; i++) { + assert(x.accessFlat(i) == xFlat[i]); + } +} + + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.topology: iota, flattened; + + auto m = iota(2, 3, 4); // Contiguous Matrix + auto x = m[0 .. $, 0 .. $, 0 .. $ - 1]; // Canonical Matrix + auto xFlat = x.flattened; + + for (size_t i = 0; i < x.elementCount; i++) { + assert(x.accessFlat(i) == xFlat[i]); + } +} + + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.topology: iota, flattened; + import mir.ndslice.dynamic: transposed; + + auto m = iota(2, 3, 4); // Contiguous Matrix + auto x = m.transposed!(2, 1, 0); // Universal Matrix + auto xFlat = x.flattened; + + for (size_t i = 0; i < x.elementCount; i++) { + assert(x.accessFlat(i) == xFlat[i]); + } +} + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.topology: iota, flattened; + import mir.ndslice.dynamic: transposed; + + auto m = iota(3, 4); // Contiguous Matrix + auto x = m.transposed; // Universal Matrix + auto xFlat = x.flattened; + + for (size_t i = 0; i < x.elementCount; i++) { + assert(x.accessFlat(i) == xFlat[i]); + } +} + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.topology: iota, flattened, diagonal; + + auto m = iota(3, 4); // Contiguous Matrix + auto x = m.diagonal; // Universal Vector + + for (size_t i = 0; i < x.elementCount; i++) { + assert(x.accessFlat(i) == m[i, i]); + } +} + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest +{ + import mir.ndslice.topology: iota, flattened; + + auto m = iota(3, 4); // Contiguous Matrix + auto x = m.front!1; // Universal Vector + + for (size_t i = 0; i < x.elementCount; i++) { + assert(x.accessFlat(i) == m[i, 0]); + } +} + +version(mir_ndslice_test) +@safe pure @nogc nothrow +unittest // check it can be compiled +{ + import mir.algebraic; + alias S = Slice!(double*, 2); + alias D = Variant!S; +} + +version(mir_ndslice_test) +unittest +{ + import mir.ndslice; + + auto matrix = slice!short(3, 4); + matrix[] = 0; + matrix.diagonal[] = 1; + + auto row = matrix[2]; + row[3] = 6; + assert(matrix[2, 3] == 6); // D & C index order +} diff --git a/source/mir/ndslice/sorting.d b/source/mir/ndslice/sorting.d index 32e9be2c..5ee53f24 100644 --- a/source/mir/ndslice/sorting.d +++ b/source/mir/ndslice/sorting.d @@ -5,8 +5,8 @@ Note: The combination of $(SUBREF topology, pairwise) with lambda `"a <= b"` (`"a < b"`) and $(SUBREF algorithm, all) can be used to check if an ndslice is sorted (strictly monotonic). - $(SUBREF topology iota) can be used to make an index. - $(SUBREF topology map) and $(SUBREF topology zip) can be used to create Schwartzian transform. + $(SUBREF topology, iota) can be used to make an index. + $(SUBREF topology, map) and $(SUBREF topology, zip) can be used to create Schwartzian transform. See also the examples in the module. @@ -14,9 +14,9 @@ See_also: $(SUBREF topology, flattened) `isSorted` and `isStrictlyMonotonic` -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Andrei Alexandrescu 2008-2016, Ilya Yaroshenko 2016-, -Authors: Andrei Alexandrescu (Phobos), Ilya Yaroshenko (API, rework, Mir adoptation) +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Andrei Alexandrescu (Phobos), Ilia Ki (API, rework, Mir adoptation) Macros: SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -24,7 +24,7 @@ Macros: module mir.ndslice.sorting; /// Check if ndslice is sorted, or strictly monotonic. -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.algorithm.iteration: all; import mir.ndslice.slice: sliced; @@ -48,7 +48,7 @@ module mir.ndslice.sorting; } /// Create index -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.algorithm.iteration: all; import mir.ndslice.allocation: slice; @@ -65,7 +65,7 @@ version(mir_test) unittest } /// Schwartzian transform -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.algorithm.iteration: all; import mir.ndslice.allocation: slice; @@ -83,11 +83,11 @@ version(mir_test) unittest } import mir.ndslice.slice; -import mir.math.common: optmath; +import mir.math.common: fmamath; -@optmath: +@fmamath: -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.algorithm.iteration: all; import mir.ndslice.topology: pairwise; @@ -104,7 +104,7 @@ import mir.math.common: optmath; assert(c.pairwise!"a <= b".all); } -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.algorithm.iteration: all; import mir.ndslice.topology: pairwise; @@ -128,9 +128,9 @@ template sort(alias less = "a < b") import mir.series: Series; static if (__traits(isSame, naryFun!less, less)) { -@optmath: +@fmamath: /++ - Sort one-dimensional series. + Sort n-dimensional slice. +/ Slice!(Iterator, N, kind) sort(Iterator, size_t N, SliceKind kind) (Slice!(Iterator, N, kind) slice) @@ -212,7 +212,7 @@ template sort(alias less = "a < b") } /// -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.algorithm.iteration: all; import mir.ndslice.slice; @@ -227,7 +227,7 @@ template sort(alias less = "a < b") } /// one-dimensional series -pure version(mir_test) unittest +pure version(mir_ndslice_test) unittest { import mir.series; @@ -248,7 +248,7 @@ pure version(mir_test) unittest } /// two-dimensional series -pure version(mir_test) unittest +pure version(mir_ndslice_test) unittest { import mir.series; import mir.ndslice.allocation: uninitSlice; @@ -293,9 +293,12 @@ void quickSortImpl(alias less, Iterator)(Slice!Iterator slice) @trusted auto r = l; r += slice.length; + if(slice.length <= 1) + return; + static if (naive > 1) { - if (slice.length <= naive) + if (slice.length <= naive || __ctfe) { auto p = r; --p; @@ -341,11 +344,6 @@ void quickSortImpl(alias less, Iterator)(Slice!Iterator slice) @trusted return; } } - else - { - if(slice.length <= 1) - return; - } // partition auto lessI = l; @@ -402,10 +400,22 @@ void setPivot(alias less, Iterator)(size_t length, ref Iterator l, ref Iterator medianOf!less(l, e, mid, b, r); } -void medianOf(alias less, Iterator) +void medianOf(alias less, bool leanRight = false, Iterator) + (ref Iterator a, ref Iterator b) @trusted +{ + import mir.utility : swapStars; + + if (less(*b, *a)) { + swapStars(a, b); + } + assert(!less(*b, *a)); +} + +void medianOf(alias less, bool leanRight = false, Iterator) (ref Iterator a, ref Iterator b, ref Iterator c) @trusted { - import mir.utility : swapStars; + import mir.utility : swapStars; + if (less(*c, *a)) // c < a { if (less(*a, *b)) // c < a < b @@ -434,10 +444,32 @@ void medianOf(alias less, Iterator) assert(!less(*c, *b)); } -void medianOf(alias less, Iterator) +void medianOf(alias less, bool leanRight = false, Iterator) + (ref Iterator a, ref Iterator b, ref Iterator c, ref Iterator d) @trusted +{ + import mir.utility: swapStars; + + static if (!leanRight) + { + // Eliminate the rightmost from the competition + if (less(*d, *c)) swapStars(c, d); // c <= d + if (less(*d, *b)) swapStars(b, d); // b <= d + medianOf!less(a, b, c); + } + else + { + // Eliminate the leftmost from the competition + if (less(*b, *a)) swapStars(a, b); // a <= b + if (less(*c, *a)) swapStars(a, c); // a <= c + medianOf!less(b, c, d); + } +} + +void medianOf(alias less, bool leanRight = false, Iterator) (ref Iterator a, ref Iterator b, ref Iterator c, ref Iterator d, ref Iterator e) @trusted { - import mir.utility : swapStars; // Credit: Teppo Niinimäki + import mir.utility: swapStars; // Credit: Teppo Niinimäki + version(unittest) scope(success) { assert(!less(*c, *a)); @@ -466,27 +498,122 @@ void medianOf(alias less, Iterator) } +/++ +Returns: `true` if a sorted array contains the value. + +Params: + test = strict ordering symmetric predicate + +For non-symmetric predicates please use a structure with two `opCall`s or an alias of two global functions, +that correponds to `(array[i], value)` and `(value, array[i])` cases. + +See_also: $(LREF transitionIndex). ++/ +template assumeSortedContains(alias test = "a < b") +{ + import mir.functional: naryFun; + static if (__traits(isSame, naryFun!test, test)) + { +@fmamath: + /++ + Params: + slice = sorted one-dimensional slice or array. + v = value to test with. It is passed to second argument. + +/ + bool assumeSortedContains(Iterator, SliceKind kind, V) + (auto ref Slice!(Iterator, 1, kind) slice, auto ref scope const V v) + { + auto ti = transitionIndex!test(slice, v); + return ti < slice.length && !test(v, slice[ti]); + } + + /// ditto + bool assumeSortedContains(T, V)(scope T[] ar, auto ref scope const V v) + { + return .assumeSortedContains!test(ar.sliced, v); + } + } + else + alias assumeSortedContains = .assumeSortedContains!(naryFun!test); +} + +/++ +Returns: the smallest index of a sorted array such + that the index corresponds to the arrays element at the index according to the predicate + and `-1` if the array doesn't contain corresponding element. + +Params: + test = strict ordering symmetric predicate. + +For non-symmetric predicates please use a structure with two `opCall`s or an alias of two global functions, +that correponds to `(array[i], value)` and `(value, array[i])` cases. + +See_also: $(LREF transitionIndex). ++/ +template assumeSortedEqualIndex(alias test = "a < b") +{ + import mir.functional: naryFun; + static if (__traits(isSame, naryFun!test, test)) + { +@fmamath: + /++ + Params: + slice = sorted one-dimensional slice or array. + v = value to test with. It is passed to second argument. + +/ + sizediff_t assumeSortedEqualIndex(Iterator, SliceKind kind, V) + (auto ref Slice!(Iterator, 1, kind) slice, auto ref scope const V v) + { + auto ti = transitionIndex!test(slice, v); + return ti < slice.length && !test(v, slice[ti]) ? ti : -1; + } + + /// ditto + sizediff_t assumeSortedEqualIndex(T, V)(scope T[] ar, auto ref scope const V v) + { + return .assumeSortedEqualIndex!test(ar.sliced, v); + } + } + else + alias assumeSortedEqualIndex = .assumeSortedEqualIndex!(naryFun!test); +} + +/// +version(mir_ndslice_test) +@safe pure unittest +{ + // sorted: a < b + auto a = [0, 1, 2, 3, 4, 6]; + + assert(a.assumeSortedEqualIndex(2) == 2); + assert(a.assumeSortedEqualIndex(5) == -1); + // <= non strict predicates doesn't work + assert(a.assumeSortedEqualIndex!"a <= b"(2) == -1); +} /++ Computes transition index using binary search. It is low-level API for lower and upper bounds of a sorted array. -See_also: $(SUBREF topology, flattened). +Params: + test = ordering predicate for (`(array[i], value)`) pairs. + +See_also: $(SUBREF topology, assumeSortedEqualIndex). +/ template transitionIndex(alias test = "a < b") { import mir.functional: naryFun; static if (__traits(isSame, naryFun!test, test)) { -@optmath: +@fmamath: /++ Params: slice = sorted one-dimensional slice or array. v = value to test with. It is passed to second argument. +/ size_t transitionIndex(Iterator, SliceKind kind, V) - (auto ref scope Slice!(Iterator, 1, kind) slice, auto ref scope const V v) + (auto ref Slice!(Iterator, 1, kind) slice, auto ref scope const V v) { size_t first = 0, count = slice.length; while (count > 0) @@ -517,6 +644,7 @@ template transitionIndex(alias test = "a < b") } /// +version(mir_ndslice_test) @safe pure unittest { // sorted: a < b @@ -530,28 +658,39 @@ template transitionIndex(alias test = "a < b") assert(j == 3); auto upperBound = a[j .. $]; - assert(a.transitionIndex(a[$-1]) == a.length - 1); - assert(a.transitionIndex!"a <= b"(a[$-1]) == a.length); + assert(a.transitionIndex(a[$ - 1]) == a.length - 1); + assert(a.transitionIndex!"a <= b"(a[$ - 1]) == a.length); } /++ Computes an index for `r` based on the comparison `less`. The index is a sorted array of indices into the original -range. This technique is similar to sorting, but it is more flexible +range. + +This technique is similar to sorting, but it is more flexible because (1) it allows "sorting" of immutable collections, (2) allows binary search even if the original collection does not offer random -access, (3) allows multiple indexes, each on a different predicate, +access, (3) allows multiple indices, each on a different predicate, and (4) may be faster when dealing with large objects. However, using an index may also be slower under certain circumstances due to the extra indirection, and is always larger than a sorting-based solution because it needs space for the index in addition to the original collection. The complexity is the same as `sort`'s. + +Can be combined with $(SUBREF topology, indexed) to create a view that is sorted +based on the index. + Params: less = The comparison to use. r = The slice/array to index. -Returns: Index slice/array. + +Returns: + Index slice/array. + +See_Also: + $(HTTPS numpy.org/doc/stable/reference/generated/numpy.argsort.html, numpy.argsort) +/ -Slice!(I*) makeIndex(I = size_t, alias less = "a < b", Iterator, SliceKind kind)(scope Slice!(Iterator, 1, kind) r) +Slice!(I*) makeIndex(I = size_t, alias less = "a < b", Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) r) { import mir.functional: naryFun; import mir.ndslice.allocation: slice; @@ -570,7 +709,9 @@ I[] makeIndex(I = size_t, alias less = "a < b", T)(scope T[] r) } /// -@system unittest +version(mir_ndslice_test) +@safe pure nothrow +unittest { import mir.algorithm.iteration: all; import mir.ndslice.topology: indexed, pairwise; @@ -580,3 +721,1139 @@ I[] makeIndex(I = size_t, alias less = "a < b", T)(scope T[] r) assert(arr.indexed(index).pairwise!"a < b".all); } + +/// Sort based on index created from a separate array +version(mir_ndslice_test) +@safe pure nothrow +unittest +{ + import mir.algorithm.iteration: equal; + import mir.ndslice.topology: indexed; + + immutable arr0 = [ 2, 3, 1, 5, 0 ]; + immutable arr1 = [ 1, 5, 4, 2, -1 ]; + + auto index = makeIndex(arr0); + assert(index.equal([4, 2, 0, 1, 3])); + auto view = arr1.indexed(index); + assert(view.equal([-1, 4, 1, 5, 2])); +} + +/++ +Partitions `slice` around `pivot` using comparison function `less`, algorithm +akin to $(LINK2 https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme, +Hoare partition). Specifically, permutes elements of `slice` and returns +an index `k < slice.length` such that: + +$(UL + +$(LI `slice[pivot]` is swapped to `slice[k]`) + + +$(LI All elements `e` in subrange `slice[0 .. k]` satisfy `!less(slice[k], e)` +(i.e. `slice[k]` is greater than or equal to each element to its left according +to predicate `less`)) + +$(LI All elements `e` in subrange `slice[k .. $]` satisfy +`!less(e, slice[k])` (i.e. `slice[k]` is less than or equal to each element to +its right according to predicate `less`))) + +If `slice` contains equivalent elements, multiple permutations of `slice` may +satisfy these constraints. In such cases, `pivotPartition` attempts to +distribute equivalent elements fairly to the left and right of `k` such that `k` +stays close to `slice.length / 2`. + +Params: + less = The predicate used for comparison + +Returns: + The new position of the pivot + +See_Also: + $(HTTP jgrcs.info/index.php/jgrcs/article/view/142, Engineering of a Quicksort + Partitioning Algorithm), D. Abhyankar, Journal of Global Research in Computer + Science, February 2011. $(HTTPS youtube.com/watch?v=AxnotgLql0k, ACCU 2016 + Keynote), Andrei Alexandrescu. ++/ +@trusted +template pivotPartition(alias less = "a < b") +{ + import mir.functional: naryFun; + + static if (__traits(isSame, naryFun!less, less)) + { + /++ + Params: + slice = slice being partitioned + pivot = The index of the pivot for partitioning, must be less than + `slice.length` or `0` if `slice.length` is `0` + +/ + size_t pivotPartition(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice, + size_t pivot) + { + assert(pivot < slice.elementCount || slice.elementCount == 0 && pivot == 0, "pivotPartition: pivot must be less than the elementCount of the slice or the slice must be empty and pivot zero"); + + if (slice.elementCount <= 1) return 0; + + import mir.ndslice.topology: flattened; + + auto flattenedSlice = slice.flattened; + auto frontI = flattenedSlice._iterator; + auto lastI = frontI + flattenedSlice.length - 1; + auto pivotI = frontI + pivot; + pivotPartitionImpl!less(frontI, lastI, pivotI); + return pivotI - frontI; + } + } else { + alias pivotPartition = .pivotPartition!(naryFun!less); + } +} + +/// pivotPartition with 1-dimensional Slice +version(mir_ndslice_test) +@safe pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: all; + + auto x = [5, 3, 2, 6, 4, 1, 3, 7].sliced; + size_t pivot = pivotPartition(x, x.length / 2); + + assert(x[0 .. pivot].all!(a => a <= x[pivot])); + assert(x[pivot .. $].all!(a => a >= x[pivot])); +} + +/// pivotPartition with 2-dimensional Slice +version(mir_ndslice_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + import mir.ndslice.topology: flattened; + import mir.algorithm.iteration: all; + + auto x = [ + [5, 3, 2, 6], + [4, 1, 3, 7] + ].fuse; + + size_t pivot = pivotPartition(x, x.elementCount / 2); + + auto xFlattened = x.flattened; + assert(xFlattened[0 .. pivot].all!(a => a <= xFlattened[pivot])); + assert(xFlattened[pivot .. $].all!(a => a >= xFlattened[pivot])); +} + +version(mir_ndslice_test) +@safe +unittest +{ + void test(alias less)() + { + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: all, equal; + + Slice!(int*) x; + size_t pivot; + + x = [-9, -4, -2, -2, 9].sliced; + pivot = pivotPartition!less(x, x.length / 2); + + assert(x[0 .. pivot].all!(a => a <= x[pivot])); + assert(x[pivot .. $].all!(a => a >= x[pivot])); + + x = [9, 2, 8, -5, 5, 4, -8, -4, 9].sliced; + pivot = pivotPartition!less(x, x.length / 2); + + assert(x[0 .. pivot].all!(a => a <= x[pivot])); + assert(x[pivot .. $].all!(a => a >= x[pivot])); + + x = [ 42 ].sliced; + pivot = pivotPartition!less(x, x.length / 2); + + assert(pivot == 0); + assert(x.equal([ 42 ])); + + x = [ 43, 42 ].sliced; + pivot = pivotPartition!less(x, 0); + assert(pivot == 1); + assert(x.equal([ 42, 43 ])); + + x = [ 43, 42 ].sliced; + pivot = pivotPartition!less(x, 1); + + assert(pivot == 0); + assert(x.equal([ 42, 43 ])); + + x = [ 42, 42 ].sliced; + pivot = pivotPartition!less(x, 0); + + assert(pivot == 0 || pivot == 1); + assert(x.equal([ 42, 42 ])); + + pivot = pivotPartition!less(x, 1); + + assert(pivot == 0 || pivot == 1); + assert(x.equal([ 42, 42 ])); + } + test!"a < b"; + static bool myLess(int a, int b) + { + static bool bogus; + if (bogus) throw new Exception(""); // just to make it no-nothrow + return a < b; + } + test!myLess; +} + +@trusted +template pivotPartitionImpl(alias less) +{ + void pivotPartitionImpl(Iterator) + (ref Iterator frontI, + ref Iterator lastI, + ref Iterator pivotI) + { + assert(pivotI <= lastI && pivotI >= frontI, "pivotPartition: pivot must be less than the length of slice or slice must be empty and pivot zero"); + + if (frontI == lastI) return; + + import mir.utility: swapStars; + + // Pivot at the front + swapStars(pivotI, frontI); + + // Fork implementation depending on nothrow copy, assignment, and + // comparison. If all of these are nothrow, use the specialized + // implementation discussed at + // https://youtube.com/watch?v=AxnotgLql0k. + static if (is(typeof( + () nothrow { auto x = frontI; x = frontI; return less(*x, *x); } + ))) + { + // Plant the pivot in the end as well as a sentinel + auto loI = frontI; + auto hiI = lastI; + auto save = *hiI; + *hiI = *frontI; // Vacancy is in r[$ - 1] now + + // Start process + for (;;) + { + // Loop invariant + version(mir_ndslice_test) + { + // this used to import std.algorithm.all, but we want to + // save imports when unittests are enabled if possible. + size_t len = lastI - frontI + 1; + foreach (x; 0 .. (loI - frontI)) + assert(!less(*frontI, frontI[x]), "pivotPartition: *frontI must not be less than frontI[x]"); + foreach (x; (hiI - frontI + 1) .. len) + assert(!less(frontI[x], *frontI), "pivotPartition: frontI[x] must not be less than *frontI"); + } + do ++loI; while (less(*loI, *frontI)); + *(hiI) = *(loI); + // Vacancy is now in slice[lo] + do --hiI; while (less(*frontI, *hiI)); + if (loI >= hiI) break; + *(loI) = *(hiI); + // Vacancy is not in slice[hi] + } + // Fixup + assert(loI - hiI <= 2, "pivotPartition: Following compare not possible"); + assert(!less(*frontI, *hiI), "pivotPartition: *hiI must not be less than *frontI"); + if (loI - hiI == 2) + { + assert(!less(hiI[1], *frontI), "pivotPartition: *(hiI + 1) must not be less than *frontI"); + *(loI) = hiI[1]; + --loI; + } + *loI = save; + if (less(*frontI, save)) --loI; + assert(!less(*frontI, *loI), "pivotPartition: *frontI must not be less than *loI"); + } else { + auto loI = frontI; + ++loI; + auto hiI = lastI; + + loop: for (;; loI++, hiI--) + { + for (;; ++loI) + { + if (loI > hiI) break loop; + if (!less(*loI, *frontI)) break; + } + // found the left bound: !less(*loI, *frontI) + assert(loI <= hiI, "pivotPartition: loI must be less or equal than hiI"); + for (;; --hiI) + { + if (loI >= hiI) break loop; + if (!less(*frontI, *hiI)) break; + } + // found the right bound: !less(*frontI, *hiI), swap & make progress + assert(!less(*loI, *hiI), "pivotPartition: *lowI must not be less than *hiI"); + swapStars(loI, hiI); + } + --loI; + } + + swapStars(loI, frontI); + pivotI = loI; + } +} + +version(mir_ndslice_test) +@safe pure nothrow +unittest { + import mir.ndslice.sorting: partitionAt; + import mir.ndslice.allocation: rcslice; + auto x = rcslice!double(4); + x[0] = 3; + x[1] = 2; + x[2] = 1; + x[3] = 0; + partitionAt!("a > b")(x, 2); +} + + +version(mir_ndslice_test) +@trusted pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: all; + + auto x = [5, 3, 2, 6, 4, 1, 3, 7].sliced; + auto frontI = x._iterator; + auto lastI = x._iterator + x.length - 1; + auto pivotI = frontI + x.length / 2; + alias less = (a, b) => (a < b); + pivotPartitionImpl!less(frontI, lastI, pivotI); + size_t pivot = pivotI - frontI; + + assert(x[0 .. pivot].all!(a => a <= x[pivot])); + assert(x[pivot .. $].all!(a => a >= x[pivot])); +} + +/++ +Partitions `slice`, such that all elements `e1` from `slice[0]` to `slice[nth]` +satisfy `!less(slice[nth], e1)`, and all elements `e2` from `slice[nth]` to +`slice[slice.length]` satisfy `!less(e2, slice[nth])`. This effectively reorders +`slice` such that `slice[nth]` refers to the element that would fall there if +the range were fully sorted. Performs an expected $(BIGOH slice.length) +evaluations of `less` and `swap`, with a worst case of $(BIGOH slice.length^^2). + +This function implements the [Fast, Deterministic Selection](https://erdani.com/research/sea2017.pdf) +algorithm that is implemented in the [`topN`](https://dlang.org/library/std/algorithm/sorting/top_n.html) +function in D's standard library (as of version `2.092.0`). + +Params: + less = The predicate to sort by. + +See_Also: + $(LREF pivotPartition), https://erdani.com/research/sea2017.pdf + ++/ +template partitionAt(alias less = "a < b") +{ + import mir.functional: naryFun; + + static if (__traits(isSame, naryFun!less, less)) + { + /++ + Params: + slice = n-dimensional slice + nth = The index of the element that should be in sorted position after the + function is finished. + +/ + void partitionAt(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice, size_t nth) @trusted nothrow @nogc + { + import mir.qualifier: lightScope; + import core.lifetime: move; + import mir.ndslice.topology: flattened; + + assert(slice.elementCount > 0, "partitionAt: slice must have elementCount greater than 0"); + assert(nth >= 0, "partitionAt: nth must be greater than or equal to zero"); + assert(nth < slice.elementCount, "partitionAt: nth must be less than the elementCount of the slice"); + + bool useSampling = true; + auto flattenedSlice = slice.move.flattened; + auto frontI = flattenedSlice._iterator.lightScope; + auto lastI = frontI + (flattenedSlice.length - 1); + partitionAtImpl!less(frontI, lastI, nth, useSampling); + } + } + else + alias partitionAt = .partitionAt!(naryFun!less); +} + +/// Partition 1-dimensional slice at nth +version(mir_ndslice_test) +@safe pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 2; + auto x = [3, 1, 5, 2, 0].sliced; + x.partitionAt(nth); + assert(x[nth] == 2); +} + +/// Partition 2-dimensional slice +version(mir_ndslice_test) +@safe pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 4; + auto x = [3, 1, 5, 2, 0, 7].sliced(3, 2); + x.partitionAt(nth); + assert(x[2, 0] == 5); +} + +/// Can supply alternate ordering function +version(mir_ndslice_test) +@safe pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 2; + auto x = [3, 1, 5, 2, 0].sliced; + x.partitionAt!("a > b")(nth); + assert(x[nth] == 2); +} + +// Check issue #328 fixed +version(mir_ndslice_test) +@safe pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + auto slice = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17].sliced; + partitionAt(slice, 8); + partitionAt(slice, 9); +} + +version(unittest) { + template checkPartitionAtAll(alias less = "a < b") + { + import mir.functional: naryFun; + import mir.ndslice.slice: SliceKind, Slice; + + static if (__traits(isSame, naryFun!less, less)) + { + @safe pure nothrow + static bool checkPartitionAtAll + (Iterator, SliceKind kind)( + Slice!(Iterator, 1, kind) x) + { + auto x_sorted = x.dup; + x_sorted.sort!less; + + bool result = true; + + foreach (nth; 0 .. x.length) + { + auto x_i = x.dup; + x_i.partitionAt!less(nth); + if (x_i[nth] != x_sorted[nth]) { + result = false; + break; + } + } + return result; + } + } else { + alias checkPartitionAtAll = .checkPartitionAtAll!(naryFun!less); + } + } +} + +version(mir_ndslice_test) +@safe pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + assert(checkPartitionAtAll([2, 2].sliced)); + + assert(checkPartitionAtAll([3, 1, 5, 2, 0].sliced)); + assert(checkPartitionAtAll([3, 1, 5, 0, 2].sliced)); + assert(checkPartitionAtAll([0, 0, 4, 3, 3].sliced)); + assert(checkPartitionAtAll([5, 1, 5, 1, 5].sliced)); + assert(checkPartitionAtAll([2, 2, 0, 0, 0].sliced)); + + assert(checkPartitionAtAll([ 2, 12, 10, 8, 1, 20, 19, 1, 2, 7].sliced)); + assert(checkPartitionAtAll([ 4, 18, 16, 0, 15, 6, 2, 17, 10, 16].sliced)); + assert(checkPartitionAtAll([ 7, 5, 9, 4, 4, 2, 12, 20, 15, 15].sliced)); + + assert(checkPartitionAtAll([17, 87, 58, 50, 34, 98, 25, 77, 88, 79].sliced)); + + assert(checkPartitionAtAll([ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22].sliced)); + assert(checkPartitionAtAll([21, 3, 11, 22, 24, 12, 14, 12, 15, 15, 1, 3, 12, 15, 25, 19, 9, 16, 16, 19].sliced)); + assert(checkPartitionAtAll([22, 6, 18, 0, 1, 8, 13, 13, 16, 19, 23, 17, 4, 6, 12, 24, 15, 20, 11, 17].sliced)); + assert(checkPartitionAtAll([19, 23, 14, 5, 12, 3, 13, 7, 25, 25, 24, 9, 21, 25, 12, 22, 15, 22, 7, 11].sliced)); + assert(checkPartitionAtAll([ 0, 2, 7, 16, 2, 20, 1, 11, 17, 5, 22, 17, 25, 13, 14, 5, 22, 21, 24, 14].sliced)); +} + +private @trusted pure nothrow @nogc +void partitionAtImpl(alias less, Iterator)( + Iterator loI, + Iterator hiI, + size_t n, + bool useSampling) +{ + assert(loI <= hiI, "partitionAtImpl: frontI must be less than or equal to lastI"); + + import mir.utility: swapStars; + import mir.functional: reverseArgs; + + Iterator pivotI; + size_t len; + + for (;;) { + len = hiI - loI + 1; + + if (len <= 1) { + break; + } + + if (n == 0) { + pivotI = loI; + foreach (i; 1 .. len) { + if (less(loI[i], *pivotI)) { + pivotI = loI + i; + } + } + swapStars(loI + n, pivotI); + break; + } + + if (n + 1 == len) { + pivotI = loI; + foreach (i; 1 .. len) { + if (reverseArgs!less(loI[i], *pivotI)) { + pivotI = loI + i; + } + } + swapStars(loI + n, pivotI); + break; + } + + if (len <= 12) { + pivotI = loI + len / 2; + pivotPartitionImpl!less(loI, hiI, pivotI); + } else if (n * 16 <= (len - 1) * 7) { + pivotI = partitionAtPartitionOffMedian!(less, false)(loI, hiI, n, useSampling); + // Quality check + if (useSampling) + { + auto pivot = pivotI - loI; + if (pivot < n) + { + if (pivot * 4 < len) + { + useSampling = false; + } + } + else if ((len - pivot) * 8 < len * 3) + { + useSampling = false; + } + } + } else if (n * 16 >= (len - 1) * 9) { + pivotI = partitionAtPartitionOffMedian!(less, true)(loI, hiI, n, useSampling); + // Quality check + if (useSampling) + { + auto pivot = pivotI - loI; + if (pivot < n) + { + if (pivot * 8 < len * 3) + { + useSampling = false; + } + } + else if ((len - pivot) * 4 < len) + { + useSampling = false; + } + } + } else { + pivotI = partitionAtPartition!less(loI, hiI, n, useSampling); + // Quality check + if (useSampling) { + auto pivot = pivotI - loI; + if (pivot * 9 < len * 2 || pivot * 9 > len * 7) + { + // Failed - abort sampling going forward + useSampling = false; + } + } + } + + if (n < (pivotI - loI)) { + hiI = pivotI - 1; + } else if (n > (pivotI - loI)) { + n -= (pivotI - loI + 1); + loI = pivotI; + ++loI; + } else { + break; + } + } +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 2; + auto x = [3, 1, 5, 2, 0].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.elementCount - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, 1, true); + assert(x[nth] == 2); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 4; + auto x = [3, 1, 5, 2, 0, 7].sliced(3, 2); + auto frontI = x._iterator; + auto lastI = frontI + x.elementCount - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); + assert(x[2, 0] == 5); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 1; + auto x = [0, 0, 4, 3, 3].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.elementCount - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); + assert(x[nth] == 0); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 2; + auto x = [0, 0, 4, 3, 3].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.elementCount - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); + assert(x[nth] == 3); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 3; + auto x = [0, 0, 4, 3, 3].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.elementCount - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); + assert(x[nth] == 3); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 4; + auto x = [ 2, 12, 10, 8, 1, 20, 19, 1, 2, 7].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.elementCount - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); + assert(x[nth] == 7); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 5; + auto x = [ 2, 12, 10, 8, 1, 20, 19, 1, 2, 7].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.elementCount - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); + assert(x[nth] == 8); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + + size_t nth = 6; + auto x = [ 2, 12, 10, 8, 1, 20, 19, 1, 2, 7].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.elementCount - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, nth, true); + assert(x[nth] == 10); +} + +// Check all partitionAt +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + import mir.ndslice.allocation: slice; + + static immutable raw = [ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22]; + + static void fill(T)(T x) { + for (size_t i = 0; i < x.length; i++) { + x[i] = raw[i]; + } + } + auto x = slice!int(raw.length); + fill(x); + auto x_sort = x.dup; + x_sort = x_sort.sort; + size_t i = 0; + while (i < raw.length) { + auto frontI = x._iterator; + auto lastI = frontI + x.length - 1; + partitionAtImpl!((a, b) => (a < b))(frontI, lastI, i, true); + assert(x[i] == x_sort[i]); + fill(x); + i++; + } +} + +private @trusted pure nothrow @nogc +Iterator partitionAtPartition(alias less, Iterator)( + ref Iterator frontI, + ref Iterator lastI, + size_t n, + bool useSampling) +{ + size_t len = lastI - frontI + 1; + + assert(len >= 9 && n < len, "partitionAtPartition: length must be longer than 9 and n must be less than r.length"); + + size_t ninth = len / 9; + size_t pivot = ninth / 2; + // Position subrange r[loI .. hiI] to have length equal to ninth and its upper + // median r[loI .. hiI][$ / 2] in exactly the same place as the upper median + // of the entire range r[$ / 2]. This is to improve behavior for searching + // the median in already sorted ranges. + auto loI = frontI; + loI += len / 2 - pivot; + auto hiI = loI; + hiI += ninth; + + // We have either one straggler on the left, one on the right, or none. + assert(loI - frontI <= lastI - hiI + 1 || lastI - hiI <= loI - frontI + 1, "partitionAtPartition: straggler check failed for loI, len, hiI"); + assert(loI - frontI >= ninth * 4, "partitionAtPartition: loI - frontI >= ninth * 4"); + assert((lastI + 1) - hiI >= ninth * 4, "partitionAtPartition: (lastI + 1) - hiI >= ninth * 4"); + + // Partition in groups of 3, and the mid tertile again in groups of 3 + if (!useSampling) { + auto loI_ = loI; + loI_ -= ninth; + auto hiI_ = hiI; + hiI_ += ninth; + p3!(less, Iterator)(frontI, lastI, loI_, hiI_); + } + p3!(less, Iterator)(frontI, lastI, loI, hiI); + + // Get the median of medians of medians + // Map the full interval of n to the full interval of the ninth + pivot = (n * (ninth - 1)) / (len - 1); + if (hiI > loI) { + auto hiI_minus = hiI; + --hiI_minus; + partitionAtImpl!less(loI, hiI_minus, pivot, useSampling); + } + + auto pivotI = loI; + pivotI += pivot; + + return expandPartition!less(frontI, lastI, loI, pivotI, hiI); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + auto x = [ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22].sliced; + auto x_sort = x.dup; + x_sort = x_sort.sort; + auto frontI = x._iterator; + auto lastI = frontI + x.length - 1; + size_t n = x.length / 2; + partitionAtPartition!((a, b) => (a < b))(frontI, lastI, n, true); + assert(x[n - 1] == x_sort[n - 1]); +} + +private @trusted pure nothrow @nogc +Iterator partitionAtPartitionOffMedian(alias less, bool leanRight, Iterator)( + ref Iterator frontI, + ref Iterator lastI, + size_t n, + bool useSampling) +{ + size_t len = lastI - frontI + 1; + + assert(len >= 12, "partitionAtPartitionOffMedian: len must be greater than 11"); + assert(n < len, "partitionAtPartitionOffMedian: n must be less than len"); + auto _4 = len / 4; + auto leftLimitI = frontI; + static if (leanRight) + leftLimitI += 2 * _4; + else + leftLimitI += _4; + // Partition in groups of 4, and the left quartile again in groups of 3 + if (!useSampling) + { + auto leftLimit_plus_4 = leftLimitI; + leftLimit_plus_4 += _4; + p4!(less, leanRight)(frontI, lastI, leftLimitI, leftLimit_plus_4); + } + auto _12 = _4 / 3; + auto loI = leftLimitI; + loI += _12; + auto hiI = loI; + hiI += _12; + p3!less(frontI, lastI, loI, hiI); + + // Get the median of medians of medians + // Map the full interval of n to the full interval of the ninth + auto pivot = (n * (_12 - 1)) / (len - 1); + if (hiI > loI) { + auto hiI_minus = hiI; + --hiI_minus; + partitionAtImpl!less(loI, hiI_minus, pivot, useSampling); + } + auto pivotI = loI; + pivotI += pivot; + return expandPartition!less(frontI, lastI, loI, pivotI, hiI); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: equal; + + auto x = [ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.length - 1; + partitionAtPartitionOffMedian!((a, b) => (a < b), false)(frontI, lastI, 5, true); + assert(x.equal([6, 7, 8, 9, 5, 0, 2, 7, 9, 15, 10, 25, 11, 10, 13, 18, 17, 13, 25, 22])); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: equal; + + auto x = [ 6, 7, 10, 25, 5, 10, 9, 0, 2, 15, 7, 9, 11, 8, 13, 18, 17, 13, 25, 22].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.length - 1; + partitionAtPartitionOffMedian!((a, b) => (a < b), true)(frontI, lastI, 15, true); + assert(x.equal([6, 7, 8, 7, 5, 2, 9, 0, 9, 15, 25, 10, 11, 10, 13, 18, 17, 13, 25, 22])); +} + +private @trusted +void p3(alias less, Iterator)( + Iterator frontI, + Iterator lastI, + Iterator loI, + Iterator hiI) +{ + assert(loI <= hiI && hiI <= lastI, "p3: loI must be less than or equal to hiI and hiI must be less than or equal to lastI"); + immutable diffI = hiI - loI; + Iterator lo_loI; + Iterator hi_loI; + for (; loI < hiI; ++loI) + { + lo_loI = loI; + lo_loI -= diffI; + hi_loI = loI; + hi_loI += diffI; + assert(lo_loI >= frontI, "p3: lo_loI must be greater than or equal to frontI"); + assert(hi_loI <= lastI, "p3: hi_loI must be less than or equal to lastI"); + medianOf!less(lo_loI, loI, hi_loI); + } +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: equal; + + auto x = [3, 4, 0, 5, 2, 1].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.length - 1; + auto loI = frontI + 2; + auto hiI = frontI + 4; + p3!((a, b) => (a < b))(frontI, lastI, loI, hiI); + assert(x.equal([0, 1, 2, 4, 3, 5])); +} + +private @trusted +template p4(alias less, bool leanRight) +{ + void p4(Iterator)( + Iterator frontI, + Iterator lastI, + Iterator loI, + Iterator hiI) + { + assert(loI <= hiI && hiI <= lastI, "p4: loI must be less than or equal to hiI and hiI must be less than or equal to lastI"); + + immutable diffI = hiI - loI; + immutable diffI2 = diffI * 2; + + Iterator lo_loI; + Iterator hi_loI; + + static if (leanRight) + Iterator lo2_loI; + else + Iterator hi2_loI; + + for (; loI < hiI; ++loI) + { + lo_loI = loI - diffI; + hi_loI = loI + diffI; + + assert(lo_loI >= frontI, "p4: lo_loI must be greater than or equal to frontI"); + assert(hi_loI <= lastI, "p4: hi_loI must be less than or equal to lastI"); + + static if (leanRight) { + lo2_loI = loI - diffI2; + assert(lo2_loI >= frontI, "lo2_loI must be greater than or equal to frontI"); + medianOf!(less, leanRight)(lo2_loI, lo_loI, loI, hi_loI); + } else { + hi2_loI = loI + diffI2; + assert(hi2_loI <= lastI, "hi2_loI must be less than or equal to lastI"); + medianOf!(less, leanRight)(lo_loI, loI, hi_loI, hi2_loI); + } + } + } +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: equal; + + auto x = [3, 4, 0, 7, 2, 6, 5, 1, 4].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.length - 1; + auto loI = frontI + 3; + auto hiI = frontI + 5; + p4!((a, b) => (a < b), false)(frontI, lastI, loI, hiI); + assert(x.equal([3, 1, 0, 4, 2, 6, 4, 7, 5])); +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest { + import mir.ndslice.slice: sliced; + import mir.algorithm.iteration: equal; + + auto x = [3, 4, 0, 8, 2, 7, 5, 1, 4, 3].sliced; + auto frontI = x._iterator; + auto lastI = frontI + x.length - 1; + auto loI = frontI + 4; + auto hiI = frontI + 6; + p4!((a, b) => (a < b), true)(frontI, lastI, loI, hiI); + assert(x.equal([0, 4, 2, 1, 3, 7, 5, 8, 4, 3])); +} + +private @trusted +template expandPartition(alias less) +{ + Iterator expandPartition(Iterator)( + ref Iterator frontI, + ref Iterator lastI, + ref Iterator loI, + ref Iterator pivotI, + ref Iterator hiI) + { + import mir.algorithm.iteration: all; + + assert(frontI <= loI, "expandPartition: frontI must be less than or equal to loI"); + assert(loI <= pivotI, "expandPartition: loI must be less than or equal pivotI"); + assert(pivotI < hiI, "expandPartition: pivotI must be less than hiI"); + assert(hiI <= lastI, "expandPartition: hiI must be less than or equal to lastI"); + + foreach(x; loI .. (pivotI + 1)) + assert(!less(*pivotI, *x), "expandPartition: loI .. (pivotI + 1) failed test"); + foreach(x; (pivotI + 1) .. hiI) + assert(!less(*x, *pivotI), "expandPartition: (pivotI + 1) .. hiI failed test"); + + import mir.utility: swapStars; + import mir.algorithm.iteration: all; + // We work with closed intervals! + --hiI; + + auto leftI = frontI; + auto rightI = lastI; + loop: for (;; ++leftI, --rightI) + { + for (;; ++leftI) + { + if (leftI == loI) break loop; + if (!less(*leftI, *pivotI)) break; + } + for (;; --rightI) + { + if (rightI == hiI) break loop; + if (!less(*pivotI, *rightI)) break; + } + swapStars(leftI, rightI); + } + + foreach(x; loI .. (pivotI + 1)) + assert(!less(*pivotI, *x), "expandPartition: loI .. (pivotI + 1) failed less than test"); + foreach(x; (pivotI + 1) .. (hiI + 1)) + assert(!less(*x, *pivotI), "expandPartition: (pivotI + 1) .. (hiI + 1) failed less than test"); + foreach(x; frontI .. leftI) + assert(!less(*pivotI, *x), "expandPartition: frontI .. leftI failed less than test"); + foreach(x; (rightI + 1) .. (lastI + 1)) + assert(!less(*x, *pivotI), "expandPartition: (rightI + 1) .. (lastI + 1) failed less than test"); + + auto oldPivotI = pivotI; + + if (leftI < loI) + { + // First loop: spend r[loI .. pivot] + for (; loI < pivotI; ++leftI) + { + if (leftI == loI) goto done; + if (!less(*oldPivotI, *leftI)) continue; + --pivotI; + assert(!less(*oldPivotI, *pivotI), "expandPartition: less check failed"); + swapStars(leftI, pivotI); + } + // Second loop: make leftI and pivot meet + for (;; ++leftI) + { + if (leftI == pivotI) goto done; + if (!less(*oldPivotI, *leftI)) continue; + for (;;) + { + if (leftI == pivotI) goto done; + --pivotI; + if (less(*pivotI, *oldPivotI)) + { + swapStars(leftI, pivotI); + break; + } + } + } + } + + // First loop: spend r[lo .. pivot] + for (; hiI != pivotI; --rightI) + { + if (rightI == hiI) goto done; + if (!less(*rightI, *oldPivotI)) continue; + ++pivotI; + assert(!less(*pivotI, *oldPivotI), "expandPartition: less check failed"); + swapStars(rightI, pivotI); + } + // Second loop: make leftI and pivotI meet + for (; rightI > pivotI; --rightI) + { + if (!less(*rightI, *oldPivotI)) continue; + while (rightI > pivotI) + { + ++pivotI; + if (less(*oldPivotI, *pivotI)) + { + swapStars(rightI, pivotI); + break; + } + } + } + + done: + swapStars(oldPivotI, pivotI); + + + foreach(x; frontI .. (pivotI + 1)) + assert(!less(*pivotI, *x), "expandPartition: frontI .. (pivotI + 1) failed test"); + foreach(x; (pivotI + 1) .. (lastI + 1)) + assert(!less(*x, *pivotI), "expandPartition: (pivotI + 1) .. (lastI + 1) failed test"); + return pivotI; + } +} + +version(mir_ndslice_test) +@trusted pure nothrow +unittest +{ + import mir.ndslice.slice: sliced; + + auto a = [ 10, 5, 3, 4, 8, 11, 13, 3, 9, 4, 10 ].sliced; + auto frontI = a._iterator; + auto lastI = frontI + a.length - 1; + auto loI = frontI + 4; + auto pivotI = frontI + 5; + auto hiI = frontI + 6; + assert(expandPartition!((a, b) => a < b)(frontI, lastI, loI, pivotI, hiI) == (frontI + 9)); +} + +version(mir_ndslice_test) +unittest +{ + import std.random; + import mir.ndslice.sorting: sort; + + static struct StructA + { + double val0; + double val1; + double val2; + } + + static struct StructB + { + ulong productId; + StructA strA; + } + + auto createStructBArray(uint nbTrades) + { + auto rnd = Random(42); + + auto p = StructA(0,0,0); + + StructB[] ret; + foreach(i;0..nbTrades) + { + ret ~= StructB(uniform(0, nbTrades, rnd), p); + } + + return ret; + } + + auto arrayB = createStructBArray(10000).sort!((a,b) => a.productId // | 1 2 | static immutable c = [1, 2]; - import std.stdio; assert(iota(2, 2).antidiagonal == c); } /// -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { // ------- // | 0 1 2 | @@ -980,7 +1115,7 @@ in assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive" ~ tailErrorMessage!()); } -body +do { size_t[N] lengths; size_t[N] rlengths = rlengths_; @@ -1004,7 +1139,7 @@ body } /// -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.allocation; @@ -1032,7 +1167,7 @@ pure nothrow version(mir_test) unittest } /// Diagonal blocks -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.allocation; @@ -1064,7 +1199,7 @@ pure nothrow version(mir_test) unittest } /// Matrix divided into vertical blocks -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.slice; @@ -1109,7 +1244,7 @@ in assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive" ~ tailErrorMessage!()); } -body +do { size_t[N] rls = rlengths; size_t[N] lengths; @@ -1136,7 +1271,7 @@ body /// @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.slice; @@ -1159,7 +1294,7 @@ version(mir_test) unittest } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.slice; @@ -1180,7 +1315,7 @@ version(mir_test) unittest } /// Multi-diagonal matrix -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.slice; @@ -1205,7 +1340,7 @@ version(mir_test) unittest } /// Sliding window over matrix columns -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; import mir.ndslice.slice; @@ -1228,7 +1363,7 @@ version(mir_test) unittest } /// Overlapping blocks using windows -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { // ---------------- // | 0 1 2 3 4 | @@ -1263,7 +1398,7 @@ version(mir_test) unittest [[12, 13, 14], [17, 18, 19], [22, 23, 24]]]]); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto w = iota(9, 9).windows(3, 3); assert(w.front == w[0]); @@ -1385,7 +1520,7 @@ Slice!(Iterator, M, kind) reshape /// @safe nothrow pure -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.dynamic : allReversed; int err; @@ -1402,7 +1537,7 @@ version(mir_test) unittest } /// Reshaping with memory allocation -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.ndslice.slice: sliced; import mir.ndslice.allocation: slice; @@ -1433,7 +1568,7 @@ version(mir_test) unittest [ 1, 2, 3]]); } -nothrow @safe pure version(mir_test) unittest +nothrow @safe pure version(mir_ndslice_test) unittest { import mir.ndslice.dynamic : allReversed; auto slice = iota(1, 1, 3, 2, 1, 2, 1).universal.allReversed; @@ -1448,14 +1583,14 @@ nothrow @safe pure version(mir_test) unittest // Issue 15919 nothrow @nogc @safe pure -version(mir_test) unittest +version(mir_ndslice_test) unittest { int err; assert(iota(3, 4, 5, 6, 7).pack!2.reshape([4, 3, 5], err)[0, 0, 0].shape == cast(size_t[2])[6, 7]); assert(err == 0); } -nothrow @nogc @safe pure version(mir_test) unittest +nothrow @nogc @safe pure version(mir_ndslice_test) unittest { import mir.ndslice.slice; @@ -1471,7 +1606,7 @@ nothrow @nogc @safe pure version(mir_test) unittest } nothrow @nogc @safe pure -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto pElements = iota(3, 4, 5, 6, 7) .pack!2 @@ -1498,10 +1633,11 @@ Slice!(FlattenedIterator!(Iterator, N, kind)) (Slice!(Iterator, N, kind) slice) if (N != 1 && kind != Contiguous) { + import core.lifetime: move; size_t[typeof(return).N] lengths; - sizediff_t[typeof(return)._iterator._indexes.length] indexes; + sizediff_t[typeof(return)._iterator._indices.length] indices; lengths[0] = slice.elementCount; - return typeof(return)(lengths, FlattenedIterator!(Iterator, N, kind)(indexes, slice)); + return typeof(return)(lengths, FlattenedIterator!(Iterator, N, kind)(indices, slice.move)); } /// ditto @@ -1516,9 +1652,10 @@ Slice!Iterator } else { + import core.lifetime: move; size_t[typeof(return).N] lengths; lengths[0] = slice.elementCount; - return typeof(return)(lengths, slice._iterator); + return typeof(return)(lengths, slice._iterator.move); } } @@ -1528,10 +1665,11 @@ Slice!(StrideIterator!Iterator) (Iterator) (Slice!(Iterator, 1, Universal) slice) { - return slice.hideStride; + import core.lifetime: move; + return slice.move.hideStride; } -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; auto sl1 = iota(2, 3).slice.universal.pack!1.flattened; @@ -1540,14 +1678,14 @@ version(mir_test) unittest } /// Regular slice -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { assert(iota(4, 5).flattened == iota(20)); assert(iota(4, 5).canonical.flattened == iota(20)); assert(iota(4, 5).universal.flattened == iota(20)); } -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { assert(iota(4).flattened == iota(4)); assert(iota(4).canonical.flattened == iota(4)); @@ -1555,7 +1693,7 @@ version(mir_test) unittest } /// Packed slice -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.dynamic; @@ -1563,14 +1701,14 @@ version(mir_test) unittest } /// Properties -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { auto elems = iota(3, 4).universal.flattened; elems.popFrontExactly(2); assert(elems.front == 2); /// `_index` is available only for canonical and universal ndslices. - assert(elems._iterator._indexes == [0, 2]); + assert(elems._iterator._indices == [0, 2]); elems.popBackExactly(2); assert(elems.back == 9); @@ -1578,14 +1716,14 @@ version(mir_test) unittest } /// Index property -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; auto slice = new long[20].sliced(5, 4); for (auto elems = slice.universal.flattened; !elems.empty; elems.popFront) { - ptrdiff_t[2] index = elems._iterator._indexes; + ptrdiff_t[2] index = elems._iterator._indices; elems.front = index[0] * 10 + index[1] * 3; } assert(slice == @@ -1596,7 +1734,7 @@ version(mir_test) unittest [40, 43, 46, 49]]); } -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { auto elems = iota(3, 4).universal.flattened; assert(elems.front == 0); @@ -1606,10 +1744,10 @@ version(mir_test) unittest /++ Random access and slicing +/ -nothrow version(mir_test) unittest +nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; - import mir.ndslice.slice : sliced; + import mir.ndslice.slice: sliced; auto elems = iota(4, 5).slice.flattened; @@ -1638,7 +1776,7 @@ nothrow version(mir_test) unittest assert(elems[2 .. 6] == sl); } -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.dynamic : allReversed; @@ -1660,7 +1798,7 @@ nothrow version(mir_test) unittest } } -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import std.range.primitives : isRandomAccessRange, hasSlicing; auto elems = iota(4, 5).flattened; @@ -1669,7 +1807,7 @@ nothrow version(mir_test) unittest } // Checks strides -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.dynamic; import std.range.primitives : isRandomAccessRange; @@ -1685,7 +1823,7 @@ nothrow version(mir_test) unittest } } -@safe @nogc pure nothrow version(mir_test) unittest +@safe @nogc pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.slice; import mir.ndslice.dynamic; @@ -1726,7 +1864,7 @@ nothrow version(mir_test) unittest } // Issue 15549 -version(mir_test) unittest +version(mir_ndslice_test) unittest { import std.range.primitives; import mir.ndslice.allocation; @@ -1741,7 +1879,7 @@ version(mir_test) unittest } // Issue 16010 -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto s = iota(3, 4).flattened; foreach (_; 0 .. s.length) @@ -1756,7 +1894,7 @@ Params: N = dimension count lengths = list of dimension lengths Returns: - `N`-dimensional slice composed of indexes + `N`-dimensional slice composed of indices See_also: $(LREF iota) +/ Slice!(FieldIterator!(ndIotaField!N), N) @@ -1769,7 +1907,7 @@ Slice!(FieldIterator!(ndIotaField!N), N) } /// -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { auto slice = ndiota(2, 3); static immutable array = @@ -1780,7 +1918,7 @@ Slice!(FieldIterator!(ndIotaField!N), N) } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { auto im = ndiota(7, 9); @@ -1791,7 +1929,7 @@ Slice!(FieldIterator!(ndIotaField!N), N) assert(cm[2, 1] == [3, 5]); } -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto r = ndiota(1); auto d = r.front; @@ -1827,10 +1965,9 @@ auto linspace(T, size_t N)(size_t[N] lengths, T[2][N] intervals...) } // example from readme -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice; - // import std.stdio: writefln; enum fmt = "%(%(%.2f %)\n%)\n"; @@ -1846,7 +1983,7 @@ version(mir_test) unittest /// 1D @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto s = linspace!double([5], [1.0, 2.0]); assert(s == [1.0, 1.25, 1.5, 1.75, 2.0]); @@ -1861,18 +1998,18 @@ version(mir_test) unittest /// 2D @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { - import mir.functional: refTuple; + import mir.functional: tuple; auto s = linspace!double([5, 3], [1.0, 2.0], [0.0, 1.0]); assert(s == [ - [refTuple(1.00, 0.00), refTuple(1.00, 0.5), refTuple(1.00, 1.0)], - [refTuple(1.25, 0.00), refTuple(1.25, 0.5), refTuple(1.25, 1.0)], - [refTuple(1.50, 0.00), refTuple(1.50, 0.5), refTuple(1.50, 1.0)], - [refTuple(1.75, 0.00), refTuple(1.75, 0.5), refTuple(1.75, 1.0)], - [refTuple(2.00, 0.00), refTuple(2.00, 0.5), refTuple(2.00, 1.0)], + [tuple(1.00, 0.00), tuple(1.00, 0.5), tuple(1.00, 1.0)], + [tuple(1.25, 0.00), tuple(1.25, 0.5), tuple(1.25, 1.0)], + [tuple(1.50, 0.00), tuple(1.50, 0.5), tuple(1.50, 1.0)], + [tuple(1.75, 0.00), tuple(1.75, 0.5), tuple(1.75, 1.0)], + [tuple(2.00, 0.00), tuple(2.00, 0.5), tuple(2.00, 1.0)], ]); assert(s.map!"a * b" == [ @@ -1886,10 +2023,12 @@ version(mir_test) unittest /// Complex numbers @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { - auto s = linspace!cdouble([3], [1.0 + 0i, 2.0 + 4i]); - assert(s == [1.0 + 0i, 1.5 + 2i, 2.0 + 4i]); + import mir.complex; + alias C = Complex!double; + auto s = linspace!C([3], [C(1.0, 0), C(2.0, 4)]); + assert(s == [C(1.0, 0), C(1.5, 2), C(2.0, 4)]); } /++ @@ -1918,18 +2057,19 @@ Slice!(SliceIterator!(Iterator, N, kind), M, Universal) (Slice!(Iterator, N, kind) slice, size_t[M] lengths...) if (M) { + import core.lifetime: move; size_t[M] ls = lengths; return typeof(return)( ls, sizediff_t[M].init, typeof(return).Iterator( slice._structure, - slice._iterator)); + move(slice._iterator))); } /// @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto sl = iota(3).repeat(4); assert(sl == [[0, 1, 2], @@ -1939,7 +2079,7 @@ version(mir_test) unittest } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.dynamic : transposed; @@ -1955,7 +2095,7 @@ version(mir_test) unittest } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation; @@ -1971,8 +2111,10 @@ version(mir_test) unittest } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { + import mir.primitives: DeepElementType; + auto sl = repeat(4.0, 2, 3); assert(sl == [[4.0, 4.0, 4.0], [4.0, 4.0, 4.0]]); @@ -2034,7 +2176,6 @@ auto cycle(size_t loopLength, T)(T[] array, size_t length) return cycle!loopLength(array.sliced, length); } - /// ditto auto cycle(size_t loopLength, T)(T withAsSlice, size_t length) if (hasAsSlice!T) @@ -2043,7 +2184,7 @@ auto cycle(size_t loopLength, T)(T withAsSlice, size_t length) } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { auto slice = iota(3); assert(slice.cycle(7) == [0, 1, 2, 0, 1, 2, 0]); @@ -2069,7 +2210,7 @@ in { assert (factor > 0, "factor must be positive."); } -body +do { static if (kind == Contiguous) return slice.universal.stride(factor); @@ -2080,6 +2221,48 @@ body } } +///ditto +template stride(size_t factor = 2) + if (factor > 1) +{ + auto stride + (Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice) + { + import core.lifetime: move; + static if (N > 1) + { + return stride(slice.move.ipack!1.map!(.stride!factor)); + } + else + static if (kind == Contiguous) + { + immutable rem = slice._lengths[0] % factor; + slice._lengths[0] /= factor; + if (rem) + slice._lengths[0]++; + return Slice!(StrideIterator!(Iterator, factor), 1, kind)(slice._structure, StrideIterator!(Iterator, factor)(move(slice._iterator))); + } + else + { + return .stride(slice.move, factor); + } + } + + /// ditto + auto stride(T)(T[] array) + { + return stride(array.sliced); + } + + /// ditto + auto stride(T)(T withAsSlice) + if (hasAsSlice!T) + { + return stride(withAsSlice.asSlice); + } +} + /// ditto auto stride(T)(T[] array, ptrdiff_t factor) { @@ -2094,20 +2277,31 @@ auto stride(T)(T withAsSlice, ptrdiff_t factor) } /// -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { auto slice = iota(6); static immutable str = [0, 2, 4]; - assert(slice.stride(2) == str); + assert(slice.stride(2) == str); // runtime factor + assert(slice.stride!2 == str); // compile time factor + assert(slice.stride == str); // default compile time factor is 2 assert(slice.universal.stride(2) == str); } +/// ND-compile time +@safe pure nothrow @nogc version(mir_ndslice_test) unittest +{ + auto slice = iota(4, 6); + static immutable str = [[0, 2, 4], [12, 14, 16]]; + assert(slice.stride!2 == str); // compile time factor + assert(slice.stride == str); // default compile time factor is 2 +} + /++ Reverses order of iteration for all dimensions. Params: - slice = Unpacked slice. + slice = slice, range, or array. Returns: - Slice with reversed order of iteration for all dimensions. + Slice/range with reversed order of iteration for all dimensions. See_also: $(SUBREF dynamic, reversed), $(SUBREF dynamic, allReversed). +/ auto retro @@ -2115,6 +2309,7 @@ auto retro (Slice!(Iterator, N, kind) slice) @trusted { + import core.lifetime: move; static if (kind == Contiguous || kind == Canonical) { size_t[slice.N] lengths; @@ -2134,18 +2329,20 @@ auto retro static if (is(Iterator : RetroIterator!It, It)) { alias Ret = Slice!(It, N, kind); - return Ret(structure, slice._iterator._iterator - slice.lastIndex); + slice._iterator._iterator -= slice.lastIndex; + return Ret(structure, slice._iterator._iterator.move); } else { alias Ret = Slice!(RetroIterator!Iterator, N, kind); - return Ret(structure, RetroIterator!Iterator(slice._iterator + slice.lastIndex)); + slice._iterator += slice.lastIndex; + return Ret(structure, RetroIterator!Iterator(slice._iterator.move)); } } else { import mir.ndslice.dynamic: allReversed; - return slice.allReversed; + return slice.move.allReversed; } } @@ -2162,8 +2359,81 @@ auto retro(T)(T withAsSlice) return retro(withAsSlice.asSlice); } +/// ditto +auto retro(Range)(Range r) + if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) +{ + import std.traits: Unqual; + + static if (is(Unqual!Range == Range)) + { + import core.lifetime: move; + static if (is(Range : RetroRange!R, R)) + { + return move(r._source); + } + else + { + return RetroRange!Range(move(r)); + } + } + else + { + return .retro!(Unqual!Range)(r); + } +} + +/// ditto +struct RetroRange(Range) +{ + import mir.primitives: hasLength; + + /// + Range _source; + + private enum hasAccessByRef = __traits(compiles, &_source.front); + + @property + { + bool empty()() const { return _source.empty; } + static if (hasLength!Range) + auto length()() const { return _source.length; } + auto ref front()() { return _source.back; } + auto ref back()() { return _source.front; } + static if (__traits(hasMember, Range, "save")) + auto save()() { return RetroRange(_source.save); } + alias opDollar = length; + + static if (!hasAccessByRef) + { + import std.traits: ForeachType; + + void front()(ForeachType!R val) + { + import mir.functional: forward; + _source.back = forward!val; + } + + void back()(ForeachType!R val) + { + import mir.functional: forward; + _source.front = forward!val; + } + } + } + + void popFront()() { _source.popBack(); } + void popBack()() { _source.popFront(); } + + static if (is(typeof(_source.moveBack()))) + auto moveFront()() { return _source.moveBack(); } + + static if (is(typeof(_source.moveFront()))) + auto moveBack()() { return _source.moveFront(); } +} + /// -@safe pure nothrow @nogc version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { auto slice = iota(2, 3); static immutable reversed = [[5, 4, 3], [2, 1, 0]]; @@ -2176,6 +2446,16 @@ auto retro(T)(T withAsSlice) static assert(is(typeof(slice.universal.retro) == typeof(slice.universal))); } +/// Ranges +@safe pure nothrow @nogc version(mir_ndslice_test) unittest +{ + import mir.algorithm.iteration: equal; + import std.range: std_iota = iota; + + assert(std_iota(4).retro.equal(iota(4).retro)); + static assert(is(typeof(std_iota(4).retro.retro) == typeof(std_iota(4)))); +} + /++ Bitwise slice over an integral slice. Params: @@ -2185,29 +2465,37 @@ Returns: A bitwise slice. auto bitwise (Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init])) (Slice!(Iterator, N, kind) slice) - if (__traits(isIntegral, I) && (kind == Contiguous || kind == Canonical)) + if (__traits(isIntegral, I) && (kind != Universal || N == 1)) { - static if (is(Iterator : FieldIterator!Field, Field)) + import core.lifetime: move; + static if (kind == Universal) { - enum simplified = true; - alias It = FieldIterator!(BitField!Field); + return slice.move.flattened.bitwise; } else { - enum simplified = false; - alias It = FieldIterator!(BitField!Iterator); + static if (is(Iterator : FieldIterator!Field, Field)) + { + enum simplified = true; + alias It = FieldIterator!(BitField!Field); + } + else + { + enum simplified = false; + alias It = FieldIterator!(BitField!Iterator); + } + alias Ret = Slice!(It, N, kind); + auto structure_ = Ret._Structure.init; + foreach(i; Iota!(Ret.N)) + structure_[0][i] = slice._lengths[i]; + structure_[0][$ - 1] *= I.sizeof * 8; + foreach(i; Iota!(Ret.S)) + structure_[1][i] = slice._strides[i]; + static if (simplified) + return Ret(structure_, It(slice._iterator._index * I.sizeof * 8, BitField!Field(slice._iterator._field.move))); + else + return Ret(structure_, It(0, BitField!Iterator(slice._iterator.move))); } - alias Ret = Slice!(It, N, kind); - auto structure_ = Ret._Structure.init; - foreach(i; Iota!(Ret.N)) - structure_[0][i] = slice._lengths[i]; - structure_[0][$ - 1] *= I.sizeof * 8; - foreach(i; Iota!(Ret.S)) - structure_[1][i] = slice._strides[i]; - static if (simplified) - return Ret(structure_, It(slice._iterator._index * I.sizeof * 8, BitField!Field(slice._iterator._field))); - else - return Ret(structure_, It(0, BitField!Iterator(slice._iterator))); } /// ditto @@ -2225,7 +2513,7 @@ auto bitwise(T)(T withAsSlice) /// @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { size_t[10] data; auto bits = data[].bitwise; @@ -2242,7 +2530,7 @@ version(mir_test) unittest } @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { size_t[10] data; auto slice = FieldIterator!(size_t[])(0, data[]).sliced(10); @@ -2253,6 +2541,12 @@ version(mir_test) unittest bits[111] = true; assert(bits[111]); assert(bits_normal[111 + size_t.sizeof * 2 * 8]); + auto ubits = slice.universal.bitwise; + assert(bits.map!"~a" == bits.map!"!a"); + static assert (is(typeof(bits.map!"~a") == typeof(bits.map!"!a"))); + assert(bits.map!"~a" == bits.map!"!!!a"); + static assert (!is(typeof(bits.map!"~a") == typeof(bits.map!"!!!a"))); + assert(bits == ubits); bits.popFront; assert(bits[110]); @@ -2271,7 +2565,8 @@ Returns: A bitwise field. auto bitwiseField(Field, I = typeof(Field.init[size_t.init]))(Field field) if (__traits(isUnsigned, I)) { - return BitField!(Field, I)(field); + import core.lifetime: move; + return BitField!(Field, I)(field.move); } /++ @@ -2289,6 +2584,7 @@ auto bitpack (Slice!(Iterator, N, kind) slice) if (__traits(isIntegral, I) && (kind == Contiguous || kind == Canonical) && pack > 1) { + import core.lifetime: move; static if (is(Iterator : FieldIterator!Field, Field) && I.sizeof * 8 % pack == 0) { enum simplified = true; @@ -2308,9 +2604,9 @@ auto bitpack foreach(i; Iota!(Ret.S)) structure[1][i] = slice._strides[i]; static if (simplified) - return Ret(structure, It(slice._iterator._index * I.sizeof * 8 / pack, BitpackField!(Field, pack)(slice._iterator._field))); + return Ret(structure, It(slice._iterator._index * I.sizeof * 8 / pack, BitpackField!(Field, pack)(slice._iterator._field.move))); else - return Ret(structure, It(0, BitpackField!(Iterator, pack)(slice._iterator))); + return Ret(structure, It(0, BitpackField!(Iterator, pack)(slice._iterator.move))); } /// ditto @@ -2328,7 +2624,7 @@ auto bitpack(size_t pack, T)(T withAsSlice) /// @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { size_t[10] data; // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. @@ -2360,12 +2656,12 @@ bytegroup (Slice!(Iterator, N, kind) slice) if ((kind == Contiguous || kind == Canonical) && group) { + import core.lifetime: move; auto structure = slice._structure; structure[0][$ - 1] /= group; - return typeof(return)(structure, BytegroupIterator!(Iterator, group, DestinationType)(slice._iterator)); + return typeof(return)(structure, BytegroupIterator!(Iterator, group, DestinationType)(slice._iterator.move)); } - /// ditto auto bytegroup(size_t pack, DestinationType, T)(T[] array) { @@ -2381,9 +2677,9 @@ auto bytegroup(size_t pack, DestinationType, T)(T withAsSlice) /// 24 bit integers @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { - import mir.ndslice.slice : DeepElementType, sliced; + import mir.ndslice.slice: DeepElementType, sliced; ubyte[20] data; // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. @@ -2403,9 +2699,9 @@ version(mir_test) unittest /// 48 bit integers @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { - import mir.ndslice.slice : DeepElementType, sliced; + import mir.ndslice.slice: DeepElementType, sliced; ushort[20] data; // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. auto int48ar = data[].sliced.bytegroup!(3, long); // 48 bit integers @@ -2435,7 +2731,7 @@ Note: Params: fun = One or more functions. See_Also: - $(LREF cached), $(LREF vmap), $(LREF indexed), + $(LREF cached), $(LREF vmap), $(LREF rcmap), $(LREF indexed), $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) +/ @@ -2445,24 +2741,26 @@ template map(fun...) import mir.functional: adjoin, naryFun, pipe; static if (fun.length == 1) { - static if (__traits(isSame, naryFun!(fun[0]), fun[0]) && !__traits(isSame, naryFun!"a", fun[0])) + static if (__traits(isSame, naryFun!(fun[0]), fun[0])) { alias f = fun[0]; - @optmath: + @fmamath: /++ Params: - slice = An input slice. + slice = An ndslice, array, or an input range. Returns: - a slice with each fun applied to all the elements. If there is more than one + ndslice or an input range with each fun applied to all the elements. If there is more than one fun, the element type will be `Tuple` containing one element for each fun. +/ auto map(Iterator, size_t N, SliceKind kind) (Slice!(Iterator, N, kind) slice) { - alias Iterator = typeof(_mapIterator!f(slice._iterator)); + import core.lifetime: move; + alias MIterator = typeof(_mapIterator!f(slice._iterator)); import mir.ndslice.traits: isIterator; - static assert(isIterator!Iterator, "mir.ndslice.map: probably the lambda function contains a compile time bug."); - return Slice!(Iterator, N, kind)(slice._structure, _mapIterator!f(slice._iterator)); + alias testIter = typeof(MIterator.init[0]); + static assert(isIterator!MIterator, "mir.ndslice.map: probably the lambda function contains a compile time bug."); + return Slice!(MIterator, N, kind)(slice._structure, _mapIterator!f(slice._iterator.move)); } /// ditto @@ -2477,56 +2775,109 @@ template map(fun...) { return map(withAsSlice.asSlice); } - } - else - static if (__traits(isSame, naryFun!"a", fun[0])) - { - /// - @optmath auto map(Iterator, size_t N, SliceKind kind) - (Slice!(Iterator, N, kind) slice) - { - return slice; - } - - /// ditto - auto map(T)(T[] array) - { - return array.sliced; - } /// ditto - auto map(T)(T withAsSlice) - if (hasAsSlice!T) + auto map(Range)(Range r) + if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) { - return withAsSlice.asSlice; + import core.lifetime: forward; + import mir.primitives: isInputRange; + static assert (isInputRange!Range, "map can work with ndslice, array, or an input range."); + return MapRange!(f, ImplicitlyUnqual!Range)(forward!r); } } - else alias map = .map!(naryFun!fun); + else alias map = .map!(staticMap!(naryFun, fun)); } else alias map = .map!(adjoin!fun); } -/// -@safe pure nothrow -version(mir_test) unittest +/// ditto +struct MapRange(alias fun, Range) { - import mir.ndslice.topology : iota; - auto s = iota(2, 3).map!(a => a * 3); - assert(s == [[ 0, 3, 6], - [ 9, 12, 15]]); -} + import std.range.primitives; -/// String lambdas -@safe pure nothrow -version(mir_test) unittest -{ - import mir.ndslice.topology : iota; - assert(iota(2, 3).map!"a * 2" == [[0, 2, 4], [6, 8, 10]]); -} + Range _input; -/// Packed tensors. + static if (isInfinite!Range) + { + enum bool empty = false; + } + else + { + bool empty() @property + { + return _input.empty; + } + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty map."); + _input.popFront(); + } + + auto ref front() @property + { + assert(!empty, "Attempting to fetch the front of an empty map."); + return fun(_input.front); + } + + static if (isBidirectionalRange!Range) + auto ref back()() @property + { + assert(!empty, "Attempting to fetch the back of an empty map."); + return fun(_input.back); + } + + static if (isBidirectionalRange!Range) + void popBack()() + { + assert(!empty, "Attempting to popBack an empty map."); + _input.popBack(); + } + + static if (hasLength!Range) + auto length() @property + { + return _input.length; + } + + static if (isForwardRange!Range) + auto save()() @property + { + return typeof(this)(_input.save); + } +} + +/// +@safe pure nothrow +version(mir_ndslice_test) unittest +{ + import mir.ndslice.topology : iota; + auto s = iota(2, 3).map!(a => a * 3); + assert(s == [[ 0, 3, 6], + [ 9, 12, 15]]); +} + +/// String lambdas +@safe pure nothrow +version(mir_ndslice_test) unittest +{ + import mir.ndslice.topology : iota; + assert(iota(2, 3).map!"a * 2" == [[0, 2, 4], [6, 8, 10]]); +} + +/// Input ranges +@safe pure nothrow +version(mir_ndslice_test) unittest +{ + import mir.algorithm.iteration: filter, equal; + assert (6.iota.filter!"a % 2".map!"a * 10".equal([10, 30, 50])); +} + +/// Packed tensors @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota, windows; import mir.math.sum: sum; @@ -2547,7 +2898,7 @@ version(mir_test) unittest /// Zipped tensors @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota, zip; @@ -2561,15 +2912,16 @@ version(mir_test) unittest auto z = zip(sl1, sl2); assert(zip(sl1, sl2).map!"a + b" == sl1 + sl2); + assert(zip(sl1, sl2).map!((a, b) => a + b) == sl1 + sl2); } /++ Multiple functions can be passed to `map`. -In that case, the element type of `map` is a refTuple containing +In that case, the element type of `map` is a tuple containing one element for each function. +/ @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; @@ -2586,7 +2938,7 @@ version(mir_test) unittest /++ `map` can be aliased to a symbol and be used separately: +/ -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; @@ -2597,7 +2949,7 @@ pure nothrow version(mir_test) unittest /++ Type normalization +/ -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.functional : pipe; import mir.ndslice.topology : iota; @@ -2607,24 +2959,74 @@ version(mir_test) unittest static assert(is(typeof(a) == typeof(b))); } +/// Use map with byDim/alongDim to apply functions to each dimension +version(mir_ndslice_test) +@safe pure +unittest +{ + import mir.ndslice.topology: byDim, alongDim; + import mir.ndslice.fuse: fuse; + import mir.math.stat: mean; + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + + // Use byDim/alongDim with map to compute mean of row/column. + assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); + assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); + assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); + assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); +} + +/++ +Use map with a lambda and with byDim/alongDim, but may need to allocate result. +This example uses fuse, which allocates. Note: fuse!1 will transpose the result. ++/ +version(mir_ndslice_test) +@safe pure +unittest { + import mir.ndslice.topology: iota, byDim, alongDim, map; + import mir.ndslice.fuse: fuse; + import mir.ndslice.slice: sliced; + + auto x = [1, 2, 3].sliced; + auto y = [1, 2].sliced; + + auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; + assert(s1 == [[ 0, 2, 6], + [ 3, 8, 15]]); + auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1; + assert(s2 == [[ 0, 1, 2], + [ 6, 8, 10]]); + auto s3 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; + assert(s1 == [[ 0, 2, 6], + [ 3, 8, 15]]); + auto s4 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1; + assert(s2 == [[ 0, 1, 2], + [ 6, 8, 10]]); +} + /// -pure version(mir_test) unittest +pure version(mir_ndslice_test) unittest { - import std.algorithm.iteration : sum, reduce; - import mir.utility : max; - import mir.ndslice.dynamic : transposed; + import mir.algorithm.iteration: reduce; + import mir.math.common: fmax; + import mir.math.stat: mean; + import mir.math.sum; /// Returns maximal column average. auto maxAvg(S)(S matrix) { - return reduce!max(matrix.universal.transposed.pack!1.map!sum) - / double(matrix.length); + return reduce!fmax(0.0, matrix.alongDim!1.map!mean); } // 1 2 // 3 4 auto matrix = iota([2, 2], 1); - assert(maxAvg(matrix) == 3); + assert(maxAvg(matrix) == 3.5); } - /++ Implements the homonym function (also known as `transform`) present in many languages of functional flavor. The call `slice.vmap(fun)` @@ -2639,36 +3041,39 @@ Params: slice = ndslice callable = callable object, structure, delegate, or function pointer. See_Also: - $(LREF cached), $(LREF map), $(LREF indexed), + $(LREF cached), $(LREF map), $(LREF rcmap), $(LREF indexed), $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) +/ -@optmath auto vmap(Iterator, size_t N, SliceKind kind, Callable) +@fmamath auto vmap(Iterator, size_t N, SliceKind kind, Callable) ( - scope return Slice!(Iterator, N, kind) slice, - scope return Callable callable, + Slice!(Iterator, N, kind) slice, + Callable callable, ) { + import core.lifetime: move; alias It = VmapIterator!(Iterator, Callable); - return Slice!(It, N, kind)(slice._structure, It(slice._iterator, callable)); + return Slice!(It, N, kind)(slice._structure, It(slice._iterator.move, callable.move)); } /// ditto -auto vmap(T, Callable)(scope return T[] array, scope return Callable callable) +auto vmap(T, Callable)(T[] array, Callable callable) { - return vmap(array.sliced, callable); + import core.lifetime: move; + return vmap(array.sliced, callable.move); } /// ditto -auto vmap(T, Callable)(scope return T withAsSlice, scope return Callable callable) +auto vmap(T, Callable)(T withAsSlice, Callable callable) if (hasAsSlice!T) { - return vmap(withAsSlice.asSlice, callable); + import core.lifetime: move; + return vmap(withAsSlice.asSlice, callable.move); } /// @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; @@ -2687,7 +3092,7 @@ version(mir_test) unittest /// Packed tensors. @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.math.sum: sum; import mir.ndslice.topology : iota, windows; @@ -2721,7 +3126,7 @@ version(mir_test) unittest /// Zipped tensors @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota, zip; @@ -2754,11 +3159,11 @@ version(mir_test) unittest // TODO /+ Multiple functions can be passed to `vmap`. -In that case, the element type of `vmap` is a refTuple containing +In that case, the element type of `vmap` is a tuple containing one element for each function. +/ @safe pure nothrow -version(none) version(mir_test) unittest +version(none) version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; @@ -2776,14 +3181,90 @@ version(none) version(mir_test) unittest } } +/// Use vmap with byDim/alongDim to apply functions to each dimension +version(mir_ndslice_test) +@safe pure +unittest +{ + import mir.ndslice.fuse: fuse; + import mir.math.stat: mean; + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + + static struct Callable + { + double factor; + this(double f) {factor = f;} + auto opCall(U)(U x) const {return x.mean + factor; } + auto lightConst()() const @property { return Callable(factor); } + } + + auto callable = Callable(0.0); + + // Use byDim/alongDim with map to compute callable of row/column. + assert(x.byDim!0.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6])); + assert(x.byDim!1.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); + assert(x.alongDim!1.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6])); + assert(x.alongDim!0.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); +} + +/++ +Use vmap with a lambda and with byDim/alongDim, but may need to allocate result. +This example uses fuse, which allocates. Note: fuse!1 will transpose the result. ++/ +version(mir_ndslice_test) +@safe pure +unittest { + import mir.ndslice.topology: iota, alongDim, map; + import mir.ndslice.fuse: fuse; + import mir.ndslice.slice: sliced; + + static struct Mul(T) + { + T factor; + this(T f) { factor = f; } + auto opCall(U)(U x) {return x * factor; } + auto lightConst()() const @property { return Mul!(typeof(factor.lightConst))(factor.lightConst); } + } + + auto a = [1, 2, 3].sliced; + auto b = [1, 2].sliced; + auto A = Mul!(typeof(a))(a); + auto B = Mul!(typeof(b))(b); + + auto x = [ + [0, 1, 2], + [3, 4, 5] + ].fuse; + + auto s1 = x.byDim!0.vmap(A).fuse; + assert(s1 == [[ 0, 2, 6], + [ 3, 8, 15]]); + auto s2 = x.byDim!1.vmap(B).fuse!1; + assert(s2 == [[ 0, 1, 2], + [ 6, 8, 10]]); + auto s3 = x.alongDim!1.vmap(A).fuse; + assert(s1 == [[ 0, 2, 6], + [ 3, 8, 15]]); + auto s4 = x.alongDim!0.vmap(B).fuse!1; + assert(s2 == [[ 0, 1, 2], + [ 6, 8, 10]]); +} + private auto hideStride (Iterator, SliceKind kind) (Slice!(Iterator, 1, kind) slice) { + import core.lifetime: move; static if (kind == Universal) return Slice!(StrideIterator!Iterator)( slice._lengths, - StrideIterator!Iterator(slice._strides[0], slice._iterator)); + StrideIterator!Iterator(slice._strides[0], move(slice._iterator))); else return slice; } @@ -2794,21 +3275,176 @@ private auto unhideStride { static if (is(Iterator : StrideIterator!It, It)) { + import core.lifetime: move; static if (kind == Universal) { alias Ret = SliceKind!(It, N, Universal); auto strides = slice._strides; foreach(i; Iota!(Ret.S)) strides[i] = slice._strides[i] * slice._iterator._stride; - return Slice!(It, N, Universal)(slice._lengths, strides, slice._iterator._iterator); + return Slice!(It, N, Universal)(slice._lengths, strides, slice._iterator._iterator.move); } else - return slice.universal.unhideStride; + return slice.move.universal.unhideStride; } else return slice; } +/++ +Implements the homonym function (also known as `transform`) present +in many languages of functional flavor. The call `rmap!(fun)(slice)` +returns an RC array (1D) or RC slice (ND) of which elements are obtained by applying `fun` +for all elements in `slice`. The original slices are +not changed. Evaluation is done eagerly. + +Note: + $(SUBREF dynamic, transposed) and + $(SUBREF topology, pack) can be used to specify dimensions. +Params: + fun = One or more functions. +See_Also: + $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed), + $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), + $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) ++/ +template rcmap(fun...) + if (fun.length) +{ + import mir.functional: adjoin, naryFun, pipe; + static if (fun.length == 1) + { + static if (__traits(isSame, naryFun!(fun[0]), fun[0])) + { + alias f = fun[0]; + @fmamath: + /++ + Params: + slice = An ndslice, array, or an input range. + Returns: + ndslice or an input range with each fun applied to all the elements. If there is more than one + fun, the element type will be `Tuple` containing one element for each fun. + +/ + auto rcmap(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice) + { + import core.lifetime: move; + auto shape = slice.shape; + auto r = slice.move.flattened; + if (false) + { + auto e = f(r.front); + r.popFront; + auto d = r.empty; + } + return () @trusted + { + import mir.rc.array: RCArray; + import std.traits: Unqual; + import mir.conv: emplaceRef; + + alias T = typeof(f(r.front)); + auto ret = RCArray!T(r.length); + auto next = ret.ptr; + while (!r.empty) + { + emplaceRef(*cast(Unqual!T*)next++, f(r.front)); + r.popFront; + } + static if (N == 1) + { + return ret; + } + else + { + return ret.moveToSlice.sliced(shape); + } + } (); + } + + /// ditto + auto rcmap(T)(T[] array) + { + return rcmap(array.sliced); + } + + /// ditto + auto rcmap(T)(T withAsSlice) + if (hasAsSlice!T) + { + static if (__traits(hasMember, T, "moveToSlice")) + return rcmap(withAsSlice.moveToSlice); + else + return rcmap(withAsSlice.asSlice); + } + + /// ditto + auto rcmap(Range)(Range r) + if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) + { + import core.lifetime: forward; + import mir.appender: scopedBuffer; + import mir.primitives: isInputRange; + import mir.rc.array: RCArray; + alias T = typeof(f(r.front)); + auto buffer = scopedBuffer!T; + while (!r.empty) + { + buffer.put(f(r.front)); + r.popFront; + } + return () @trusted + { + auto ret = RCArray!T(buffer.length, false); + buffer.moveDataAndEmplaceTo(ret[]); + return ret; + } (); + } + } + else alias rcmap = .rcmap!(staticMap!(naryFun, fun)); + } + else alias rcmap = .rcmap!(adjoin!fun); +} + +/// Returns RCArray for input ranges and one-dimensional slices. +@safe pure nothrow @nogc +version(mir_ndslice_test) unittest +{ + import mir.algorithm.iteration: filter, equal; + auto factor = 10; + auto step = 20; + assert (3.iota.rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); + assert (6.iota.filter!"a % 2".rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); +} + +/// For multidimensional case returns `Slice!(RCI!T, N)`. +@safe pure nothrow @nogc +version(mir_ndslice_test) unittest +{ + import mir.ndslice.topology : iota; + auto factor = 3; + auto s = iota(2, 3).rcmap!(a => a * factor); + assert(s == iota(2, 3) * factor); +} + +/// String lambdas +@safe pure nothrow +version(mir_ndslice_test) unittest +{ + import mir.ndslice.topology : iota; + assert(iota(2, 3).rcmap!"a * 2" == iota(2, 3) * 2); +} + +@safe pure nothrow @nogc +version(mir_ndslice_test) unittest +{ + import mir.algorithm.iteration: filter, equal; + auto factor = 10; + auto step = 20; + assert (3.iota.as!(const int).rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); + assert (6.iota.filter!"a % 2".as!(immutable int).rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); +} + /++ Creates a random access cache for lazyly computed elements. Params: @@ -2839,7 +3475,7 @@ Slice!(CachedIterator!(Iterator, CacheIterator, FlagIterator), N, kind) /// @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: cached, iota, map; import mir.ndslice.allocation: bitSlice, uninitSlice; @@ -2876,7 +3512,7 @@ version(mir_test) unittest /// Cache of immutable elements @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.slice: DeepElementType; import mir.ndslice.topology: cached, iota, map, as; @@ -2951,7 +3587,7 @@ auto cachedGC(T)(T withAsSlice) /// @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: cachedGC, iota, map; @@ -2985,7 +3621,7 @@ version(mir_test) unittest /// Cache of immutable elements @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.slice: DeepElementType; import mir.ndslice.topology: cachedGC, iota, map, as; @@ -3027,7 +3663,7 @@ See_also: $(LREF map), $(LREF vmap) template as(T) { /// - @optmath auto as(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) + @fmamath auto as(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) { static if (is(slice.DeepElement == T)) return slice; @@ -3036,8 +3672,9 @@ template as(T) return slice.toConst; else { + import core.lifetime: move; import mir.conv: to; - return map!(to!T)(slice); + return map!(to!T)(slice.move); } } @@ -3053,11 +3690,26 @@ template as(T) { return as(withAsSlice.asSlice); } + + /// ditto + auto as(Range)(Range r) + if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) + { + static if (is(ForeachType!Range == T)) + return r; + else + { + import core.lifetime: move; + import mir.conv: to; + return map!(to!T)(r.move); + } + } } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { + import mir.ndslice.slice: Slice; import mir.ndslice.allocation : slice; import mir.ndslice.topology : diagonal, as; @@ -3077,60 +3729,68 @@ template as(T) } /// Special behavior for pointers to a constant data. -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; - import mir.ndslice.slice : Contiguous, Slice; + import mir.ndslice.slice: Contiguous, Slice; Slice!(double*, 2) matrix = slice!double([2, 2], 0); Slice!(const(double)*, 2) const_matrix = matrix.as!(const double); } +/// Ranges +@safe pure nothrow version(mir_ndslice_test) unittest +{ + import mir.algorithm.iteration: filter, equal; + assert(5.iota.filter!"a % 2".as!double.map!"a / 2".equal([0.5, 1.5])); +} + /++ -Takes a field `source` and a slice `indexes`, and creates a view of source as if its elements were reordered according to indexes. -`indexes` may include only a subset of the elements of `source` and may also repeat elements. +Takes a field `source` and a slice `indices`, and creates a view of source as if its elements were reordered according to indices. +`indices` may include only a subset of the elements of `source` and may also repeat elements. Params: source = a filed, source of data. `source` must be an array or a pointer, or have `opIndex` primitive. Full random access range API is not required. - indexes = a slice, source of indexes. + indices = a slice, source of indices. Returns: n-dimensional slice with the same kind, shape and strides. -See_also: `indexed` is similar to $(LREF, vmap), but a field (`[]`) is used instead of a function (`()`), and order of arguments is reversed. +See_also: `indexed` is similar to $(LREF vmap), but a field (`[]`) is used instead of a function (`()`), and order of arguments is reversed. +/ Slice!(IndexIterator!(Iterator, Field), N, kind) indexed(Field, Iterator, size_t N, SliceKind kind) - (Field source, Slice!(Iterator, N, kind) indexes) + (Field source, Slice!(Iterator, N, kind) indices) { + import core.lifetime: move; return typeof(return)( - indexes._structure, + indices._structure, IndexIterator!(Iterator, Field)( - indexes._iterator, + indices._iterator.move, source)); } /// ditto -auto indexed(Field, S)(Field source, S[] indexes) +auto indexed(Field, S)(Field source, S[] indices) { - return indexed(source, indexes.sliced); + return indexed(source, indices.sliced); } /// ditto -auto indexed(Field, S)(Field source, S indexes) +auto indexed(Field, S)(Field source, S indices) if (hasAsSlice!S) { - return indexed(source, indexes.asSlice); + return indexed(source, indices.asSlice); } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { auto source = [1, 2, 3, 4, 5]; - auto indexes = [4, 3, 1, 2, 0, 4]; - auto ind = source.indexed(indexes); + auto indices = [4, 3, 1, 2, 0, 4]; + auto ind = source.indexed(indices); assert(ind == [5, 4, 2, 3, 1, 5]); - assert(ind.retro == source.indexed(indexes.retro)); + assert(ind.retro == source.indexed(indices.retro)); ind[3] += 10; // for index 2 // 0 1 2 3 4 @@ -3138,10 +3798,10 @@ auto indexed(Field, S)(Field source, S indexes) } /++ -Maps indexes pairs to subslices. +Maps index pairs to subslices. Params: sliceable = pointer, array, ndslice, series, or something sliceable with `[a .. b]`. - slices = ndslice composed of indexes pairs. + slices = ndslice composed of index pairs. Returns: ndslice composed of subslices. See_also: $(LREF chopped), $(LREF pairwise). @@ -3152,9 +3812,10 @@ Slice!(SubSliceIterator!(Iterator, Sliceable), N, kind) Slice!(Iterator, N, kind) slices, ) { + import core.lifetime: move; return typeof(return)( slices._structure, - SubSliceIterator!(Iterator, Sliceable)(slices._iterator, sliceable) + SubSliceIterator!(Iterator, Sliceable)(slices._iterator.move, sliceable.move) ); } @@ -3172,7 +3833,7 @@ auto subSlices(S, Sliceable)(Sliceable sliceable, S slices) } /// -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.functional: staticArray; auto subs =[ @@ -3189,7 +3850,7 @@ auto subSlices(S, Sliceable)(Sliceable sliceable, S slices) } /++ -Maps indexes pairs to subslices. +Maps index pairs to subslices. Params: bounds = ndslice composed of consequent (`a_i <= a_(i+1)`) pairwise index bounds. sliceable = pointer, array, ndslice, series, or something sliceable with `[a_i .. a_(i+1)]`. @@ -3208,20 +3869,20 @@ in assert(b); } do { - + import core.lifetime: move; sizediff_t length = bounds._lengths[0] <= 1 ? 0 : bounds._lengths[0] - 1; static if (hasLength!Sliceable) { if (length && bounds[length - 1] > sliceable.length) { version (D_Exceptions) - throw choppedException; + { import mir.exception : toMutable; throw choppedException.toMutable; } else assert(0, choppedExceptionMsg); } } - return typeof(return)([size_t(length)], ChopIterator!(Iterator, Sliceable)(bounds._iterator, sliceable)); + return typeof(return)([size_t(length)], ChopIterator!(Iterator, Sliceable)(bounds._iterator.move, sliceable.move)); } /// ditto @@ -3238,10 +3899,10 @@ auto chopped(S, Sliceable)(Sliceable sliceable, S bounds) } /// -@safe pure version(mir_test) unittest +@safe pure version(mir_ndslice_test) unittest { import mir.functional: staticArray; - import mir.ndslice.slice : sliced; + import mir.ndslice.slice: sliced; auto pairwiseIndexes = [2, 4, 10].sliced; auto sliceable = 10.iota; @@ -3258,48 +3919,59 @@ Params: sameStrides = if `true` assumes that all slices has the same strides. slices = list of slices Returns: - n-dimensional slice of elements refTuple + n-dimensional slice of elements tuple See_also: $(SUBREF slice, Slice.strides). +/ -auto zip - (bool sameStrides = false, Slices...)(Slices slices) - if (Slices.length > 1 && allSatisfy!(isConvertibleToSlice, Slices)) +template zip(bool sameStrides = false) { - static if (allSatisfy!(isSlice, Slices)) + /++ + Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional. + Params: + slices = list of slices + Returns: + n-dimensional slice of elements tuple + See_also: $(SUBREF slice, Slice.strides). + +/ + @fmamath + auto zip(Slices...)(Slices slices) + if (Slices.length > 1 && allSatisfy!(isConvertibleToSlice, Slices)) { - enum N = Slices[0].N; - foreach(i, S; Slices[1 .. $]) - { - static assert(S.N == N, "zip: all Slices must have the same dimension count"); - assert(slices[i + 1]._lengths == slices[0]._lengths, "zip: all slices must have the same lengths"); - static if (sameStrides) - assert(slices[i + 1].strides == slices[0].strides, "zip: all slices must have the same strides when unpacked"); - } - static if (!sameStrides && minElem(staticMap!(kindOf, Slices)) != Contiguous) + static if (allSatisfy!(isSlice, Slices)) { - static assert(N == 1, "zip: cannot zip canonical and universal multidimensional slices if `sameStrides` is false"); - mixin(`return .zip(` ~ _iotaArgs!(Slices.length, "slices[", "].hideStride, ") ~`);`); + enum N = Slices[0].N; + foreach(i, S; Slices[1 .. $]) + { + static assert(S.N == N, "zip: all Slices must have the same dimension count"); + assert(slices[i + 1]._lengths == slices[0]._lengths, "zip: all slices must have the same lengths"); + static if (sameStrides) + assert(slices[i + 1].strides == slices[0].strides, "zip: all slices must have the same strides when unpacked"); + } + static if (!sameStrides && minElem(staticMap!(kindOf, Slices)) != Contiguous) + { + static assert(N == 1, "zip: cannot zip canonical and universal multidimensional slices if `sameStrides` is false"); + mixin(`return .zip(` ~ _iotaArgs!(Slices.length, "slices[", "].hideStride, ") ~`);`); + } + else + { + enum kind = maxElem(staticMap!(kindOf, Slices)); + alias Iterator = ZipIterator!(staticMap!(_IteratorOf, Slices)); + alias Ret = Slice!(Iterator, N, kind); + auto structure = Ret._Structure.init; + structure[0] = slices[0]._lengths; + foreach (i; Iota!(Ret.S)) + structure[1][i] = slices[0]._strides[i]; + return Ret(structure, mixin("Iterator(" ~ _iotaArgs!(Slices.length, "slices[", "]._iterator, ") ~ ")")); + } } else { - enum kind = maxElem(staticMap!(kindOf, Slices)); - alias Iterator = ZipIterator!(staticMap!(_IteratorOf, Slices)); - alias Ret = Slice!(Iterator, N, kind); - auto structure = Ret._Structure.init; - structure[0] = slices[0]._lengths; - foreach (i; Iota!(Ret.S)) - structure[1][i] = slices[0]._strides[i]; - return Ret(structure, mixin("Iterator(" ~ _iotaArgs!(Slices.length, "slices[", "]._iterator, ") ~ ")")); + return .zip(toSlices!slices); } } - else - { - return .zip(toSlices!slices); - } } /// -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; import mir.ndslice.topology : flattened, iota; @@ -3319,7 +3991,7 @@ auto zip assert(alpha == beta); } -@safe pure nothrow version(mir_test) unittest +@safe pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; import mir.ndslice.topology : flattened, iota; @@ -3344,6 +4016,17 @@ Returns: auto unzip (char name, size_t N, SliceKind kind, Iterators...) (Slice!(ZipIterator!Iterators, N, kind) slice) +{ + import core.lifetime: move; + enum size_t i = name - 'a'; + static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`); + return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i].move).unhideStride; +} + +/// ditto +auto unzip + (char name, size_t N, SliceKind kind, Iterators...) + (ref Slice!(ZipIterator!Iterators, N, kind) slice) { enum size_t i = name - 'a'; static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`); @@ -3351,7 +4034,7 @@ auto unzip } /// -pure nothrow version(mir_test) unittest +pure nothrow version(mir_ndslice_test) unittest { import mir.ndslice.allocation : slice; import mir.ndslice.topology : iota; @@ -3370,100 +4053,231 @@ pure nothrow version(mir_test) unittest private enum TotalDim(NdFields...) = [staticMap!(DimensionCount, NdFields)].sum; +private template applyInner(alias fun, size_t N) +{ + static if (N == 0) + alias applyInner = fun; + else + { + import mir.functional: pipe; + alias applyInner = pipe!(zip!true, map!(.applyInner!(fun, N - 1))); + } +} + /++ -Sliding map for vectors. -Works with packed slices. +Lazy convolution for tensors. -Suitable for simple convolution algorithms. +Suitable for advanced convolution algorithms. Params: - params = windows length. - fun = map functions with `params` arity. -See_also: $(LREF pairwise), $(LREF diff). + params = convolution windows length. + fun = one dimensional convolution function with `params` arity. + SDimensions = dimensions to perform lazy convolution along. Negative dimensions are supported. +See_also: $(LREF slide), $(LREF pairwise), $(LREF diff). +/ -template slide(size_t params, alias fun) - if (params <= 'z' - 'a' + 1) +template slideAlong(size_t params, alias fun, SDimensions...) + if (params <= 'z' - 'a' + 1 && SDimensions.length > 0) { import mir.functional: naryFun; - static if (params > 1 && __traits(isSame, naryFun!fun, fun)) + static if (allSatisfy!(isSizediff_t, SDimensions) && params > 1 && __traits(isSame, naryFun!fun, fun)) { - @optmath: + @fmamath: /++ - Params: - slice = An input slice with first dimension pack equals to one (e.g. 1-dimensional for not packed slices). - Returns: - 1d-slice composed of `fun(slice[i], ..., slice[i + params - 1])`. + Params: slice = ndslice or array + Returns: lazy convolution result +/ - auto slide(Iterator, size_t N, SliceKind kind) + auto slideAlong(Iterator, size_t N, SliceKind kind) (Slice!(Iterator, N, kind) slice) - if (N == 1) { - auto s = slice.map!"a".flattened; - if (cast(sizediff_t)s._lengths[0] < sizediff_t(params - 1)) - s._lengths[0] = 0; + import core.lifetime: move; + static if (N > 1 && kind == Contiguous) + { + return slideAlong(slice.move.canonical); + } else - s._lengths[0] -= params - 1; - - alias I = SlideIterator!(_IteratorOf!(typeof(s)), params, fun); - return Slice!(I)( - s._structure, - I(s._iterator)); + static if (N == 1 && kind == Universal) + { + return slideAlong(slice.move.flattened); + } + else + { + alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions); + enum dimension = Dimensions[$ - 1]; + size_t len = slice._lengths[dimension] - (params - 1); + if (sizediff_t(len) <= 0) // overfow + len = 0; + slice._lengths[dimension] = len; + static if (dimension + 1 == N || kind == Universal) + { + alias I = SlideIterator!(Iterator, params, fun); + auto ret = Slice!(I, N, kind)(slice._structure, I(move(slice._iterator))); + } + else + { + alias Z = ZipIterator!(Repeat!(params, Iterator)); + Z z; + foreach_reverse (p; Iota!(1, params)) + z._iterators[p] = slice._iterator + slice._strides[dimension] * p; + z._iterators[0] = move(slice._iterator); + alias M = MapIterator!(Z, fun); + auto ret = Slice!(M, N, kind)(slice._structure, M(move(z))); + } + static if (Dimensions.length == 1) + { + return ret; + } + else + { + return .slideAlong!(params, fun, Dimensions[0 .. $ - 1])(ret); + } + } } /// ditto - auto slide(S)(S[] slice) + auto slideAlong(S)(S[] slice) { - return slide(slice.sliced); + return slideAlong(slice.sliced); } /// ditto - auto slide(S)(S slice) + auto slideAlong(S)(S slice) if (hasAsSlice!S) { - return slide(slice.asSlice); + return slideAlong(slice.asSlice); } } else static if (params == 1) - alias slide = .map!(naryFun!fun); - else alias slide = .slide!(params, naryFun!fun); + alias slideAlong = .map!(naryFun!fun); + else alias slideAlong = .slideAlong!(params, naryFun!fun, staticMap!(toSizediff_t, SDimensions)); } /// -version(mir_test) unittest +@safe pure nothrow @nogc version(mir_ndslice_test) unittest { - auto data = 10.iota; - auto sw = data.slide!(3, "a + 2 * b + c"); - - import mir.utility: max; - assert(sw.length == max(0, cast(ptrdiff_t)data.length - 3 + 1)); - assert(sw == sw.length.iota.map!"(a + 1) * 4"); - assert(sw == [4, 8, 12, 16, 20, 24, 28, 32]); -} - -/++ -Pairwise map for vectors. -Works with packed slices. + auto data = [4, 5].iota; -Params: - fun = function to accumulate - lag = an integer indicating which lag to use -Returns: lazy ndslice composed of `fun(a_n, a_n+1)` values. + alias scaled = a => a * 0.25; -See_also: $(LREF slide), $(LREF subSlices). -+/ -alias pairwise(alias fun, size_t lag = 1) = slide!(lag + 1, fun); + auto v = data.slideAlong!(3, "a + 2 * b + c", 0).map!scaled; + auto h = data.slideAlong!(3, "a + 2 * b + c", 1).map!scaled; -/// -version(mir_test) unittest -{ - assert([2, 4, 3, -1].sliced.pairwise!"a + b" == [6, 7, 2]); + assert(v == [4, 5].iota[1 .. $ - 1, 0 .. $]); + assert(h == [4, 5].iota[0 .. $, 1 .. $ - 1]); } /++ -Differences between vector elements. -Works with packed slices. +Lazy convolution for tensors. + +Suitable for simple convolution algorithms. + +Params: + params = windows length. + fun = one dimensional convolution function with `params` arity. +See_also: $(LREF slideAlong), $(LREF withNeighboursSum), $(LREF pairwise), $(LREF diff). ++/ +template slide(size_t params, alias fun) + if (params <= 'z' - 'a' + 1) +{ + import mir.functional: naryFun; + + static if (params > 1 && __traits(isSame, naryFun!fun, fun)) + { + @fmamath: + /++ + Params: slice = ndslice or array + Returns: lazy convolution result + +/ + auto slide(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice) + { + import core.lifetime: move; + return slice.move.slideAlong!(params, fun, Iota!N); + } + + /// ditto + auto slide(S)(S[] slice) + { + return slide(slice.sliced); + } + + /// ditto + auto slide(S)(S slice) + if (hasAsSlice!S) + { + return slide(slice.asSlice); + } + } + else + static if (params == 1) + alias slide = .map!(naryFun!fun); + else alias slide = .slide!(params, naryFun!fun); +} + +/// +version(mir_ndslice_test) unittest +{ + auto data = 10.iota; + auto sw = data.slide!(3, "a + 2 * b + c"); + + import mir.utility: max; + assert(sw.length == max(0, cast(ptrdiff_t)data.length - 3 + 1)); + assert(sw == sw.length.iota.map!"(a + 1) * 4"); + assert(sw == [4, 8, 12, 16, 20, 24, 28, 32]); +} + +/++ +ND-use case ++/ +@safe pure nothrow @nogc version(mir_ndslice_test) unittest +{ + auto data = [4, 5].iota; + + enum factor = 1.0 / 4 ^^ data.shape.length; + alias scaled = a => a * factor; + + auto sw = data.slide!(3, "a + 2 * b + c").map!scaled; + + assert(sw == [4, 5].iota[1 .. $ - 1, 1 .. $ - 1]); +} + +/++ +Pairwise map for tensors. + +The computation is performed on request, when the element is accessed. + +Params: + fun = function to accumulate + lag = an integer indicating which lag to use +Returns: lazy ndslice composed of `fun(a_n, a_n+1)` values. + +See_also: $(LREF slide), $(LREF slideAlong), $(LREF subSlices). ++/ +alias pairwise(alias fun, size_t lag = 1) = slide!(lag + 1, fun); + +/// +@safe pure nothrow version(mir_ndslice_test) unittest +{ + import mir.ndslice.slice: sliced; + assert([2, 4, 3, -1].sliced.pairwise!"a + b" == [6, 7, 2]); +} + +/// N-dimensional +@safe pure nothrow +version(mir_ndslice_test) unittest +{ + // performs pairwise along each dimension + // 0 1 2 3 + // 4 5 6 7 + // 8 9 10 11 + assert([3, 4].iota.pairwise!"a + b" == [[10, 14, 18], [26, 30, 34]]); +} + +/++ +Differences between tensor elements. + +The computation is performed on request, when the element is accessed. Params: lag = an integer indicating which lag to use @@ -3474,19 +4288,37 @@ See_also: $(LREF slide), $(LREF slide). alias diff(size_t lag = 1) = pairwise!(('a' + lag) ~ " - a", lag); /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { + import mir.ndslice.slice: sliced; assert([2, 4, 3, -1].sliced.diff == [2, -1, -4]); } +/// N-dimensional +@safe pure nothrow @nogc +version(mir_ndslice_test) unittest +{ + // 0 1 2 3 + // 4 5 6 7 => + // 8 9 10 11 + + // 1 1 1 + // 1 1 1 => + // 1 1 1 + + // 0 0 0 + // 0 0 0 + + assert([3, 4].iota.diff == repeat(0, [2, 3])); +} + /// packed slices -version(mir_test) unittest +version(mir_ndslice_test) unittest { // 0 1 2 3 // 4 5 6 7 // 8 9 10 11 auto s = iota(3, 4); - import std.stdio; assert(iota(3, 4).byDim!0.diff == [ [4, 4, 4, 4], [4, 4, 4, 4]]); @@ -3496,6 +4328,138 @@ version(mir_test) unittest [1, 1, 1]]); } +/++ +Drops borders for all dimensions. + +Params: + slice = ndslice +Returns: + Tensors with striped borders +See_also: + $(LREF universal), + $(LREF assumeCanonical), + $(LREF assumeContiguous). ++/ +Slice!(Iterator, N, N > 1 && kind == Contiguous ? Canonical : kind, Labels) + dropBorders + (Iterator, size_t N, SliceKind kind, Labels...) + (Slice!(Iterator, N, kind, Labels) slice) +{ + static if (N > 1 && kind == Contiguous) + { + import core.lifetime: move; + auto ret = slice.move.canonical; + } + else + { + alias ret = slice; + } + ret.popFrontAll; + ret.popBackAll; + return ret; +} + +/// +version(mir_ndslice_test) unittest +{ + assert([4, 5].iota.dropBorders == [[6, 7, 8], [11, 12, 13]]); +} + +/++ +Lazy zip view of elements packed with sum of their neighbours. + +Params: + fun = neighbours accumulation function. +See_also: $(LREF slide), $(LREF slideAlong). ++/ +template withNeighboursSum(alias fun = "a + b") +{ + import mir.functional: naryFun; + + static if (__traits(isSame, naryFun!fun, fun)) + { + @fmamath: + /++ + Params: + slice = ndslice or array + Returns: + Lazy zip view of elements packed with sum of their neighbours. + +/ + auto withNeighboursSum(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice) + { + import core.lifetime: move; + static if (N > 1 && kind == Contiguous) + { + return withNeighboursSum(slice.move.canonical); + } + else + static if (N == 1 && kind == Universal) + { + return withNeighboursSum(slice.move.flattened); + } + else + { + enum around = kind != Universal; + alias Z = NeighboursIterator!(Iterator, N - around, fun, around); + + size_t shift; + foreach (dimension; Iota!N) + { + slice._lengths[dimension] -= 2; + if (sizediff_t(slice._lengths[dimension]) <= 0) // overfow + slice._lengths[dimension] = 0; + shift += slice._stride!dimension; + } + + Z z; + z._iterator = move(slice._iterator); + z._iterator += shift; + foreach (dimension; Iota!(N - around)) + { + z._neighbours[dimension][0] = z._iterator - slice._strides[dimension]; + z._neighbours[dimension][1] = z._iterator + slice._strides[dimension]; + } + return Slice!(Z, N, kind)(slice._structure, move(z)); + } + } + + /// ditto + auto withNeighboursSum(S)(S[] slice) + { + return withNeighboursSum(slice.sliced); + } + + /// ditto + auto withNeighboursSum(S)(S slice) + if (hasAsSlice!S) + { + return withNeighboursSum(slice.asSlice); + } + } + else alias withNeighboursSum = .withNeighboursSum!(naryFun!fun); +} + +/// +@safe pure nothrow @nogc version(mir_ndslice_test) unittest +{ + import mir.ndslice.allocation: slice; + import mir.algorithm.iteration: all; + + auto wn = [4, 5].iota.withNeighboursSum; + assert(wn.all!"a[0] == a[1] * 0.25"); + assert(wn.map!"a" == wn.map!"b * 0.25"); +} + +@safe pure nothrow @nogc version(mir_ndslice_test) unittest +{ + import mir.ndslice.allocation: slice; + import mir.algorithm.iteration: all; + + auto wn = [4, 5].iota.withNeighboursSum.universal; + assert(wn.all!"a[0] == a[1] * 0.25"); + assert(wn.map!"a" == wn.map!"b * 0.25"); +} /++ Cartesian product. @@ -3513,7 +4477,7 @@ auto cartesian(NdFields...)(NdFields fields) } /// 1D x 1D -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto a = [10, 20, 30]; auto b = [ 1, 2, 3]; @@ -3528,7 +4492,7 @@ version(mir_test) unittest } /// 1D x 2D -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto a = [10, 20, 30]; auto b = iota([2, 3], 1); @@ -3554,7 +4518,7 @@ version(mir_test) unittest } /// 1D x 1D x 1D -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto u = [100, 200]; auto v = [10, 20, 30]; @@ -3578,8 +4542,6 @@ version(mir_test) unittest ]]); } - - /++ $(LINK2 https://en.wikipedia.org/wiki/Kronecker_product, Kronecker product). @@ -3597,7 +4559,7 @@ template kronecker(alias fun = product) Returns: $(SUBREF ndfield, Kronecker)`!(fun, NdFields)(fields).`$(SUBREF slice, slicedNdField) +/ - @optmath auto kronecker(NdFields...)(NdFields fields) + @fmamath auto kronecker(NdFields...)(NdFields fields) if (allSatisfy!(hasShape, NdFields) || allSatisfy!(hasLength, NdFields)) { return Kronecker!(fun, NdFields)(fields).slicedNdField; @@ -3607,10 +4569,10 @@ template kronecker(alias fun = product) } /// 2D -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; - import mir.ndslice.slice : sliced; + import mir.ndslice.slice: sliced; // eye auto a = slice!double([4, 4], 0); @@ -3633,7 +4595,7 @@ version(mir_test) unittest } /// 1D -version(mir_test) unittest +version(mir_ndslice_test) unittest { auto a = iota([3], 1); @@ -3645,10 +4607,10 @@ version(mir_test) unittest } /// 2D with 3 arguments -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.allocation: slice; - import mir.ndslice.slice : sliced; + import mir.ndslice.slice: sliced; auto a = [ 1, 2, 3, 4].sliced(2, 2); @@ -3692,7 +4654,7 @@ auto magic(size_t length) /// @safe pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.math.sum; import mir.ndslice: slice, magic, byDim, map, as, repeat, diagonal, antidiagonal; @@ -3766,7 +4728,7 @@ auto stairs(string type, S)(S slice, size_t n) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, stairs; @@ -3836,7 +4798,7 @@ auto stairs(string type, Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) sli } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology: iota, as, stairs; @@ -3881,69 +4843,52 @@ version(mir_test) unittest } /++ -Returns a slice that can be iterated by dimension. Transposes dimensions on top and then packs them. +Returns a slice that can be iterated along dimension. Transposes other dimensions on top and then packs them. -Combines $(LREF transposed) and $(LREF ipack). +Combines $(LREF byDim) and $(LREF evertPack). Params: - Dimensions = dimensions to perform iteration on + SDimensions = dimensions to iterate along, length of d, `1 <= d < n`. Negative dimensions are supported. Returns: - n-dimensional slice ipacked to allow iteration by dimension + `(n-d)`-dimensional slice composed of d-dimensional slices See_also: - $(LREF slice), + $(LREF byDim), + $(LREF iota), + $(SUBREF allocation, slice), $(LREF ipack), - $(LREF transposed). + $(SUBREF dynamic, transposed). +/ -template byDim(Dimensions...) - if (Dimensions.length > 0) +template alongDim(SDimensions...) + if (SDimensions.length > 0) { - import mir.ndslice.internal : isSize_t; - import std.meta : allSatisfy; - - static if (!allSatisfy!(isSize_t, Dimensions)) + static if (allSatisfy!(isSizediff_t, SDimensions)) { - import std.meta : staticMap; - import mir.ndslice.internal : toSize_t; - - alias byDim = .byDim!(staticMap!(toSize_t, Dimensions)); - } - else - { - import mir.ndslice.slice : Slice, SliceKind; /++ Params: - slice = input slice (may not be 1-dimensional slice) + slice = input n-dimensional slice, n > d Returns: - n-dimensional slice ipacked to allow iteration by dimension + `(n-d)`-dimensional slice composed of d-dimensional slices +/ - @optmath auto byDim(Iterator, size_t N, SliceKind kind) - (Slice!(Iterator, N, kind) slice) + @fmamath auto alongDim(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice) + if (N > SDimensions.length) { - import mir.ndslice.topology : ipack; - import mir.ndslice.internal : DimensionsCountCTError; - - mixin DimensionsCountCTError; - - static if (N == 1) - { - return slice; - } - else - { - import mir.ndslice.dynamic: transposed; - return slice - .transposed!Dimensions - .ipack!(Dimensions.length); - } + import core.lifetime: move; + return slice.move.byDim!SDimensions.evertPack; } } + else + { + alias alongDim = .alongDim!(staticMap!(toSizediff_t, SDimensions)); + } } /// 2-dimensional slice support @safe @nogc pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { - import mir.ndslice.topology : iota; + import mir.ndslice; + // ------------ // | 0 1 2 3 | // | 4 5 6 7 | @@ -3962,7 +4907,9 @@ version(mir_test) unittest // | 4 5 6 7 | // | 8 9 10 11 | // ------------ - auto x = slice.byDim!0; + auto x = slice.alongDim!(-1); // -1 is the last dimension index, the same as 1 for this case. + static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); + assert(x.shape == shape3); assert(x.front.shape == shape4); assert(x.front == iota(4)); @@ -3975,7 +4922,9 @@ version(mir_test) unittest // | 2 6 10 | // | 3 7 11 | // --------- - auto y = slice.byDim!1; + auto y = slice.alongDim!0; // alongDim!(-2) is the same for matrices. + static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); + assert(y.shape == shape4); assert(y.front.shape == shape3); assert(y.front == iota([3], 0, 4)); @@ -3985,10 +4934,10 @@ version(mir_test) unittest /// 3-dimensional slice support, N-dimensional also supported @safe @nogc pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { - import mir.ndslice.topology : iota, universal, flattened, reshape; - import mir.ndslice.dynamic : strided, transposed; + import mir.ndslice; + // ---------------- // | 0 1 2 3 4 | // | 5 6 7 8 9 | @@ -4006,20 +4955,7 @@ version(mir_test) unittest // | 55 56 57 58 59 | // ---------------- auto slice = iota(3, 4, 5); - //-> - // | 4 5 | - //-> - // | 3 5 | - //-> - // | 3 4 | - //-> - // | 5 4 | - //-> - // | 3 | - //-> - // | 4 | - //-> - // | 5 | + size_t[2] shape45 = [4, 5]; size_t[2] shape35 = [3, 5]; size_t[2] shape34 = [3, 4]; @@ -4028,6 +4964,41 @@ version(mir_test) unittest size_t[1] shape4 = [4]; size_t[1] shape5 = [5]; + // ---------- + // | 0 20 40 | + // | 5 25 45 | + // | 10 30 50 | + // | 15 35 55 | + // - - - - - + // | 1 21 41 | + // | 6 26 46 | + // | 11 31 51 | + // | 16 36 56 | + // - - - - - + // | 2 22 42 | + // | 7 27 47 | + // | 12 32 52 | + // | 17 37 57 | + // - - - - - + // | 3 23 43 | + // | 8 28 48 | + // | 13 33 53 | + // | 18 38 58 | + // - - - - - + // | 4 24 44 | + // | 9 29 49 | + // | 14 34 54 | + // | 19 39 59 | + // ---------- + auto a = slice.alongDim!0.transposed; + static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal))); + + assert(a.shape == shape54); + assert(a.front.shape == shape4); + assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed); + a.popFront; + assert(a.front.front == iota([3], 1, 20)); + // ---------------- // | 0 1 2 3 4 | // | 5 6 7 8 9 | @@ -4044,7 +5015,9 @@ version(mir_test) unittest // | 50 51 52 53 54 | // | 55 56 57 58 59 | // ---------------- - auto x = slice.byDim!0; + auto x = slice.alongDim!(1, 2); + static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal))); + assert(x.shape == shape3); assert(x.front.shape == shape45); assert(x.front == iota([4, 5])); @@ -4068,7 +5041,9 @@ version(mir_test) unittest // | 35 36 37 38 39 | // | 55 56 57 58 59 | // ---------------- - auto y = slice.byDim!1; + auto y = slice.alongDim!(0, 2); + static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal))); + assert(y.shape == shape4); assert(y.front.shape == shape35); int err; @@ -4097,23 +5072,333 @@ version(mir_test) unittest // | 24 29 34 39 | // | 44 49 54 59 | // ------------- - auto z = slice.byDim!2; + auto z = slice.alongDim!(0, 1); + static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal)))); + assert(z.shape == shape5); assert(z.front.shape == shape34); assert(z.front == iota([3, 4], 0, 5)); z.popFront; assert(z.front.front == iota([4], 1, 5)); +} - // ---------- - // | 0 20 40 | - // | 5 25 45 | - // | 10 30 50 | - // | 15 35 55 | - // - - - - - - // | 1 21 41 | - // | 6 26 46 | - // | 11 31 51 | - // | 16 36 56 | +/// Use alongDim to calculate column mean/row mean of 2-dimensional slice +version(mir_ndslice_test) +@safe pure +unittest +{ + import mir.ndslice.topology: alongDim; + import mir.ndslice.fuse: fuse; + import mir.math.stat: mean; + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + + // Use alongDim with map to compute mean of row/column. + assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); + assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); + + // FIXME + // Without using map, computes the mean of the whole slice + // assert(x.alongDim!1.mean == x.sliced.mean); + // assert(x.alongDim!0.mean == x.sliced.mean); +} + +/++ +Use alongDim and map with a lambda, but may need to allocate result. This example +uses fuse, which allocates. Note: fuse!1 will transpose the result. ++/ +version(mir_ndslice_test) +@safe pure +unittest { + import mir.ndslice.topology: iota, alongDim, map; + import mir.ndslice.fuse: fuse; + import mir.ndslice.slice: sliced; + + auto x = [1, 2, 3].sliced; + auto y = [1, 2].sliced; + + auto s1 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; + assert(s1 == [[ 0, 2, 6], + [ 3, 8, 15]]); + auto s2 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1; + assert(s2 == [[ 0, 1, 2], + [ 6, 8, 10]]); +} + +/++ +Returns a slice that can be iterated by dimension. Transposes dimensions on top and then packs them. + +Combines $(SUBREF dynamic, transposed), $(LREF ipack), and SliceKind Selectors. + +Params: + SDimensions = dimensions to perform iteration on, length of d, `1 <= d <= n`. Negative dimensions are supported. +Returns: + d-dimensional slice composed of `(n-d)`-dimensional slices +See_also: + $(LREF alongDim), + $(SUBREF allocation, slice), + $(LREF ipack), + $(SUBREF dynamic, transposed). ++/ +template byDim(SDimensions...) + if (SDimensions.length > 0) +{ + static if (allSatisfy!(isSizediff_t, SDimensions)) + { + /++ + Params: + slice = input n-dimensional slice, n >= d + Returns: + d-dimensional slice composed of `(n-d)`-dimensional slices + +/ + @fmamath auto byDim(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice) + if (N >= SDimensions.length) + { + + alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions); + + mixin DimensionsCountCTError; + + static if (N == 1) + { + return slice; + } + else + { + import core.lifetime: move; + import mir.ndslice.dynamic: transposed; + import mir.algorithm.iteration: all; + + auto trans = slice + .move + .transposed!Dimensions; + static if (Dimensions.length == N) + { + return trans; + } + else + { + auto ret = trans.move.ipack!(Dimensions.length); + static if ((kind == Contiguous || kind == Canonical && N - Dimensions.length == 1) && [Dimensions].all!(a => a < Dimensions.length)) + { + return ret + .move + .evertPack + .assumeContiguous + .evertPack; + } + else + static if (kind == Canonical && [Dimensions].all!(a => a < N - 1)) + { + return ret + .move + .evertPack + .assumeCanonical + .evertPack; + } + else + static if ((kind == Contiguous || kind == Canonical && Dimensions.length == 1) && [Dimensions] == [Iota!(N - Dimensions.length, N)]) + { + return ret.assumeContiguous; + } + else + static if ((kind == Contiguous || kind == Canonical) && Dimensions[$-1] == N - 1) + { + return ret.assumeCanonical; + } + else + { + return ret; + } + } + } + } + } + else + { + alias byDim = .byDim!(staticMap!(toSizediff_t, SDimensions)); + } +} + +/// 2-dimensional slice support +@safe @nogc pure nothrow +version(mir_ndslice_test) unittest +{ + import mir.ndslice; + + // ------------ + // | 0 1 2 3 | + // | 4 5 6 7 | + // | 8 9 10 11 | + // ------------ + auto slice = iota(3, 4); + //-> + // | 3 | + //-> + // | 4 | + size_t[1] shape3 = [3]; + size_t[1] shape4 = [4]; + + // ------------ + // | 0 1 2 3 | + // | 4 5 6 7 | + // | 8 9 10 11 | + // ------------ + auto x = slice.byDim!0; // byDim!(-2) is the same for matrices. + static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); + + assert(x.shape == shape3); + assert(x.front.shape == shape4); + assert(x.front == iota(4)); + x.popFront; + assert(x.front == iota([4], 4)); + + // --------- + // | 0 4 8 | + // | 1 5 9 | + // | 2 6 10 | + // | 3 7 11 | + // --------- + auto y = slice.byDim!(-1); // -1 is the last dimension index, the same as 1 for this case. + static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); + + assert(y.shape == shape4); + assert(y.front.shape == shape3); + assert(y.front == iota([3], 0, 4)); + y.popFront; + assert(y.front == iota([3], 1, 4)); +} + +/// 3-dimensional slice support, N-dimensional also supported +@safe @nogc pure nothrow +version(mir_ndslice_test) unittest +{ + import mir.ndslice; + + // ---------------- + // | 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 48 49 | + // | 50 51 52 53 54 | + // | 55 56 57 58 59 | + // ---------------- + auto slice = iota(3, 4, 5); + + size_t[2] shape45 = [4, 5]; + size_t[2] shape35 = [3, 5]; + size_t[2] shape34 = [3, 4]; + size_t[2] shape54 = [5, 4]; + size_t[1] shape3 = [3]; + size_t[1] shape4 = [4]; + size_t[1] shape5 = [5]; + + // ---------------- + // | 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 48 49 | + // | 50 51 52 53 54 | + // | 55 56 57 58 59 | + // ---------------- + auto x = slice.byDim!0; + static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal))); + + assert(x.shape == shape3); + assert(x.front.shape == shape45); + assert(x.front == iota([4, 5])); + x.popFront; + assert(x.front == iota([4, 5], (4 * 5))); + + // ---------------- + // | 0 1 2 3 4 | + // | 20 21 22 23 24 | + // | 40 41 42 43 44 | + // - - - - - - - - + // | 5 6 7 8 9 | + // | 25 26 27 28 29 | + // | 45 46 47 48 49 | + // - - - - - - - - + // | 10 11 12 13 14 | + // | 30 31 32 33 34 | + // | 50 51 52 53 54 | + // - - - - - - - - + // | 15 16 17 18 19 | + // | 35 36 37 38 39 | + // | 55 56 57 58 59 | + // ---------------- + auto y = slice.byDim!1; + static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal))); + + assert(y.shape == shape4); + assert(y.front.shape == shape35); + int err; + assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err)); + y.popFront; + assert(y.front.front == iota([5], 5)); + + // ------------- + // | 0 5 10 15 | + // | 20 25 30 35 | + // | 40 45 50 55 | + // - - - - - - - + // | 1 6 11 16 | + // | 21 26 31 36 | + // | 41 46 51 56 | + // - - - - - - - + // | 2 7 12 17 | + // | 22 27 32 37 | + // | 42 47 52 57 | + // - - - - - - - + // | 3 8 13 18 | + // | 23 28 33 38 | + // | 43 48 53 58 | + // - - - - - - - + // | 4 9 14 19 | + // | 24 29 34 39 | + // | 44 49 54 59 | + // ------------- + auto z = slice.byDim!2; + static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal)))); + + assert(z.shape == shape5); + assert(z.front.shape == shape34); + assert(z.front == iota([3, 4], 0, 5)); + z.popFront; + assert(z.front.front == iota([4], 1, 5)); + + // ---------- + // | 0 20 40 | + // | 5 25 45 | + // | 10 30 50 | + // | 15 35 55 | + // - - - - - + // | 1 21 41 | + // | 6 26 46 | + // | 11 31 51 | + // | 16 36 56 | // - - - - - // | 2 22 42 | // | 7 27 47 | @@ -4131,16 +5416,66 @@ version(mir_test) unittest // | 19 39 59 | // ---------- auto a = slice.byDim!(2, 1); + static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal))); + assert(a.shape == shape54); assert(a.front.shape == shape4); - assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed!1); + assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed); a.popFront; assert(a.front.front == iota([3], 1, 20)); } +/// Use byDim to calculate column mean/row mean of 2-dimensional slice +version(mir_ndslice_test) +@safe pure +unittest +{ + import mir.ndslice.topology: byDim; + import mir.ndslice.fuse: fuse; + import mir.math.stat: mean; + import mir.algorithm.iteration: all; + import mir.math.common: approxEqual; + + auto x = [ + [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], + [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] + ].fuse; + + // Use byDim with map to compute mean of row/column. + assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); + assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); + + // FIXME + // Without using map, computes the mean of the whole slice + // assert(x.byDim!0.mean == x.sliced.mean); + // assert(x.byDim!1.mean == x.sliced.mean); +} + +/++ +Use byDim and map with a lambda, but may need to allocate result. This example +uses fuse, which allocates. Note: fuse!1 will transpose the result. ++/ +version(mir_ndslice_test) +@safe pure +unittest { + import mir.ndslice.topology: iota, byDim, map; + import mir.ndslice.fuse: fuse; + import mir.ndslice.slice: sliced; + + auto x = [1, 2, 3].sliced; + auto y = [1, 2].sliced; + + auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; + assert(s1 == [[ 0, 2, 6], + [ 3, 8, 15]]); + auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1; + assert(s2 == [[ 0, 1, 2], + [ 6, 8, 10]]); +} + // Ensure works on canonical @safe @nogc pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota, canonical; // ------------ @@ -4162,6 +5497,8 @@ version(mir_test) unittest // | 8 9 10 11 | // ------------ auto x = slice.byDim!0; + static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); + assert(x.shape == shape3); assert(x.front.shape == shape4); assert(x.front == iota(4)); @@ -4175,6 +5512,8 @@ version(mir_test) unittest // | 3 7 11 | // --------- auto y = slice.byDim!1; + static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); + assert(y.shape == shape4); assert(y.front.shape == shape3); assert(y.front == iota([3], 0, 4)); @@ -4184,7 +5523,7 @@ version(mir_test) unittest // Ensure works on universal @safe @nogc pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota, universal; // ------------ @@ -4206,6 +5545,8 @@ version(mir_test) unittest // | 8 9 10 11 | // ------------ auto x = slice.byDim!0; + static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal))); + assert(x.shape == shape3); assert(x.front.shape == shape4); assert(x.front == iota(4)); @@ -4219,6 +5560,8 @@ version(mir_test) unittest // | 3 7 11 | // --------- auto y = slice.byDim!1; + static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal))); + assert(y.shape == shape4); assert(y.front.shape == shape3); assert(y.front == iota([3], 0, 4)); @@ -4228,7 +5571,7 @@ version(mir_test) unittest // 1-dimensional slice support @safe @nogc pure nothrow -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.topology : iota; // ------- @@ -4236,9 +5579,227 @@ version(mir_test) unittest // ------- auto slice = iota(3); auto x = slice.byDim!0; + static assert (is(typeof(x) == typeof(slice))); assert(x == slice); } +/++ +Constructs a new view of an n-dimensional slice with dimension `axis` removed. + +Throws: + `AssertError` if the length of the corresponding dimension doesn' equal 1. +Params: + axis = dimension to remove, if it is single-dimensional + slice = n-dimensional slice +Returns: + new view of a slice with dimension removed +See_also: $(LREF unsqueeze), $(LREF iota). ++/ +template squeeze(sizediff_t axis = 0) +{ + Slice!(Iterator, N - 1, kind != Canonical ? kind : ((axis == N - 1 || axis == -1) ? Universal : (N == 2 ? Contiguous : kind))) + squeeze(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice) + if (-sizediff_t(N) <= axis && axis < sizediff_t(N) && N > 1) + in { + assert(slice._lengths[axis < 0 ? N + axis : axis] == 1); + } + do { + import mir.utility: swap; + enum sizediff_t a = axis < 0 ? N + axis : axis; + typeof(return) ret; + foreach (i; Iota!(0, a)) + ret._lengths[i] = slice._lengths[i]; + foreach (i; Iota!(a + 1, N)) + ret._lengths[i - 1] = slice._lengths[i]; + static if (kind == Universal) + { + foreach (i; Iota!(0, a)) + ret._strides[i] = slice._strides[i]; + foreach (i; Iota!(a + 1, N)) + ret._strides[i - 1] = slice._strides[i]; + } + else + static if (kind == Canonical) + { + static if (a == N - 1) + { + foreach (i; Iota!(0, N - 1)) + ret._strides[i] = slice._strides[i]; + } + else + { + foreach (i; Iota!(0, a)) + ret._strides[i] = slice._strides[i]; + foreach (i; Iota!(a + 1, N - 1)) + ret._strides[i - 1] = slice._strides[i]; + } + } + swap(ret._iterator, slice._iterator); + return ret; + } +} + +/// +version(mir_ndslice_test) +unittest +{ + import mir.ndslice.topology : iota; + import mir.ndslice.allocation : slice; + + // [[0, 1, 2]] -> [0, 1, 2] + assert([1, 3].iota.squeeze == [3].iota); + // [[0], [1], [2]] -> [0, 1, 2] + assert([3, 1].iota.squeeze!1 == [3].iota); + assert([3, 1].iota.squeeze!(-1) == [3].iota); + + assert([1, 3].iota.canonical.squeeze == [3].iota); + assert([3, 1].iota.canonical.squeeze!1 == [3].iota); + assert([3, 1].iota.canonical.squeeze!(-1) == [3].iota); + + assert([1, 3].iota.universal.squeeze == [3].iota); + assert([3, 1].iota.universal.squeeze!1 == [3].iota); + assert([3, 1].iota.universal.squeeze!(-1) == [3].iota); + + assert([1, 3, 4].iota.squeeze == [3, 4].iota); + assert([3, 1, 4].iota.squeeze!1 == [3, 4].iota); + assert([3, 4, 1].iota.squeeze!(-1) == [3, 4].iota); + + assert([1, 3, 4].iota.canonical.squeeze == [3, 4].iota); + assert([3, 1, 4].iota.canonical.squeeze!1 == [3, 4].iota); + assert([3, 4, 1].iota.canonical.squeeze!(-1) == [3, 4].iota); + + assert([1, 3, 4].iota.universal.squeeze == [3, 4].iota); + assert([3, 1, 4].iota.universal.squeeze!1 == [3, 4].iota); + assert([3, 4, 1].iota.universal.squeeze!(-1) == [3, 4].iota); +} + +/++ +Constructs a view of an n-dimensional slice with a dimension added at `axis`. Used +to unsqueeze a squeezed slice. + +Params: + slice = n-dimensional slice + axis = dimension to be unsqueezed (add new dimension), default values is 0, the first dimension +Returns: + unsqueezed n+1-dimensional slice of the same slice kind +See_also: $(LREF squeeze), $(LREF iota). ++/ +Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice, sizediff_t axis) +in { + assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N)); +} +do { + import mir.utility: swap; + typeof(return) ret; + auto a = axis < 0 ? axis + N + 1 : axis; + foreach (i; 0 .. a) + ret._lengths[i] = slice._lengths[i]; + ret._lengths[a] = 1; + foreach (i; a .. N) + ret._lengths[i + 1] = slice._lengths[i]; + static if (kind == Universal) + { + foreach (i; 0 .. a) + ret._strides[i] = slice._strides[i]; + foreach (i; a .. N) + ret._strides[i + 1] = slice._strides[i]; + } + else + static if (kind == Canonical) + { + if (a == N) + { + foreach (i; Iota!(0, N - 1)) + ret._strides[i] = slice._strides[i]; + ret._strides[N - 1] = 1; + } + else + { + foreach (i; 0 .. a) + ret._strides[i] = slice._strides[i]; + foreach (i; a .. N - 1) + ret._strides[i + 1] = slice._strides[i]; + } + } + swap(ret._iterator, slice._iterator); + return ret; +} + +/// ditto +template unsqueeze(sizediff_t axis = 0) +{ + Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind) + (Slice!(Iterator, N, kind) slice) + in { + assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N)); + } + do { + import mir.utility: swap; + typeof(return) ret; + enum a = axis < 0 ? axis + N + 1 : axis; + foreach (i; Iota!a) + ret._lengths[i] = slice._lengths[i]; + ret._lengths[a] = 1; + foreach (i; Iota!(a, N)) + ret._lengths[i + 1] = slice._lengths[i]; + static if (kind == Universal) + { + foreach (i; Iota!a) + ret._strides[i] = slice._strides[i]; + foreach (i; Iota!(a, N)) + ret._strides[i + 1] = slice._strides[i]; + } + else + static if (kind == Canonical) + { + static if (a == N) + { + foreach (i; Iota!(0, N - 1)) + ret._strides[i] = slice._strides[i]; + ret._strides[N - 1] = 1; + } + else + { + foreach (i; Iota!(0, a)) + ret._strides[i] = slice._strides[i]; + foreach (i; Iota!(a, N - 1)) + ret._strides[i + 1] = slice._strides[i]; + } + } + swap(ret._iterator, slice._iterator); + return ret; + } +} + +/// +version (mir_ndslice_test) +@safe pure nothrow @nogc +unittest +{ + // [0, 1, 2] -> [[0, 1, 2]] + assert([3].iota.unsqueeze == [1, 3].iota); + + assert([3].iota.universal.unsqueeze == [1, 3].iota); + assert([3, 4].iota.unsqueeze == [1, 3, 4].iota); + assert([3, 4].iota.canonical.unsqueeze == [1, 3, 4].iota); + assert([3, 4].iota.universal.unsqueeze == [1, 3, 4].iota); + + // [0, 1, 2] -> [[0], [1], [2]] + assert([3].iota.unsqueeze(-1) == [3, 1].iota); + assert([3].iota.unsqueeze!(-1) == [3, 1].iota); + + assert([3].iota.universal.unsqueeze(-1) == [3, 1].iota); + assert([3].iota.universal.unsqueeze!(-1) == [3, 1].iota); + assert([3, 4].iota.unsqueeze(-1) == [3, 4, 1].iota); + assert([3, 4].iota.unsqueeze!(-1) == [3, 4, 1].iota); + assert([3, 4].iota.canonical.unsqueeze(-1) == [3, 4, 1].iota); + assert([3, 4].iota.canonical.unsqueeze!(-1) == [3, 4, 1].iota); + assert([3, 4].iota.universal.unsqueeze(-1) == [3, 4, 1].iota); + assert([3, 4].iota.universal.unsqueeze!(-1) == [3, 4, 1].iota); +} + /++ Field (element's member) projection. @@ -4279,7 +5840,7 @@ template member(string name) } /// -version(mir_test) +version(mir_ndslice_test) @safe pure unittest { // struct, union or class @@ -4324,7 +5885,7 @@ template orthogonalReduceField(alias fun) import mir.functional: naryFun; static if (__traits(isSame, naryFun!fun, fun)) { - @optmath: + @fmamath: /++ Params: slice = Non empty input slice composed of fields or iterators. @@ -4353,7 +5914,7 @@ template orthogonalReduceField(alias fun) } /// bit array operations -version(mir_test) +version(mir_ndslice_test) unittest { import mir.ndslice.slice: slicedField; @@ -4427,8 +5988,9 @@ auto triplets(string type, S)(S slice, size_t n) } /// -version(mir_test) unittest +version(mir_ndslice_test) unittest { + import mir.ndslice.slice: sliced; import mir.ndslice.topology: triplets, member, iota; auto a = [4, 5, 2, 8]; diff --git a/source/mir/ndslice/traits.d b/source/mir/ndslice/traits.d index b9b7c14a..087efb6f 100644 --- a/source/mir/ndslice/traits.d +++ b/source/mir/ndslice/traits.d @@ -19,9 +19,9 @@ $(T2 isUniversalMatrix, Test if type is a universal two-dimensional slice.) $(T2 isIterator, Test if type is a random access iterator.) ) -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright $(COPYRIGHT) 2016-, Ilya Yaroshenko, John Hall -Authors: John Hall +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: John Hall Macros: @@ -65,7 +65,7 @@ enum bool isUniversalMatrix(T) = is(T : Slice!(Iterator, 2, Universal), Iterato /// @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { import mir.ndslice.slice : Slice; @@ -86,7 +86,7 @@ version(mir_test) unittest } @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { alias S2 = Slice!(float*, 1, Universal); static assert(!isContiguousVector!S2); @@ -105,7 +105,7 @@ version(mir_test) unittest } @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { alias S3 = Slice!(byte*, 2); static assert(!isContiguousVector!S3); @@ -124,7 +124,7 @@ version(mir_test) unittest } @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { alias S4 = Slice!(int*, 2, Canonical); static assert(!isContiguousVector!S4); @@ -143,7 +143,7 @@ version(mir_test) unittest } @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { alias S5 = Slice!(int*, 2, Universal); static assert(!isContiguousVector!S5); @@ -162,7 +162,7 @@ version(mir_test) unittest } @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { alias S6 = Slice!(int*, 3); @@ -182,7 +182,7 @@ version(mir_test) unittest } @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { alias S7 = Slice!(int*, 3, Canonical); @@ -202,7 +202,7 @@ version(mir_test) unittest } @safe pure nothrow @nogc -version(mir_test) unittest +version(mir_ndslice_test) unittest { alias S8 = Slice!(int*, 3, Universal); diff --git a/source/mir/numeric.d b/source/mir/numeric.d index a90d0e07..436b14f7 100644 --- a/source/mir/numeric.d +++ b/source/mir/numeric.d @@ -3,18 +3,19 @@ Base numeric algorithms. Reworked part of `std.numeric`. -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Authors: Ilya Yaroshenko (API, findLocalMin, findRoot extension), Don Clugston (findRoot) +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki (API, findLocalMin, findRoot extension), Don Clugston (findRoot), Lars Tandle Kyllingstad (diff) +/ module mir.numeric; import mir.internal.utility: isFloatingPoint; import mir.math.common; import mir.math.ieee; +import mir.exception: toMutable; version(D_Exceptions) { - private static immutable findRoot_badBounds = new Exception("findRoot/findLocalMin: f(ax) and f(bx) must have opposite signs to bracket the root."); + private static immutable findRoot_badBounds = new Exception("findRoot: f(ax) and f(bx) must have opposite signs to bracket the root."); private static immutable findRoot_nanX = new Exception("findRoot/findLocalMin: ax or bx is NaN."); private static immutable findRoot_nanY = new Exception("findRoot/findLocalMin: f(x) returned NaN."); } @@ -64,9 +65,9 @@ struct mir_find_root_result(T) with(FindRootStatus) final switch(status) { case success: return this; - case badBounds: throw findRoot_badBounds; - case nanX: throw findRoot_nanX; - case nanY: throw findRoot_nanY; + case badBounds: throw findRoot_badBounds.toMutable; + case nanX: throw findRoot_nanX.toMutable; + case nanY: throw findRoot_nanY.toMutable; } } @@ -199,7 +200,7 @@ FindRootResult!T findRoot(alias f, alias tolerance = null, T)( // @nogc version(mir_test) @safe unittest { - import mir.math.common: log, exp; + import mir.math.common: log, exp, fabs; auto logRoot = findRoot!log(0, double.infinity).validate.x; assert(logRoot == 1); @@ -244,6 +245,7 @@ version(mir_test) @safe unittest } /// ditto +version(mir_test) unittest { import core.stdc.tgmath: atan; @@ -311,6 +313,7 @@ With adaptive bounds and single initial point. Reverse outer bound order controls first step direction in case of `f(a) == f(b)`. +/ +version(mir_test) unittest { enum root = 1.0; @@ -413,7 +416,7 @@ private @fmamath FindRootResult!T findRootImplGen(T)( // (www.netlib.org). The changes to improve the worst-cast performance are // entirely original. - // Author: Ilya Yaroshenko (Bounds extension logic, + // Author: Ilia Ki (Bounds extension logic, // API improvements, infinity and huge numbers handing, compiled code size reduction) T d; // [a .. b] is our current bracket. d is the third best guess. @@ -502,8 +505,8 @@ private @fmamath FindRootResult!T findRootImplGen(T)( { // Find the coefficients of the quadratic polynomial. const T a0 = fa; - const T a1 = (fb - fa)/(b - a); - const T a2 = ((fd - fb)/(d - b) - a1)/(d - a); + const T a1 = (fb - fa) / (b - a); + const T a2 = ((fd - fb) / (d - b) - a1) / (d - a); // Determine the starting point of newton steps. T c = a2.signbit != fa.signbit ? a : b; @@ -950,7 +953,7 @@ version(mir_test) @safe unittest return pow(x, n) + double.min_normal; } int [] power_nvals = [3, 5, 7, 9, 19, 25]; - // Alefeld paper states that pow(x,n) is a very poor case, where bisection + // Alefeld paper states that pow(x, n) is a very poor case, where bisection // outperforms his method, and gives total numcalls = // 921 for bisection (2.4 calls per bit), 1830 for Alefeld (4.76/bit), // 0.5f624 for brent (6.8/bit) @@ -977,7 +980,7 @@ version(mir_test) @safe unittest ++numCalls; real q = sin(x) - x/2; for (int i=1; i<20; ++i) - q+=(2*i-5.0)*(2*i-5.0)/((x-i*i)*(x-i*i)*(x-i*i)); + q+=(2*i-5.0)*(2*i-5.0) / ((x-i*i)*(x-i*i)*(x-i*i)); return q; } real alefeld1(real x) @@ -1020,7 +1023,7 @@ version(mir_test) @safe unittest { ++numCalls; ++alefeldSums[7]; - return (n*x-1)/((n-1)*x); + return (n*x-1) / ((n-1)*x); } numProblems=0; @@ -1126,9 +1129,9 @@ struct FindLocalMinResult(T) with(FindRootStatus) final switch(status) { case success: return this; - case badBounds: throw findRoot_badBounds; - case nanX: throw findRoot_nanX; - case nanY: throw findRoot_nanY; + case badBounds: throw findRoot_badBounds.toMutable; + case nanX: throw findRoot_nanX.toMutable; + case nanY: throw findRoot_nanY.toMutable; } } @@ -1160,6 +1163,11 @@ Params: bx = Right bound of initial range of f known to contain the minimum. relTolerance = Relative tolerance. absTolerance = Absolute tolerance. + N = number of addition inner points of uniform grid. + The algorithm computes function value for the N points. + Then reassing ax to the point that preceeds the grid's argmin, + reassing bx to the point that follows the the grid's argmin. + The search interval reduces (N + 1) / 2 times. Preconditions: `ax` and `bx` shall be finite reals. $(BR) `relTolerance` shall be normal positive real. $(BR) @@ -1177,7 +1185,9 @@ FindLocalMinResult!T findLocalMin(alias f, T)( const T ax, const T bx, const T relTolerance = sqrt(T.epsilon), - const T absTolerance = sqrt(T.epsilon)) + const T absTolerance = sqrt(T.epsilon), + size_t N = 0, +) if (isFloatingPoint!T && __traits(compiles, T(f(T.init)))) { if (false) // break attributes @@ -1189,7 +1199,7 @@ FindLocalMinResult!T findLocalMin(alias f, T)( }; scope fun = funInst.trustedAllAttr; - return findLocalMinImpl(ax, bx, relTolerance, absTolerance, fun); + return findLocalMinImpl(ax, bx, relTolerance, absTolerance, fun, N); } @fmamath @@ -1199,10 +1209,11 @@ private FindLocalMinResult!float findLocalMinImpl( const float relTolerance, const float absTolerance, scope const float delegate(float) @safe pure nothrow @nogc f, + size_t N, ) @safe pure nothrow @nogc { pragma(inline, false); - return findLocalMinImplGen!float(ax, bx, relTolerance, absTolerance, f); + return findLocalMinImplGen!float(ax, bx, relTolerance, absTolerance, f, N); } @fmamath @@ -1212,10 +1223,11 @@ private FindLocalMinResult!double findLocalMinImpl( const double relTolerance, const double absTolerance, scope const double delegate(double) @safe pure nothrow @nogc f, + size_t N, ) @safe pure nothrow @nogc { pragma(inline, false); - return findLocalMinImplGen!double(ax, bx, relTolerance, absTolerance, f); + return findLocalMinImplGen!double(ax, bx, relTolerance, absTolerance, f, N); } @fmamath @@ -1225,19 +1237,21 @@ private FindLocalMinResult!real findLocalMinImpl( const real relTolerance, const real absTolerance, scope const real delegate(real) @safe pure nothrow @nogc f, + size_t N, ) @safe pure nothrow @nogc { pragma(inline, false); - return findLocalMinImplGen!real(ax, bx, relTolerance, absTolerance, f); + return findLocalMinImplGen!real(ax, bx, relTolerance, absTolerance, f, N); } @fmamath private FindLocalMinResult!T findLocalMinImplGen(T)( - const T ax, - const T bx, + T ax, + T bx, const T relTolerance, const T absTolerance, scope const T delegate(T) @safe pure nothrow @nogc f, + size_t N, ) if (isFloatingPoint!T && __traits(compiles, {T _ = f(T.init);})) in @@ -1249,6 +1263,26 @@ in } do { + if (N > 1) + { + import mir.ndslice.topology: linspace; + + auto xPoints = linspace!T([N + 2], [ax, bx]); + size_t idx; + double value = double.infinity; + foreach (i; 0 .. N) + { + auto y = f(xPoints[i + 1]); + if (y < value) + { + value = y; + idx = i; + } + } + ax = xPoints[idx + 0]; + bx = xPoints[idx + 2]; + } + version(LDC) pragma(inline, true); // c is the squared inverse of the golden ratio // (3 - sqrt(5))/2 @@ -1280,7 +1314,7 @@ do i++; T m = (a + b) * 0.5f; // This fix is not part of the original algorithm - if (!((m.fabs < T.infinity))) // fix infinity loop. Issue can be reproduced in R. + if (!(m.fabs < T.infinity)) // fix infinity loop. Issue can be reproduced in R. { m = a.half + b.half; } @@ -1376,7 +1410,7 @@ version(mir_test) @safe unittest /// version(mir_test) @safe unittest { - import mir.math.common: approxEqual; + import mir.math.common: approxEqual, log, fabs; alias AliasSeq(T...) = T; static foreach (T; AliasSeq!(double, float, real)) { @@ -1415,6 +1449,248 @@ version(mir_test) @safe unittest } } +/// Tries to find a global minimum using uniform grid to reduce the search interval +version(mir_test) +unittest +{ + import mir.math.common: cos; + import mir.test; + + alias f = x => cos(x) + x / 10; + + findLocalMin!f(-4.0, 6.0, double.epsilon, 2 * double.epsilon) + .validate.x.shouldApprox == 3.041425233955413; + // Use 10 inner points on uniform grid to reduce the interval + // reduces the interval of search 5.5 = (10 + 1) / 2 times + findLocalMin!f(-4.0, 6.0, double.epsilon, 2 * double.epsilon, 10) + .validate.x.shouldApprox == -3.2417600767368255; +} + +/++ +A set of one or two smile roots. + +Because we assume that volatility smile is convex the equantion above can have no more then two roots. + +The `left` and `right` members are equal if the smile has only one root. ++/ +struct SmileRoots(T) + if (__traits(isFloating, T)) +{ + import mir.small_array: SmallArray; + + /// + T left; + /// + T right; + +@safe pure nothrow @nogc: + + /// + this(T value) + { + left = right = value; + } + + /// + this(T left, T right) + { + this.left = left; + this.right = right; + } + + /// + this(SmallArray!(T, 2) array) + { + if (array.length == 2) + this(array[0], array[1]); + else + if (array.length == 1) + this(array[0]); + else + this(T.nan, T.nan); + } + + /// + inout(T)[] opIndex() @trusted inout + { + return (&left)[0 .. 2]; + } + + /// + size_t count() const + { + return 1 + (left != right); + } +} + +/++ ++/ +struct FindSmileRootsResult(T) + if (__traits(isFloating, T)) +{ + import mir.algebraic: Nullable; + + /++ + Left result if any + +/ + Nullable!(FindRootResult!T) leftResult; + /++ + Right result if any + +/ + Nullable!(FindRootResult!T) rightResult; + /++ + +/ + Nullable!(FindLocalMinResult!T) localMinResult; + +@safe pure @nogc scope const @property: + + /++ + Returns: $(LREF mir_find_root_status) + +/ + FindRootStatus status() + { + if (leftResult && leftResult.get.status) + return leftResult.get.status; + if (rightResult && rightResult.get.status) + return rightResult.get.status; + if (!leftResult && !rightResult) + return FindRootStatus.badBounds; + return FindRootStatus.success; + } + + /// + version(D_Exceptions) + ref validate() inout return + { + with(FindRootStatus) final switch(status) + { + case success: return this; + case badBounds: throw findRoot_badBounds.toMutable; + case nanX: throw findRoot_nanX.toMutable; + case nanY: throw findRoot_nanY.toMutable; + } + } + + /++ + Returns: `SmallArray!(T, 2)` of roots if any. + +/ + auto roots()() + { + import mir.small_array; + SmallArray!(T, 2) ret; + if (leftResult) + ret.append(leftResult.get.x); + if (rightResult) + ret.append(rightResult.get.x); + return ret; + } + + /++ + Returns: $(LREF SmileRoots). + +/ + SmileRoots!double smileRoots()() + { + return typeof(return)(roots); + } +} + +/++ +Find one or two roots of a real function f(x) using combination of $(LREF FindRoot) and $(LREF FindLocalMin). + +Params: + f = Function to be analyzed. If `f(ax)` and `f(bx)` have opposite signs one root is returned, + otherwise the implementation tries to find a local minimum and returns two roots. + At least one of `f(ax)` and `f(bx)` should be greater or equal to zero. + tolerance = Defines an early termination condition. Receives the + current upper and lower bounds on the root. The delegate must return `true` when these bounds are + acceptable. If this function always returns `false` or it is null, full machine precision will be achieved. + ax = Left inner bound of initial range of `f` known to contain the roots. + bx = Right inner bound of initial range of `f` known to contain the roots. Can be equal to `ax`. + fax = Value of `f(ax)` (optional). + fbx = Value of `f(bx)` (optional). + relTolerance = Relative tolerance used by $(LREF findLocalMin). + absTolerance = Absolute tolerance used by $(LREF findLocalMin). + lowerBound = + upperBound = + maxIterations = Appr. maximum allowed number of function calls for each $(LREF findRoot) call. + steps = + +Returns: $(LREF FindSmileRootsResult) ++/ +FindSmileRootsResult!T findSmileRoots(alias f, alias tolerance = null, T)( + const T ax, + const T bx, + const T fax = T.nan, + const T fbx = T.nan, + const T relTolerance = sqrt(T.epsilon), + const T absTolerance = sqrt(T.epsilon), + const T lowerBound = T.nan, + const T upperBound = T.nan, + uint maxIterations = T.sizeof * 16, + uint steps = 0, + ) +if (__traits(isFloating, T)) +{ + FindSmileRootsResult!T ret; + auto res = findRoot!(f, tolerance)(ax, bx, fax, fbx, T.nan, T.nan, maxIterations); + if (res.status == FindRootStatus.success) + { + (res.bx.signbit ? ret.leftResult : ret.rightResult) = res; + } + else + if (res.status == FindRootStatus.badBounds) + { + ret.localMinResult = findLocalMin!f(ax, bx, relTolerance, absTolerance); + ret.leftResult = findRoot!(f, tolerance)(ax, ret.localMinResult.x, T.nan, ret.localMinResult.y, T.nan, T.nan, maxIterations); + ret.rightResult = findRoot!(f, tolerance)(ret.localMinResult.x, bx, ret.localMinResult.y, T.nan, T.nan, T.nan, maxIterations); + } + return ret; +} + +/// Smile +version(mir_test) +unittest +{ + import mir.math.common: approxEqual; + auto result = findSmileRoots!(x => x ^^ 2 - 1)(-10.0, 10.0); + assert(result.roots.length == 2); + assert(result.roots[0].approxEqual(-1)); + assert(result.roots[1].approxEqual(+1)); + assert(result.smileRoots.left.approxEqual(-1)); + assert(result.smileRoots.right.approxEqual(+1)); + assert(result.leftResult); + assert(result.rightResult); + assert(result.localMinResult); + assert(result.localMinResult.get.x.approxEqual(0)); + assert(result.localMinResult.get.y.approxEqual(-1)); +} + +/// Skew +version(mir_test) +unittest +{ + import mir.math.common: approxEqual; + auto result = findSmileRoots!(x => x ^^ 2 - 1)(0.5, 10.0); + assert(result.roots.length == 1); + assert(result.roots[0].approxEqual(+1)); + assert(result.smileRoots.left.approxEqual(+1)); + assert(result.smileRoots.right.approxEqual(+1)); + assert(!result.leftResult); + assert(result.rightResult); +} + +version(mir_test) +unittest +{ + auto result = findSmileRoots!(x => x ^^ 2 - 1)(-10.0, -0.5); + assert(result.roots.length == 1); + assert(result.roots[0].approxEqual(-1)); + assert(result.smileRoots.left.approxEqual(-1)); + assert(result.smileRoots.right.approxEqual(-1)); + assert(result.leftResult); + assert(!result.rightResult); +} + // force disabled FMA math private static auto half(T)(const T x) { @@ -1432,3 +1708,374 @@ private auto trustedAllAttr(T)(T t) @trusted | FunctionAttribute.nothrow_; return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; } + +/++ +Calculate the derivative of a function. +This function uses Ridders' method of extrapolating the results +of finite difference formulas for consecutively smaller step sizes, +with an improved stopping criterion described in the Numerical Recipes +books by Press et al. + +This method gives a much higher degree of accuracy in the answer +compared to a single finite difference calculation, but requires +more function evaluations; typically 6 to 12. The maximum number +of function evaluations is $(D 24). + +Params: + f = The function of which to take the derivative. + x = The point at which to take the derivative. + h = A "characteristic scale" over which the function changes. + factor = Stepsize is decreased by factor at each iteration. + safe = Return when error is SAFE worse than the best so far. + +References: +$(UL + $(LI + C. J. F. Ridders, + $(I Accurate computation of F'(x) and F'(x)F''(x)). + Advances in Engineering Software, vol. 4 (1982), issue 2, p. 75.) + $(LI + W. H. Press, S. A. Teukolsky, W. T. Vetterling, and B. P. Flannery, + $(I Numerical Recipes in C++) (2nd ed.). + Cambridge University Press, 2003.) +) ++/ +@fmamath +DiffResult!T diff(alias f, T)(const T x, const T h, const T factor = T(2).sqrt, const T safe = 2) +{ + if (false) // break attributes + T y = f(T(1)); + scope funInst = delegate(T x) { + return T(f(x)); + }; + scope fun = funInst.trustedAllAttr; + return diffImpl(fun, x, h, factor, safe); +} + +///ditto +DiffResult!T diffImpl(T) + (scope const T delegate(T) @safe pure nothrow @nogc f, const T x, const T h, const T factor = T(2).sqrt, const T safe = 2) + @trusted pure nothrow @nogc +in { + assert(h < T.max); + assert(h > T.min_normal); +} +do { + // Set up Romberg tableau. + import mir.ndslice.slice: sliced; + pragma(inline, false); + + enum tableauSize = 16; + T[tableauSize ^^ 2] workspace = void; + auto tab = workspace[].sliced(tableauSize, tableauSize); + + // From the NR book: Stop when the difference between consecutive + // approximations is bigger than SAFE*error, where error is an + // estimate of the absolute error in the current (best) approximation. + + // First approximation: A_0 + T result = void; + T error = T.max; + T hh = h; + + tab[0,0] = (f(x + h) - f(x - h)) / (2 * h); + foreach (n; 1 .. tableauSize) + { + // Decrease h. + hh /= factor; + + // Compute A_n + tab[0, n] = (f(x + hh) - f(x - hh)) / (2 * hh); + + T facm = 1; + foreach (m; 1 .. n + 1) + { + facm *= factor ^^ 2; + + // Compute B_(n-1), C_(n-2), ... + T upLeft = tab[m - 1, n - 1]; + T up = tab[m - 1, n]; + T current = (facm * up - upLeft) / (facm - 1); + tab[m, n] = current; + + // Calculate and check error. + T currentError = fmax(fabs(current - upLeft), fabs(current - up)); + if (currentError <= error) + { + result = current; + error = currentError; + } + } + + if (fabs(tab[n, n] - tab[n - 1, n - 1]) >= safe * error) + break; + } + + return typeof(return)(result, error); +} + +/// +version(mir_test) +unittest +{ + import mir.math.common; + + auto f(double x) { return exp(x) / (sin(x) - x ^^ 2); } + auto d(double x) { return -(exp(x) * ((x - 2) * x - sin(x) + cos(x)))/(x^^2 - sin(x))^^2; } + auto r = diff!f(1.0, 0.01); + assert (approxEqual(r.value, d(1))); +} + +/++ ++/ +struct DiffResult(T) + if (__traits(isFloating, T)) +{ + /// + T value = 0; + /// + T error = 0; +} + +/++ +Integrates function on the interval `[a, b]` using adaptive Gauss-Lobatto algorithm. + +References: + W. Gander and W. Gautschi. Adaptive Quadrature — Revisited + +Params: + f = function to integrate. `f` should be valid on interval `[a, b]` including the bounds. + a = finite left interval bound + b = finite right interval bound + tolerance = (optional) relative tolerance should be greater or equal to `T.epsilon` + +Returns: + Integral value ++/ +T integrate(alias f, T)(const T a, const T b, const T tolerance = T.epsilon) + if (__traits(isFloating, T)) +{ + if (false) // break attributes + T y = f(T(1)); + scope funInst = delegate(T x) { + return T(f(x)); + }; + scope fun = funInst.trustedAllAttr; + return integrateImpl(fun, a, b, tolerance); +} + +/// ditto +@fmamath +T integrateImpl(T)( + scope const T delegate(T) @safe pure nothrow @nogc f, + const T a, + const T b, + const T tolerance = T.epsilon, +) @safe pure nothrow @nogc + if (__traits(isFloating, T)) +in { + assert(-T.infinity < a); + assert(b < +T.infinity); + assert(a < b); + assert(tolerance >= T.epsilon); +} +do { + pragma(inline, false); + enum T alpha = 0.816496580927726032732428024901963797322; + enum T beta = 0.447213595499957939281834733746255247088; + enum T x1 = 0.94288241569947971905635175843185720232; + enum T x2 = 0.64185334234978130978123554132903188394; + enum T x3 = 0.23638319966214988028222377349205292599; + enum T A = 0.015827191973480183087169986733305510591; + enum T B = 0.094273840218850045531282505077108171960; + enum T C = 0.19507198733658539625363597980210298680; + enum T D = 0.18882157396018245442000533937297167125; + enum T E = 0.19977340522685852679206802206648840246; + enum T F = 0.22492646533333952701601768799639508076; + enum T G = 0.24261107190140773379964095790325635233; + enum T A1 = 77 / 1470.0L; + enum T B1 = 432 / 1470.0L; + enum T C1 = 625 / 1470.0L; + enum T D1 = 672 / 1470.0L; + enum T A2 = 1 / 6.0L; + enum T B2 = 5 / 6.0L; + + T m = (a + b) * 0.5f; + // This fix is not part of the original algorithm + if (!(m.fabs < T.infinity)) + { + m = a.half + b.half; + } + T h = (b - a) * 0.5f; + // This fix is not part of the original algorithm + if (!(h.fabs < T.infinity)) + { + h = b.half - a.half; + } + T[13] x = [ + a, + m - x1 * h, + m - alpha * h, + m - x2 * h, + m - beta * h, + m - x3 * h, + m, + m + x3 * h, + m + beta * h, + m + x2 * h, + m + alpha * h, + m + x1 * h, + b, + ]; + T[13] y = [ + f(x[ 0]), + f(x[ 1]), + f(x[ 2]), + f(x[ 3]), + f(x[ 4]), + f(x[ 5]), + f(x[ 6]), + f(x[ 7]), + f(x[ 8]), + f(x[ 9]), + f(x[10]), + f(x[11]), + f(x[12]), + ]; + T i2 = h * ( + + A2 * (y[0] + y[12]) + + B2 * (y[4] + y[ 8]) + ); + T i1 = h * ( + + A1 * (y[0] + y[12]) + + B1 * (y[2] + y[10]) + + C1 * (y[4] + y[ 8]) + + D1 * (y[6] ) + ); + T si = h * ( + + A * (y[0] + y[12]) + + B * (y[1] + y[11]) + + C * (y[2] + y[10]) + + D * (y[3] + y[ 9]) + + E * (y[4] + y[ 8]) + + F * (y[5] + y[ 7]) + + G * (y[6] ) + ); + T erri1 = fabs(i1 - si); + T erri2 = fabs(i2 - si); + T R = erri1 / erri2; + T tol = tolerance; + if (tol < T.epsilon) + tol = T.epsilon; + if (0 < R && R < 1) + tol /= R; + si *= tol / T.epsilon; + if (si == 0) + si = b - a; + + if (!(si.fabs < T.infinity)) + return T.nan; + + T step(const T a, const T b, const T fa, const T fb) + { + T m = (a + b) * 0.5f; + // This fix is not part of the original algorithm + if (!(m.fabs < T.infinity)) + { + m = a.half + b.half; + } + T h = (b - a) * 0.5f; + if (!(h.fabs < T.infinity)) + { + h = b.half - a.half; + } + T[5] x = [ + m - alpha * h, + m - beta * h, + m, + m + beta * h, + m + alpha * h, + ]; + T[5] y = [ + f(x[0]), + f(x[1]), + f(x[2]), + f(x[3]), + f(x[4]), + ]; + T i2 = h * ( + + A2 * (fa + fb) + + B2 * (y[1] + y[3]) + ); + T i1 = h * ( + + A1 * (fa + fb) + + B1 * (y[0] + y[4]) + + C1 * (y[1] + y[3]) + + D1 * y[2] + ); + auto sic = si + (i1 - i2); + if (!(i1.fabs < T.infinity) || sic == si || !(a < x[0]) || !(x[4] < b)) + { + return i1; + } + return + + (step( a, x[0], fa, y[0]) + + step(x[0], x[1], y[0], y[1])) + + (step(x[1], x[2], y[1], y[2]) + + step(x[2], x[3], y[2], y[3])) + + (step(x[3], x[4], y[3], y[4]) + + step(x[4], b, y[4], fb)); + } + + foreach (i; 0 .. 12) + x[i] = step(x[i], x[i + 1], y[i], y[i + 1]); + + return + + ((x[ 0] + + x[ 1]) + + (x[ 2] + + x[ 3])) + + ((x[ 4] + + x[ 5]) + + (x[ 6] + + x[ 7])) + + ((x[ 8] + + x[ 9]) + + (x[10] + + x[11])); +} + +/// +version(mir_test) +unittest +{ + import mir.math.common; + import mir.math.constant; + + alias cosh = x => 0.5 * (exp(x) + exp(-x)); + enum Pi = double(PI); + + assert(integrate!exp(0.0, 1.0).approxEqual(double(E - 1))); + assert(integrate!(x => x >= 3)(0.0, 10.0).approxEqual(7.0)); + assert(integrate!sqrt(0.0, 1.0).approxEqual(2.0 / 3)); + assert(integrate!(x => 23.0 / 25 * cosh(x) - cos(x))(-1.0, 1.0).approxEqual(0.479428226688801667)); + assert(integrate!(x => 1 / (x ^^ 4 + x ^^ 2 + 0.9))(-1.0, 1.0).approxEqual(1.5822329637294)); + assert(integrate!(x => sqrt(x ^^ 3))(0.0, 1.0).approxEqual(0.4)); + assert(integrate!(x => x ? 1 / sqrt(x) : 0)(0.0, 1.0).approxEqual(2)); + assert(integrate!(x => 1 / (1 + x ^^ 4))(0.0, 1.0).approxEqual(0.866972987339911)); + assert(integrate!(x => 2 / (2 + sin(10 * Pi * x)))(0.0, 1.0).approxEqual(1.1547005383793)); + assert(integrate!(x => 1 / (1 + x))(0.0, 1.0).approxEqual(0.6931471805599)); + assert(integrate!(x => 1 / (1 + exp(x)))(0.0, 1.0).approxEqual(0.3798854930417)); + assert(integrate!(x => exp(x) - 1 ? x / (exp(x) - 1) : 0)(0.0, 1.0).approxEqual(0.777504634112248)); + assert(integrate!(x => sin(100 * Pi * x) / (Pi * x))(0.1, 1.0).approxEqual(0.0090986375391668)); + assert(integrate!(x => sqrt(50.0) * exp(-50 * Pi * x ^^ 2))(0.0, 10.0).approxEqual(0.5)); + assert(integrate!(x => 25 * exp(-25 * x))(0.0, 10.0).approxEqual(1.0)); + assert(integrate!(x => 50 / Pi * (2500 * x ^^ 2 + 1))(0.0, 10.0).approxEqual(1.3263071079268e+7)); + assert(integrate!(x => 50 * (sin(50 * Pi * x) / (50 * Pi * x)) ^^ 2)(0.01, 1.0).approxEqual(0.11213930374164)); + assert(integrate!(x => cos(cos(x) + 3 * sin(x) + 2 * cos(2 * x) + 3 * sin(2 * x) + 3 * cos(3 * x)))(0.0, Pi).approxEqual(0.83867634269443)); + assert(integrate!(x => x > 1e-15 ? log(x) : 0)(0.0, 1.0).approxEqual(-1)); + assert(integrate!(x => 1 / (x ^^ 2 + 1.005))(-1.0, 1.0).approxEqual(1.5643964440690)); + assert(integrate!(x => 1 / cosh(20 * (x - 0.2)) + 1 / cosh(400 * (x - 0.04)) + 1 / cosh(8000 * (x - 0.008)))(0.0, 1.0).approxEqual(0.16349495585710)); + assert(integrate!(x => 4 * Pi ^^ 2 * x * sin(20 * Pi * x) * cos(2 * Pi * x))(0.0, 1.0).approxEqual(-0.6346651825434)); + assert(integrate!(x => 1 / (1 + (230 * x - 30) ^^ 2))(0.0, 1.0).approxEqual(0.013492485649468)); +} diff --git a/source/mir/parse.d b/source/mir/parse.d new file mode 100644 index 00000000..b870c213 --- /dev/null +++ b/source/mir/parse.d @@ -0,0 +1,503 @@ +/++ +$(H1 @nogc and nothrow Parsing Utilities) + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments ++/ +module mir.parse; + +/++ +Parsing position ++/ +struct ParsePosition +{ + /// + string file; + /// 0 is for unknown + uint line; + /// 0 is for unknown + uint column; + + /// + void toString(W)(scope ref W w) scope const + { + w.put(file); + if (line) + { + import mir.format: print; + w.put("("); + w.print(line); + if (column) + { + w.put(","); + w.print(column); + } + w.put(")"); + } + } +} + +/// +enum DecimalExponentKey +{ + /// + none = 0, + /// + infinity = 1, + /// + nan = 2, + /// + dot = '.' - '0', + /// + d = 'd' - '0', + /// + e = 'e' - '0', + /// + D = 'D' - '0', + /// + E = 'E' - '0', +} + +/// +struct DecimalExponentInfo +{ + /// + long exponent; + /// + DecimalExponentKey key; +} + +/// `mir.conv: to` extension. +version(mir_bignum_test) +@safe pure @nogc +unittest +{ + import mir.test: should; + import mir.conv: to; + + "123.0".to!double.should == 123; + "123".to!int.should == 123; + "123".to!byte.should == 123; + + import mir.small_string; + alias S = SmallString!32; + "123.0".SmallString!32.to!double.should == 123; +} + +import std.traits: isMutable, isFloatingPoint, isSomeChar, isSigned, isUnsigned, Unsigned; + +/++ +Performs `nothrow` and `@nogc` string to native type conversion. + +Returns: + parsed value +Throws: + `nogc` Exception in case of parse error or non-empty remaining input. + +Floating_point: + Mir parsing supports up-to quadruple precision. +The conversion error is 0 ULP for normal numbers. + Subnormal numbers with an exponent greater than or equal to -512 have upper error bound equal to 1 ULP.+/ +template fromString(T) + if (isMutable!T) +{ + /// + T fromString(C)(scope const(C)[] str) + if (isSomeChar!C) + { + import mir.utility: _expect; + static immutable excfp = new Exception("fromString failed to parse " ~ T.stringof); + + static if (isFloatingPoint!T) + { + T value; + if (_expect(.fromString(str, value), true)) + return value; + version (D_Exceptions) + { import mir.exception : toMutable; throw excfp.toMutable; } + else + assert(0); + } + else + { + static immutable excne = new Exception("fromString: remaining input is not empty after parsing " ~ T.stringof); + + T value; + if (_expect(parse!T(str, value), true)) + { + if (_expect(str.length == 0, true)) + return value; + version (D_Exceptions) + { import mir.exception : toMutable; throw excne.toMutable; } + else + assert(0); + } + else + { + version (D_Exceptions) + { import mir.exception : toMutable; throw excfp.toMutable; } + else + assert(0); + } + } + } +} + +version(unittest) +{ + import core.stdc.stdlib: strtof, strtod, strtold; + private auto _assumePure(T)(scope return T t) { + import std.traits; + enum attrs = functionAttributes!T | FunctionAttribute.pure_; + return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; + } + + private static @trusted float _stdc_parse(T : float)(string str){ auto endPtr = str.ptr + str.length; return _assumePure(&strtof)(str.ptr, &endPtr); } + private static @trusted double _stdc_parse(T : double)(string str){ auto endPtr = str.ptr + str.length; return _assumePure(&strtod)(str.ptr, &endPtr); } + private static @trusted real _stdc_parse(T : real)(string str){ auto endPtr = str.ptr + str.length; return _assumePure(&strtold)(str.ptr, &endPtr); } +} + +/// +version(mir_bignum_test) +@safe pure @nogc unittest +{ + import mir.test; + "123".fromString!int.should == 123; + + ".5".fromString!float.should == .5; + "12.3".fromString!double.should == 12.3; + "12.3".fromString!float.should == 12.3f; + "12.3".fromString!real.should == 12.3L; + "-12.3e-30".fromString!double.should == -12.3e-30; + "2.9802322387695312E-8".fromString!double.should == 2.9802322387695312E-8; + + // default support of underscores + "123_456.789_012".fromString!double.should == 123_456.789_012; + "12_34_56_78_90_12e-6".fromString!double.should == 123_456.789_012; + + // default support of leading zeros + "010".fromString!double.should == 10.0; + "000010".fromString!double.should == 10.0; + "0000.10".fromString!double.should == 0.1; + "0000e10".fromString!double.should == 0; + + version(all) {} else + version (TeslAlgoM) {} else + { + /// Test CTFE support + static assert("-123".fromString!int == -123); + + static assert("-12.3e-30".fromString!double == -0x1.f2f280b2414d5p-97); + static assert("+12.3e+30".fromString!double == 0x1.367ee3119d2bap+103); + + static assert("1.448997445238699".fromString!double == 0x1.72f17f1f49aadp0); + static if (real.mant_dig >= 64) + static assert("1.448997445238699".fromString!real == 1.448997445238699L); + + static assert("3.518437208883201171875".fromString!float == 0x1.c25c26p+1); + static assert("3.518437208883201171875".fromString!double == 0x1.c25c268497684p+1); + static if (real.mant_dig >= 64) + static assert("3.518437208883201171875".fromString!real == 0xe.12e13424bb4232fp-2L); + } + + void test(string str) + { + version(CRuntime_DigitalMars) // No precise parsing at all + { + } + else + { + str.fromString!float.should == str._stdc_parse!float; + str.fromString!double.should == str._stdc_parse!double; + version (Windows) // No precise real parsing on windows + { + } + else + str.fromString!real.should == str._stdc_parse!real; + } + } + + test("2.5e-324"); + + // large + test("1e300"); + test("123456789.34567e250"); + test("943794359898089732078308743689303290943794359843568973207830874368930329."); + + // min normal + test("2.2250738585072014e-308"); + + // subnormals + test("5e-324"); + test("91e-324"); + test("1e-322"); + test("13245643e-320"); + test("2.22507385851e-308"); + test("2.1e-308"); + test("4.9406564584124654e-324"); + + // infinity + test("1e400"); + test("1e309"); + test("2e308"); + test("1.7976931348624e308"); + + // zero + test("0.0"); + test("1e-325"); + test("1e-326"); + test("1e-500"); + + // Triggers the tricky underflow case in AlgorithmM (for f32) + test("101e-33"); + // Triggers AlgorithmR + test("1e23"); + // Triggers another path through AlgorithmR + test("2075e23"); + // ... and yet another. + test("8713e-23"); + + // 2^65 - 3, triggers half-to-even with even significand + test("36893488147419103229.0"); + test("36893488147419103229"); + + test("18446744073709551615."); + test("-18446744073709551615."); + test("18446744073709551616."); + test("-18446744073709551616."); + +// Related DMD Issues: +// https://issues.dlang.org/show_bug.cgi?id=20951 +// https://issues.dlang.org/show_bug.cgi?id=20952 +// https://issues.dlang.org/show_bug.cgi?id=20953 +// https://issues.dlang.org/show_bug.cgi?id=20967 +} + +version(mir_bignum_test) +@safe pure unittest +{ + import std.exception: assertThrown; + assertThrown("1_".fromString!float); + assertThrown("1__2".fromString!float); + assertThrown("_1".fromString!float); + assertThrown("123_.456".fromString!float); + assertThrown("123_e0".fromString!float); + assertThrown("123._456".fromString!float); + assertThrown("12__34.56".fromString!float); + assertThrown("123.456_".fromString!float); + assertThrown("-_123.456".fromString!float); + assertThrown("_123.456".fromString!float); +} + +/++ +Performs `nothrow` and `@nogc` string to native type conversion. + +Rseturns: true if success and false otherwise. ++/ +bool fromString(T, C)(scope const(C)[] str, ref T value) @trusted + if (isSomeChar!C && isFloatingPoint!T) +{ + import mir.bignum.decimal: Decimal, DecimalExponentKey; + import mir.utility: _expect; + + Decimal!128 decimal = void; + DecimalExponentKey key; + auto ret = decimal.fromStringImpl(str, key); + if (_expect(ret, true)) + { + value = cast(T) decimal; + } + return ret; +} + +/// ditto +bool fromString(T, C)(scope const(C)[] str, ref T value) + if (isSomeChar!C && !isFloatingPoint!T) +{ + return parse!T(str, value) && str.length == 0; +} + +/// +version(mir_test) +@safe pure nothrow @nogc unittest +{ + int value; + assert("123".fromString(value) && value == 123); +} + +/// +version(mir_test) +@safe pure nothrow @nogc unittest +{ + double value = 0; + assert("+Inf".fromString(value) && value == double.infinity); + assert("-nan".fromString(value) && value != value); +} + +/++ +Single character parsing utilities. + +Returns: true if success and false otherwise. ++/ +bool parse(T, C)(ref scope inout(C)[] str, ref scope T value) + if (isSomeChar!C && isSomeChar!T && T.sizeof == C.sizeof) +{ + if (str.length == 0) + return false; + value = str[0]; + str = str[1 .. $]; + return true; +} + +/// +version(mir_test) @safe pure nothrow @nogc +unittest +{ + auto s = "str"; + char c; + assert(parse(s, c)); + assert(c == 's'); + assert(s == "tr"); +} + +/++ +Integer parsing utilities. + +Returns: true if success and false otherwise. ++/ +bool parse(T, C)(ref scope inout(C)[] str, out scope T value) + if ((is(T == byte) || is(T == short)) && isSomeChar!C) +{ + int lvalue; + auto ret = .parse!(int, C)(str, lvalue); + value = cast(T) lvalue; + return ret && value == lvalue; +} + +bool parse(T, C)(ref scope inout(C)[] str, out scope T value) + if ((is(T == ubyte) || is(T == ushort)) && isSomeChar!C) +{ + uint lvalue; + auto ret = .parse!(uint, C)(str, lvalue); + value = cast(T) lvalue; + return ret && value == lvalue; +} + +/// +version (mir_test) unittest +{ + import mir.test: should; + import std.meta: AliasSeq; + foreach (T; AliasSeq!( + byte, ubyte, short, ushort, + int, uint, long, ulong)) + { + auto str = "123"; + T val; + assert(parse(str, val)); + val.should == 123; + str = "0"; + assert(parse(str, val)); + val.should == 0; + str = "9"; + assert(parse(str, val)); + val.should == 9; + str = ""; + assert(!parse(str, val)); + val.should == 0; + str = "text"; + assert(!parse(str, val)); + val.should == 0; + } +} + +/// +version (mir_test) unittest +{ + import mir.test: should; + import mir.conv: to; + import std.meta: AliasSeq; + foreach (T; AliasSeq!(byte, short, int, long)) + { + auto str = "-123"; + T val; + assert(parse(str, val)); + val.should == -123; + str = "-0"; + assert(parse(str, val)); + val.should == 0; + str = "-9text"; + assert(parse(str, val)); + val.should == -9; + assert(str == "text"); + enum m = T.min + 0; + str = m.to!string; + assert(parse(str, val)); + val.should == T.min; + } +} + +bool parse(T, C)(ref scope inout(C)[] str, scope out T value) + if ((isSigned!T || isUnsigned!T) && T.sizeof >= uint.sizeof && isSomeChar!C) +{ + version(LDC) pragma(inline, true); + import mir.checkedint: addu, mulu; + + if (str.length == 0) + return false; + + Unsigned!T x = str[0] - C('0'); + + static if (isSigned!T) + bool sign; + + if (x >= 10) + { + static if (isSigned!T) + { + if (x == C('-') - C('0')) + { + sign = true; + goto S; + } + } + + if (x != C('+') - C('0')) + return false; + S: + str = str[1 .. $]; + if (str.length == 0) + return false; + x = str[0] - C('0'); + if (x >= 10) + return false; + } + + str = str[1 .. $]; + + while (str.length) + { + uint c = str[0] - C('0'); + if (c >= 10) + break; + str = str[1 .. $]; + bool overflow; + x = x.mulu(10u, overflow); + if (overflow) + return false; + x = x.addu(c, overflow); + if (overflow) + return false; + } + + static if (isSigned!T) + { + if (x > Unsigned!T(T.max + sign)) + return false; + x = sign ? -x : x; + } + + value = x; + return true; +} diff --git a/source/mir/polynomial.d b/source/mir/polynomial.d new file mode 100644 index 00000000..db2eca86 --- /dev/null +++ b/source/mir/polynomial.d @@ -0,0 +1,226 @@ +/++ +Polynomial ref-counted structure. + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Authors: Ilia Ki ++/ +module mir.polynomial; + +import mir.math.common: fmamath; +import mir.rc.array; + +@fmamath: + +/++ +Polynomial callable ref-counted structure. ++/ +struct Polynomial(F) +{ + /// + RCArray!(const F) coefficients; + + /++ + Params: + coefficients = coefficients `c[i]` for polynomial function `f(x)=c[0]+c[1]*x^^1+...+c[n]*x^^n` + +/ + this(RCArray!(const F) coefficients) + { + import core.lifetime: move; + this.coefficients = coefficients.move; + } + + /++ + Params: + derivative = derivative order + +/ + template opCall(uint derivative = 0) + { + /++ + Params: + x = `x` point + +/ + @fmamath typeof(F.init * X.init * 1f + F.init) opCall(X)(in X x) const + { + return x.poly!derivative(this.coefficients[]); + } + } +} + +/// ditto +Polynomial!F polynomial(F)(RCArray!(const F) coefficients) +{ + import core.lifetime: move; + return typeof(return)(coefficients.move); +} + +/// +version (mir_test) @safe pure nothrow @nogc unittest +{ + import mir.test; + import mir.rc.array; + auto a = rcarray!(const double)(3.0, 4.5, 1.9, 2); + auto p = a.polynomial; + + alias f = (x) => 3.0 + 4.5 * x^^1 + 1.9 * x^^2 + 2 * x^^3; + alias df = (x) => 4.5 + 2 * 1.9 * x^^1 + 3 * 2 * x^^2; + alias d2f = (x) => 2 * 1.9 + 6 * 2 * x^^1; + + p(3.3).shouldApprox == f(3.3); + p(7.2).shouldApprox == f(7.2); + + p.opCall!1(3.3).shouldApprox == df(3.3); + p.opCall!1(7.2).shouldApprox == df(7.2); + + p.opCall!2(3.3).shouldApprox == d2f(3.3); + p.opCall!2(7.2).shouldApprox == d2f(7.2); +} + +/++ +Evaluate polynomial. + +Coefficients assumed to be in the order a0 + a1 * x ^^ 1 + ... + aN * x ^^ N + +Params: + F = controls type of output + derivative = order of derivatives (default = 0) + +Returns: + Value of the polynomial, evaluated at `x` + +See_also: + $(WEB en.wikipedia.org/wiki/Polynomial, Polynomial). ++/ +template poly(uint derivative = 0) +{ + import std.traits: ForeachType; + /++ + Params: + x = value to evaluate + coefficients = coefficients of polynomial + +/ + @fmamath typeof(F.init * X.init * 1f + F.init) poly(X, F)(in X x, scope const F[] coefficients...) + { + import mir.internal.utility: Iota; + auto ret = cast(typeof(return))0; + if (coefficients.length > 0) + { + ptrdiff_t i = coefficients.length - 1; + assert(i >= 0); + auto c = cast()coefficients[i]; + static foreach (d; Iota!derivative) + c *= i - d; + ret = cast(typeof(return)) c; + while (--i >= cast(ptrdiff_t)derivative) + { + assert(i < coefficients.length); + c = cast()coefficients[i]; + static foreach (d; Iota!derivative) + c *= i - d; + ret *= x; + ret += c; + } + } + return ret; + } +} + +/// +version (mir_test) @safe pure nothrow unittest +{ + import mir.math.common: approxEqual; + + double[] x = [3.0, 4.5, 1.9, 2]; + + alias f = (x) => 3.0 + 4.5 * x^^1 + 1.9 * x^^2 + 2 * x^^3; + alias df = (x) => 4.5 + 2 * 1.9 * x^^1 + 3 * 2 * x^^2; + alias d2f = (x) => 2 * 1.9 + 6 * 2 * x^^1; + + assert(poly(3.3, x).approxEqual(f(3.3))); + assert(poly(7.2, x).approxEqual(f(7.2))); + + assert(poly!1(3.3, x).approxEqual(df(3.3))); + assert(poly!1(7.2, x).approxEqual(df(7.2))); + + assert(poly!2(3.3, x).approxEqual(d2f(3.3))); + assert(poly!2(7.2, x).approxEqual(d2f(7.2))); +} + +// static array test +version (mir_test) @safe pure @nogc nothrow unittest +{ + import mir.math.common: approxEqual; + + double[4] x = [3.0, 4.5, 1.9, 2]; + + alias f = (x) => 3.0 + 4.5 * x^^1 + 1.9 * x^^2 + 2 * x^^3; + alias df = (x) => 4.5 + 2 * 1.9 * x^^1 + 3 * 2 * x^^2; + alias d2f = (x) => 2 * 1.9 + 6 * 2 * x^^1; + + assert(poly(3.3, x).approxEqual(f(3.3))); + assert(poly(7.2, x).approxEqual(f(7.2))); + + assert(poly!1(3.3, x).approxEqual(df(3.3))); + assert(poly!1(7.2, x).approxEqual(df(7.2))); + + assert(poly!2(3.3, x).approxEqual(d2f(3.3))); + assert(poly!2(7.2, x).approxEqual(d2f(7.2))); +} + +// Check coefficient.length = 3 +version (mir_test) @safe pure nothrow unittest +{ + import mir.math.common: approxEqual; + + double[] x = [3.0, 4.5, 1.9]; + + alias f = (x) => 3.0 + 4.5 * x^^1 + 1.9 * x^^2; + alias df = (x) => 4.5 + 2 * 1.9 * x^^1; + alias d2f = (x) => 2 * 1.9; + + assert(poly(3.3, x).approxEqual(f(3.3))); + assert(poly(7.2, x).approxEqual(f(7.2))); + + assert(poly!1(3.3, x).approxEqual(df(3.3))); + assert(poly!1(7.2, x).approxEqual(df(7.2))); + + assert(poly!2(3.3, x).approxEqual(d2f(3.3))); + assert(poly!2(7.2, x).approxEqual(d2f(7.2))); +} + +// Check coefficient.length = 2 +version (mir_test) @safe pure nothrow unittest +{ + import mir.math.common: approxEqual; + + double[] x = [3.0, 4.5]; + + alias f = (x) => 3.0 + 4.5 * x^^1; + alias df = (x) => 4.5; + alias d2f = (x) => 0.0; + + assert(poly(3.3, x).approxEqual(f(3.3))); + assert(poly(7.2, x).approxEqual(f(7.2))); + + assert(poly!1(3.3, x).approxEqual(df(3.3))); + assert(poly!1(7.2, x).approxEqual(df(7.2))); + + assert(poly!2(3.3, x).approxEqual(d2f(3.3))); + assert(poly!2(7.2, x).approxEqual(d2f(7.2))); +} + +// Check coefficient.length = 1 +version (mir_test) @safe pure nothrow unittest +{ + import mir.math.common: approxEqual; + + double[] x = [3.0]; + + alias f = (x) => 3.0; + alias df = (x) => 0.0; + + assert(poly(3.3, x).approxEqual(f(3.3))); + assert(poly(7.2, x).approxEqual(f(7.2))); + + assert(poly!1(3.3, x).approxEqual(df(3.3))); + assert(poly!1(7.2, x).approxEqual(df(7.2))); +} diff --git a/source/mir/range.d b/source/mir/range.d index c487cbb2..c96526ee 100644 --- a/source/mir/range.d +++ b/source/mir/range.d @@ -3,9 +3,9 @@ Ranges. See_also: $(MREF mir,_primitives). -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Copyright: Copyright © 2017-, Ilya Yaroshenko -Authors: Ilya Yaroshenko, Phobos Authors +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki, Phobos Authors +/ module mir.range; diff --git a/source/mir/rc/array.d b/source/mir/rc/array.d index 38ded14e..697a2495 100644 --- a/source/mir/rc/array.d +++ b/source/mir/rc/array.d @@ -9,34 +9,36 @@ import mir.rc.context; import mir.type_info; import std.traits; -private static immutable allocationExcMsg = "mir_rcarray: out of memory error."; +package static immutable allocationExcMsg = "mir_rcarray: out of memory error."; version (D_Exceptions) { import core.exception: OutOfMemoryError; - private static immutable allocationError = new OutOfMemoryError(allocationExcMsg); + package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); } /++ Thread safe reference counting array. -`__xdtor` if any is used to destruct objects. - The implementation never adds roots into the GC. +/ struct mir_rcarray(T) { + import mir.internal.utility: isComplex, realType; /// - private T* _payload; - private ref inout(mir_rc_context) context() inout scope return pure nothrow @nogc @trusted @property + package T* _payload; + package ref mir_rc_context context() inout return scope pure nothrow @nogc @trusted @property { assert(_payload); - return (cast(inout(mir_rc_context)*)_payload)[-1]; + return (cast(mir_rc_context*)_payload)[-1]; } - private void _reset() { _payload = null; } + package void _reset() { _payload = null; } + + package alias ThisTemplate = .mir_rcarray; + package alias _thisPtr = _payload; - private alias ThisTemplate = .mir_rcarray; - private alias _thisPtr = _payload; + /// + alias serdeKeysProxy = Unqual!T; /// void proxySwap(ref typeof(this) rhs) pure nothrow @nogc @safe @@ -46,33 +48,62 @@ struct mir_rcarray(T) rhs._payload = t; } + /// + this(typeof(null)) + { + } + /// mixin CommonRCImpl; + /// + pragma(inline, true) + bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc + { + return !this; + } + + /// ditto + bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc + { + static if (isComplex!T) + return cast(const realType!T[]) opIndex() == cast(const realType!Y[]) rhs.opIndex(); + else + return opIndex() == rhs.opIndex(); + } + + /// + int opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc + { + static if (isComplex!T) + return __cmp(cast(const realType!T[])opIndex(), cast(const realType!Y[])rhs.opIndex()); + else + return __cmp(opIndex(), rhs.opIndex()); + } + + /// + size_t toHash() @trusted scope const pure nothrow @nogc + { + static if (isComplex!T) + return hashOf(cast(const realType!T[])opIndex()); + else + return hashOf(opIndex()); + } + /// ~this() nothrow { - static if (hasDestructor!T) + static if (hasElaborateDestructor!T || hasDestructor!T) { if (false) // break @safe and pure attributes { Unqual!T* object; - (*object).__xdtor(); + (*object).__xdtor; } } if (this) { (() @trusted { mir_rc_decrease_counter(context); })(); - debug _reset; - } - } - - /// - this(this) scope @trusted pure nothrow @nogc - { - if (this) - { - mir_rc_increase_counter(context); } } @@ -109,13 +140,38 @@ struct mir_rcarray(T) } /// - auto asSlice() @property + auto asSlice()() @property { import mir.ndslice.slice: mir_slice; alias It = mir_rci!T; return mir_slice!It([length], It(this)); } + /// + auto asSlice()() const @property + { + import mir.ndslice.slice: mir_slice; + alias It = mir_rci!(const T); + return mir_slice!It([length], It(this.lightConst)); + } + + /// + auto asSlice()() immutable @property + { + import mir.ndslice.slice: mir_slice; + alias It = mir_rci!(immutable T); + return mir_slice!It([length], It(this.lightImmutable)); + } + + /// + auto moveToSlice()() @property + { + import core.lifetime: move; + import mir.ndslice.slice: mir_slice; + alias It = mir_rci!T; + return mir_slice!It([length], It(move(this))); + } + /++ Params: length = array length @@ -135,14 +191,14 @@ struct mir_rcarray(T) if (!ctx) { version(D_Exceptions) - throw allocationError; + { import mir.exception : toMutable; throw allocationError.toMutable; } else assert(0, allocationExcMsg); } _payload = cast(T*)(ctx + 1); ar = cast(Unqual!T[])_payload[0 .. length]; } (); - if (initialize || hasElaborateAssign!T) + if (initialize || hasElaborateAssign!(Unqual!T)) { import mir.conv: uninitializedFillDefault; uninitializedFillDefault(ar); @@ -151,12 +207,71 @@ struct mir_rcarray(T) static if (isImplicitlyConvertible!(const T, T)) static if (isImplicitlyConvertible!(const Unqual!T, T)) - private alias V = const Unqual!T; + package alias V = const Unqual!T; else - private alias V = const T; + package alias V = const T; else - private alias V = T; + package alias V = T; + + static if (is(T == const) || is(T == immutable)) + this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc + { + if (rhs) + { + this._payload = cast(typeof(this._payload))rhs._payload; + mir_rc_increase_counter(context); + } + } + + static if (is(T == immutable)) + this(return ref scope const typeof(this) rhs) immutable @trusted pure nothrow @nogc + { + if (rhs) + { + this._payload = cast(typeof(this._payload))rhs._payload; + mir_rc_increase_counter(context); + } + } + + static if (is(T == immutable)) + this(return ref scope const typeof(this) rhs) const @trusted pure nothrow @nogc + { + if (rhs) + { + this._payload = cast(typeof(this._payload))rhs._payload; + mir_rc_increase_counter(context); + } + } + this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc + { + if (rhs) + { + this._payload = rhs._payload; + mir_rc_increase_counter(context); + } + } + + /// + ref opAssign(typeof(null)) scope return @trusted // pure nothrow @nogc + { + this = typeof(this).init; + } + + /// + ref opAssign(scope return typeof(this) rhs) scope return @trusted // pure nothrow @nogc + { + this.proxySwap(rhs); + return this; + } + + /// + ref opAssign(Q)(scope return ThisTemplate!Q rhs) scope return @trusted // pure nothrow @nogc + if (isImplicitlyConvertible!(Q*, T*)) + { + this.proxySwap(*()@trusted{return cast(typeof(this)*)&rhs;}()); + return this; + } } /// ditto @@ -190,31 +305,20 @@ unittest static assert(is(typeof(fs) == Slice!(double*))); } -/// version(mir_test) @safe pure @nogc nothrow unittest { - RCArray!double a = rcarray!double(1.0, 2, 5, 3); - assert(a[0] == 1); - assert(a[$ - 1] == 3); - - auto s = rcarray!char("hello!"); - assert(s[0] == 'h'); - assert(s[$ - 1] == '!'); - - alias rcstring = rcarray!(immutable char); - auto r = rcstring("string"); - assert(r[0] == 's'); - assert(r[$ - 1] == 'g'); + import mir.complex; + auto a = rcarray(complex(2.0, 3), complex(4.9, 2)); } -private template LikeArray(Range) +package template LikeArray(Range) { static if (__traits(identifier, Range) == "mir_slice") { - import mir.ndslice.slice; - enum LikeArray = is(Range : Slice!(T*, N, kind), T, size_t N, SliceKind kind); + import mir.ndslice.slice: Slice, SliceKind; + enum LikeArray = is(Range : Slice!(T*, N, SliceKind.contiguous), T, size_t N); } else { @@ -224,14 +328,14 @@ private template LikeArray(Range) /// auto rcarray(T = void, Range)(ref Range range) - if (is(T == void) && hasLength!Range && !is(Range == LightScopeOf!Range)) + if (is(T == void) && !is(Range == LightScopeOf!Range)) { return .rcarray(range.lightScope); } /// ditto auto rcarray(T = void, Range)(Range range) - if (is(T == void) && hasLength!Range && isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) + if (is(T == void) && isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) { static if (LikeArray!Range) { @@ -243,109 +347,93 @@ auto rcarray(T = void, Range)(Range range) } } -/// ditto -RCArray!V rcarray(T = void, V)(V[] values...) - if (is(T == void) && hasIndirections!V) -{ - return .rcarray(values, true); -} - /// ditto RCArray!V rcarray(T = void, V)(scope V[] values...) - if (is(T == void) && !hasIndirections!V) + if (is(T == void)) { return .rcarray(values, true); } -/// ditto -RCArray!V rcarray(T = void, V)(V[] values, bool deallocate) - if (is(T == void) && hasIndirections!V) -{ - return .rcarray!V(values); -} - /// ditto RCArray!V rcarray(T = void, V)(scope V[] values, bool deallocate) - if (is(T == void) && !hasIndirections!V) + if (is(T == void)) { - return .rcarray!V(values); + return .rcarray!V(values, deallocate); } -/++ -+/ +/// ditto template rcarray(T) - if(!is(T : E[], E) && !is(T == void)) + if(!is(T == E[], E) && !is(T == void)) { + import mir.primitives: isInputRange, isInfinite; + /// auto rcarray(Range)(ref Range range) - if (hasLength!Range && !is(Range == LightScopeOf!Range)) + if (!is(Range == LightScopeOf!Range)) { return .rcarray!T(range.lightScope); } /// ditto auto rcarray(Range)(Range range) - if (hasLength!Range && isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) + if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isArray!Range || isPointer!Range && (isInputRange!(PointerTarget!Range) || isIterable!(PointerTarget!Range))) { static if (LikeArray!Range) { return .rcarray!T(range.field); } - else + else static if (hasLength!Range) { - auto ret = RCArray!T(range.length, false); import mir.conv: emplaceRef; - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; + auto ret = RCArray!T(range.length, false); size_t i; - foreach(ref e; range) - ret[i++].emplaceRef!T(e); - return move(ret); + static if (isInputRange!Range) + for (; !range.empty; range.popFront) + ret[i++].emplaceRef!T(range.front); + else + static if (isPointer!Range) + foreach (e; *range) + ret[i++].emplaceRef!T(e); + else + foreach (e; range) + ret[i++].emplaceRef!T(e); + return ret; + } + else + { + import mir.appender: scopedBuffer; + import mir.conv: emplaceRef; + auto a = scopedBuffer!T; + static if (isInputRange!Range) + for (; !range.empty; range.popFront) + a.put(range.front); + else + static if (isPointer!Range) + foreach (e; *range) + a.put(e); + else + foreach (e; range) + a.put(e); + scope values = a.data; + return ()@trusted { + auto ret = RCArray!T(values.length, false); + a.moveDataAndEmplaceTo(ret[]); + return ret; + } (); } - } - - /// ditto - RCArray!T rcarray(V)(V[] values...) - if (hasIndirections!V) - { - return .rcarray!T(values, true); } /// ditto RCArray!T rcarray(V)(scope V[] values...) - if (!hasIndirections!V) { return .rcarray!T(values, true); } - /// ditto - RCArray!T rcarray(V)(V[] values, bool deallocate) - if (hasIndirections!V) - { - auto ret = mir_rcarray!T(values.length, false, deallocate); - static if (!hasElaborateAssign!T && is(Unqual!V == Unqual!T)) - { - ()@trusted { - import core.stdc.string: memcpy; - memcpy(cast(void*)ret.ptr, cast(const void*)values.ptr, values.length * T.sizeof); - }(); - } - else - { - import mir.conv: emplaceRef; - auto lhs = ret[]; - foreach (i, ref e; values) - lhs[i].emplaceRef!T(e); - } - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - return move(ret); - } - /// ditto RCArray!T rcarray(V)(scope V[] values, bool deallocate) - if (!hasIndirections!V) { - auto ret = mir_rcarray!T(values.length, false); - static if (!hasElaborateAssign!T && is(Unqual!V == Unqual!T)) + auto ret = mir_rcarray!T(values.length, hasElaborateDestructor!T, deallocate); + static if (!hasElaborateAssign!(Unqual!T) && is(Unqual!V == Unqual!T)) { ()@trusted { import core.stdc.string: memcpy; @@ -359,11 +447,41 @@ template rcarray(T) foreach (i, ref e; values) lhs[i].emplaceRef!T(e); } - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - return move(ret); + return ret; } } +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + RCArray!double a = rcarray!double(1.0, 2, 5, 3); + assert(a[0] == 1); + assert(a[$ - 1] == 3); + + auto s = rcarray!char("hello!"); + assert(s[0] == 'h'); + assert(s[$ - 1] == '!'); + + alias rcstring = rcarray!(immutable char); + auto r = rcstring("string"); + assert(r[0] == 's'); + assert(r[$ - 1] == 'g'); +} + +/// With Input Ranges +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + import mir.algorithm.iteration: filter; + static immutable numbers = [3, 2, 5, 2, 3, 7, 3]; + static immutable filtered = [5.0, 7]; + auto result = numbers.filter!"a > 3".rcarray!(immutable double); + static assert(is(typeof(result) == RCArray!(immutable double))); + assert (result[] == filtered); +} /++ Params: @@ -377,6 +495,7 @@ RCArray!T mininitRcarray(T)(size_t length, bool deallocate = true) } /// +version(mir_test) @safe pure nothrow @nogc unittest { auto a = mininitRcarray!double(5); @@ -390,6 +509,9 @@ Thread safe reference counting iterator. +/ struct mir_rci(T) { + import mir.ndslice.slice: Slice; + import mir.ndslice.iterator: IotaIterator; + /// T* _iterator; @@ -399,21 +521,19 @@ struct mir_rci(T) /// this(RCArray!T array) { - import mir.utility: swap; this._iterator = (()@trusted => array.ptr)(); - swap(this._array, array); + this._array.proxySwap(array); } /// this(T* _iterator, RCArray!T array) { - import mir.utility: swap; this._iterator = _iterator; - swap(this._array, array); + this._array.proxySwap(array); } /// - inout(T)* lightScope()() scope return inout @property @trusted + inout(T)* lightScope()() return scope inout @property @trusted { debug { @@ -444,22 +564,22 @@ struct mir_rci(T) ref opAssign(Q)(return mir_rci!Q rhs) scope return nothrow if (isImplicitlyConvertible!(Q*, T*)) { - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; + import core.lifetime: move; _iterator = rhs._iterator; _array = move(rhs._array); return this; } /// - mir_rci!(const T) lightConst()() scope return const nothrow @property + mir_rci!(const T) lightConst()() return scope const nothrow @property { return typeof(return)(_iterator, _array.lightConst); } /// - mir_rci!(immutable T) lightImmutable()() scope return immutable nothrow @property + mir_rci!(immutable T) lightImmutable()() return scope immutable nothrow @property { return typeof(return)(_iterator, _array.lightImmutable); } - /// - ref inout(T) opUnary(string op : "*")() inout scope return + /// + ref inout(T) opUnary(string op : "*")() inout return scope { debug { @@ -471,8 +591,8 @@ struct mir_rci(T) return *_iterator; } - /// - ref inout(T) opIndex(ptrdiff_t index) inout scope return @trusted + /// + ref inout(T) opIndex(ptrdiff_t index) inout return scope @trusted { debug { @@ -484,12 +604,42 @@ struct mir_rci(T) return _iterator[index]; } - /// + /// Returns: slice type of `Slice!(IotaIterator!size_t)` + Slice!(IotaIterator!size_t) opSlice(size_t dimension)(size_t i, size_t j) @safe scope const + if (dimension == 0) + in + { + assert(i <= j, "RCI!T.opSlice!0: the left opSlice boundary must be less than or equal to the right bound."); + } + do + { + return typeof(return)(j - i, typeof(return).Iterator(i)); + } + + /// Returns: ndslice on top of the refcounted iterator + auto opIndex(Slice!(IotaIterator!size_t) slice) + { + import core.lifetime: move; + auto it = this; + it += slice._iterator._index; + return Slice!(RCI!T)(slice.length, it.move); + } + + /// ditto + auto opIndex(Slice!(IotaIterator!size_t) slice) const + { + import core.lifetime: move; + auto it = lightConst; + it += slice._iterator._index; + return Slice!(RCI!(const T))(slice.length, it.move); + } + + /// void opUnary(string op)() scope if (op == "--" || op == "++") { mixin(op ~ "_iterator;"); } - /// + /// void opOpAssign(string op)(ptrdiff_t index) scope if (op == "-" || op == "+") { mixin("_iterator " ~ op ~ "= index;"); } @@ -499,27 +649,27 @@ struct mir_rci(T) if (op == "+" || op == "-") { return mir_rci!T(_iterator + index, _array); } - /// + /// mir_rci!(const T) opBinary(string op)(ptrdiff_t index) const if (op == "+" || op == "-") { return mir_rci!T(_iterator + index, _array); } - /// + /// mir_rci!(immutable T) opBinary(string op)(ptrdiff_t index) immutable if (op == "+" || op == "-") { return mir_rci!T(_iterator + index, _array); } - /// + /// ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const { return this._iterator - right._iterator; } - /// + /// bool opEquals()(scope ref const typeof(this) right) scope const { return this._iterator == right._iterator; } - /// - ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const - { return this._iterator - right._iterator; } + /// + int opCmp()(scope ref const typeof(this) right) scope const + { auto d = this - right; return d ? d < 0 ? -1 : 1 : 0; } } /// ditto @@ -529,16 +679,16 @@ alias RCI = mir_rci; version(mir_test) @safe @nogc unittest { + import mir.ndslice.traits: isIterator; import mir.ndslice.slice; import mir.rc.array; - auto array = mir_rcarray!double(10); - auto slice = array.asSlice; + auto slice = mir_rcarray!double(10).asSlice; static assert(isIterator!(RCI!double)); static assert(is(typeof(slice) == Slice!(RCI!double))); auto matrix = slice.sliced(2, 5); static assert(is(typeof(matrix) == Slice!(RCI!double, 2))); - array[7] = 44; + slice[7] = 44; assert(matrix[1, 2] == 44); } @@ -650,8 +800,6 @@ unittest version(mir_test) @safe unittest { - import core.stdc.stdio; - struct S { uint s; @@ -681,3 +829,16 @@ version(mir_test) S[1] d = [S(1)]; auto r = rcarray(d); } + +version(mir_test) +unittest +{ + import mir.small_string; + alias S = SmallString!32u; + auto ars = [S("123"), S("422")]; + alias R = mir_rcarray!S; + auto rc = ars.rcarray!S; + + RCArray!int value = null; + value = null; +} diff --git a/source/mir/rc/context.d b/source/mir/rc/context.d index 29b3c621..9ba44b50 100644 --- a/source/mir/rc/context.d +++ b/source/mir/rc/context.d @@ -10,7 +10,7 @@ import mir.type_info; struct mir_rc_context { /// - void* allocator; + extern (C) void function(mir_rc_context*) @system nothrow @nogc pure deallocator; /// immutable(mir_type_info)* typeInfo; /// @@ -48,7 +48,7 @@ Params: export extern(C) void mir_rc_decrease_counter(ref mir_rc_context context) @system nothrow @nogc pure { - pragma(inline, false); + pragma(inline, true); import core.atomic: atomicOp; with(context) { @@ -59,6 +59,10 @@ void mir_rc_decrease_counter(ref mir_rc_context context) @system nothrow @nogc p mir_rc_delete(context); } } + // else + // { + // assert(0); + // } } } @@ -68,6 +72,7 @@ export extern(C) void mir_rc_delete(ref mir_rc_context context) @system nothrow @nogc pure { + assert(context.deallocator); with(context) { with(typeInfo) @@ -75,16 +80,24 @@ void mir_rc_delete(ref mir_rc_context context) if (destructor) { auto ptr = cast(void*)(&context + 1); - foreach(i; 0 .. length) + auto i = length; + assert(i); + do { destructor(ptr); ptr += size; } + while(--i); } } } - import mir.internal.memory: free; - free(&context); + if (context.counter) + assert(0); + version (mir_secure_memory) + { + (cast(ubyte*)(&context + 1))[0 .. context.length * context.typeInfo.size] = 0; + } + context.deallocator(&context); } /++ @@ -94,23 +107,29 @@ mir_rc_context* mir_rc_create( ref immutable(mir_type_info) typeInfo, size_t length, scope const void* payload = null, - bool initialise = true, + bool initialize = true, bool deallocate = true, ) @system nothrow @nogc pure { - import mir.internal.memory: malloc; + import mir.internal.memory: malloc, free; import core.stdc.string: memset, memcpy; assert(length); auto size = length * typeInfo.size; - if (auto context = cast(mir_rc_context*)malloc(mir_rc_context.sizeof + size)) + auto fullSize = mir_rc_context.sizeof + size; + if (auto p = malloc(fullSize)) { - context.allocator = null; + version (mir_secure_memory) + { + (cast(ubyte*)p)[0 .. fullSize] = 0; + } + auto context = cast(mir_rc_context*)p; + context.deallocator = &free; context.typeInfo = &typeInfo; context.counter = deallocate; context.length = length; - if (initialise) + if (initialize) { auto ptr = cast(void*)(context + 1); if (payload) @@ -157,39 +176,20 @@ mir_rc_context* mir_rc_create( package mixin template CommonRCImpl() { /// - this(typeof(null)) - { - } - - /// - ref opAssign(typeof(null)) scope return - { - this = typeof(this).init; - } + ThisTemplate!(const T) lightConst()() return scope const @nogc nothrow @trusted @property + { return *cast(typeof(return)*) &this; } - /// - ref opAssign(return typeof(this) rhs) scope return @trusted - { - this.proxySwap(rhs); - return this; - } + /// ditto + ThisTemplate!(immutable T) lightImmutable()() return scope immutable @nogc nothrow @trusted @property + { return *cast(typeof(return)*) &this; } /// - ref opAssign(Q)(return ThisTemplate!Q rhs) scope return pure nothrow @nogc @trusted - if (isImplicitlyConvertible!(Q*, T*)) + ThisTemplate!(const Unqual!T) moveToConst()() return scope @nogc nothrow @trusted @property { - this.proxySwap(*cast(typeof(this)*)&rhs); - return this; + import core.lifetime: move; + return move(*cast(typeof(return)*) &this); } - /// - ThisTemplate!(const T) lightConst()() scope return const @nogc nothrow @trusted @property - { return *cast(typeof(return)*) &this; } - - /// ditto - ThisTemplate!(immutable T) lightImmutable()() scope return immutable @nogc nothrow @trusted @property - { return *cast(typeof(return)*) &this; } - /// pragma(inline, true) size_t _counter() @trusted scope pure nothrow @nogc const @property @@ -198,7 +198,8 @@ package mixin template CommonRCImpl() } /// - bool opCast(C : bool)() const + C opCast(C)() const + if (is(Unqual!C == bool)) { return _thisPtr !is null; } @@ -224,27 +225,10 @@ package mixin template CommonRCImpl() return *cast(typeof(return)*)&this; } - /// - pragma(inline, true) - bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc @property - { - return !this; - } - /// ditto - bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc @property + C opCast(C : ThisTemplate!Q, Q)() pure nothrow @nogc const @system + if (isImplicitlyConvertible!(immutable(T)*, Q*) && !isImplicitlyConvertible!(const(T)*, Q*)) { - return _thisPtr == rhs._thisPtr; - } - - /// - sizediff_t opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc @property - { - return cast(void*)_thisPtr - cast(void*)rhs._thisPtr; - } - - size_t toHash() @trusted scope const pure nothrow @nogc @property - { - return cast(size_t) _thisPtr; + return *cast(typeof(return)*)&this; } } diff --git a/source/mir/rc/package.d b/source/mir/rc/package.d index 18ac136d..7a26a36c 100644 --- a/source/mir/rc/package.d +++ b/source/mir/rc/package.d @@ -1,10 +1,156 @@ /++ $(H1 Thread-safe reference-counted arrays and pointers) -Publicly imports $(MREF mir,rc,array) and $(MREF mir,rc,ptr). +Mir provides two kinds of ref-counting pointers and two kinds of ref-counted arrays. + +The first kind pointer is `RCPtr`, which consists of a pointer to the context and pointer to the value.`RCPtr` supports structural and object polymorphism. It allows getting members with the same context as the root. +The second kind is `SlimRCPtr`, which consist only from a pointer to the value. The context for `SlimRCPtr`is computed using a fixed-length memory shift from the pointer to the value. +`SlimRCPtr` can be converted to an `RCPtr` and to an `RCArray` of the one element. + +`RCArray` is an array type without range primitives. It's length can't be reduced after construction.In the other hand, `Slice!(RCI!(T))` is an ndslice with all random-access range primitives.`RCI` is an iterator, which consists of `RCArray` and the pointer to the current element. +`RCArray!T` can be converted or moved to `Slice!(RCI!(T))` using `.asSlice` or `.moveToSlice` methods respectively. + +$(RED `RCArray!T` aliases itself to a common D array slice. This feature may cause a segmentation fault in safe code if used without DIP1000.) + +`RCPtr!T` can be constructed from an element index and `RCArray!T` / `Slice!(RCI!(T))`. + +The package publicly imports $(MREF mir,rc,array), $(MREF mir,rc,ptr), and $(MREF mir,rc,slim_ptr). + +See_also: $(MREF mir,ndslice). +/ module mir.rc; +/// public import mir.rc.array; +/// public import mir.rc.ptr; +/// public import mir.rc.slim_ptr; + +import mir.ndslice.slice; + +/++ +Returns: shared pointer constructed from the slim shared pointer. + +The function has zero computation cost. ++/ +RCPtr!F toRCPtr(F)(return SlimRCPtr!F contextAndValue) @trusted +{ + typeof(return) ret; + ret._value = contextAndValue._value; + ret._context = &contextAndValue.context(); + contextAndValue._value = null; + return ret; +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + import core.lifetime: move; + struct S + { + double e; + } + struct C + { + int i; + S s; + } + + auto a = createSlimRC!C(10, S(3)); + auto s = a.move.toRCPtr.shareMember!"s"; + assert(s._counter == 1); + assert(s.e == 3); +} + +/++ +Returns: shared pointer constructed with the `array`'s context and the value points to `array[index]`. + +The function has zero computation cost. ++/ +RCPtr!F toRCPtrAt(F)(return RCArray!F array, size_t index) @trusted + if (!is(R == class) && !is(R == interface)) +in { + assert(index < array.length, "toRCPtrAt: index should be less then array.length"); +} +do { + typeof(return) ret; + ret._value = array._payload + index; + ret._context = &array.context(); + array._payload = null; + return ret; +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + struct S { double e; } + + auto a = RCArray!S(10); + a[3].e = 4; + + auto s = a.toRCPtrAt(3); + + assert(s._counter == 2); + assert(s.e == 4); +} + +/// ditto +RCPtr!F toRCPtrAt(F)(return Slice!(RCI!F) array, size_t index) @trusted + if (!is(R == class) && !is(R == interface)) +in { + assert(index < array.length, "toRCPtrAt: index should be less then array.length"); +} +do { + typeof(return) ret; + ret._value = array._iterator._iterator + index; + ret._context = &array._iterator._array.context(); + array._iterator._array._payload = null; + return ret; +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + struct S { double e; } + + auto a = RCArray!S(10).asSlice[5 .. $]; + a[3].e = 4; + + auto s = a.toRCPtrAt(3); + + assert(s._counter == 2); + assert(s.e == 4); +} + +/++ +Returns: RC array length of one constructed from the slim shared pointer. + +The function has zero computation cost. ++/ +RCArray!F toRCArray(F)(return SlimRCPtr!F context) @trusted +{ + typeof(return) ret; + ret._payload = context._value; + context._value = null; + return ret; +} + +/// +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + struct S { double e; } + + auto a = createSlimRC!S(4).toRCArray; + assert(a._counter == 1); + assert(a.length == 1); + assert(a[0].e == 4); +} diff --git a/source/mir/rc/ptr.d b/source/mir/rc/ptr.d index 0e8f7bc9..75f39aa5 100644 --- a/source/mir/rc/ptr.d +++ b/source/mir/rc/ptr.d @@ -9,13 +9,13 @@ import mir.rc.context; import mir.type_info; import std.traits; -private static immutable allocationExcMsg = "mir_rcptr: out of memory error."; -private static immutable getExcMsg = "mir_rcptr: trying to use null value."; +package static immutable allocationExcMsg = "mir_rcptr: out of memory error."; +package static immutable getExcMsg = "mir_rcptr: trying to use null value."; version (D_Exceptions) { import core.exception: OutOfMemoryError, InvalidMemoryOperationError; - private static immutable allocationError = new OutOfMemoryError(allocationExcMsg); + package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); } /++ @@ -34,28 +34,28 @@ struct mir_rcptr(T) /// static if (is(T == class) || is(T == interface)) - private Unqual!T _value; + package Unqual!T _value; else - private T* _value; - private mir_rc_context* _context; + package T* _value; + package mir_rc_context* _context; - private ref inout(mir_rc_context) context() inout scope return @trusted @property + package ref mir_rc_context context() inout return scope @trusted @property { - return *_context; + return *cast(mir_rc_context*)_context; } - private void _reset() + package void _reset() { _value = null; _context = null; } - inout(void)* _thisPtr() inout scope return @trusted @property + inout(void)* _thisPtr() inout return scope @trusted @property { return cast(inout(void)*) _value; } - private alias ThisTemplate = .mir_rcptr; + package alias ThisTemplate = .mir_rcptr; /// ditto alias opUnary(string op : "*") = _get_value; @@ -65,7 +65,7 @@ struct mir_rcptr(T) static if (is(T == class) || is(T == interface)) /// pragma(inline, true) - inout(T) _get_value() scope inout @property + inout(T) _get_value() return scope inout @property { assert(this, getExcMsg); return _value; @@ -73,7 +73,7 @@ struct mir_rcptr(T) else /// pragma(inline, true) - ref inout(T) _get_value() scope inout @property + ref inout(T) _get_value() return scope inout @property { assert(this, getExcMsg); return *_value; @@ -90,18 +90,59 @@ struct mir_rcptr(T) rhs._context = t1; } + /// + this(typeof(null)) + { + } + /// mixin CommonRCImpl; + + /// + pragma(inline, true) + bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc + { + return !this; + } + + /// ditto + bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc + { + if (_thisPtr is null) + return rhs._thisPtr is null; + if (rhs._thisPtr is null) + return false; + return _get_value == rhs._get_value; + } + + /// + auto opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc + { + if (_thisPtr is null) + return (rhs._thisPtr is null) - 1; + if (rhs._thisPtr is null) + return 1; + return _get_value.opCmp(rhs._get_value); + } + + /// + size_t toHash() @trusted scope const pure nothrow @nogc + { + if (_thisPtr is null) + return 0; + return hashOf(_get_value); + } + /// ~this() nothrow { - static if (hasDestructor!T) + static if (hasElaborateDestructor!T || hasDestructor!T) { if (false) // break @safe and pure attributes { Unqual!T* object; - (*object).__xdtor(); + (*object).__xdtor; } } if (this) @@ -111,63 +152,100 @@ struct mir_rcptr(T) } } - /// - this(this) scope @trusted pure nothrow @nogc + static if (is(T == const) || is(T == immutable)) + this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc { - if (this) + if (rhs) + { + this._value = cast(typeof(this._value))rhs._value; + this._context = cast(typeof(this._context))rhs._context; + mir_rc_increase_counter(context); + } + } + + static if (is(T == immutable)) + this(return ref scope const typeof(this) rhs) immutable @trusted pure nothrow @nogc + { + if (rhs) { + this._value = cast(typeof(this._value))rhs._value; + this._context = cast(typeof(this._context))rhs._context; mir_rc_increase_counter(context); } } - static if (!is(T == interface) && !__traits(isAbstractClass, T)) + static if (is(T == immutable)) + this(return ref scope const typeof(this) rhs) const @trusted pure nothrow @nogc { - private this(Args...)(auto ref Args args) + if (rhs) { - () @trusted { - _context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T); - if (!_context) - { - version(D_Exceptions) - throw allocationError; - else - assert(0, allocationExcMsg); - } - _value = cast(typeof(_value))(_context + 1); - } (); - import mir.functional: forward; - import mir.conv: emplace; - emplace!T(_value, forward!args); + this._value = cast(typeof(this._value))rhs._value; + this._context = cast(typeof(this._context))rhs._context; + mir_rc_increase_counter(context); + } + } + + this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc + { + if (rhs) + { + this._value = rhs._value; + this._context = rhs._context; + mir_rc_increase_counter(context); } } + + /// + ref opAssign(typeof(null)) scope return @trusted // pure nothrow @nogc + { + this = typeof(this).init; + } + + /// + ref opAssign(return typeof(this) rhs) scope return @trusted // pure nothrow @nogc + { + this.proxySwap(rhs); + return this; + } + + /// + ref opAssign(Q)(return ThisTemplate!Q rhs) scope return @trusted // pure nothrow @nogc + if (isImplicitlyConvertible!(Q*, T*)) + { + this.proxySwap(*()@trusted{return cast(typeof(this)*)&rhs;}()); + return this; + } } /// alias RCPtr = mir_rcptr; /++ +Returns: shared pointer of the member and the context from the current pointer. +/ -auto shareMember(string member, T, Args...)(return mir_rcptr!T context, auto ref Args args) +auto shareMember(string member, T, Args...)(return mir_rcptr!T context, auto ref Args args) @safe { + import core.lifetime: move; void foo(A)(auto ref A) {} + assert(context != null); static if (args.length) { // breaks safaty - if (false) foo(__traits(getMember, context._get_value, member)(forward!args)); - return (()@trusted => createRCWithContext(context, __traits(getMember, context._get_value, member)(forward!args)))(); + if (false) foo(__traits(getMember, T.init, member)(forward!args)); + return (()@trusted => createRCWithContext(__traits(getMember, context._get_value, member)(forward!args), context.move))(); } else { // breaks safaty - if (false) foo(__traits(getMember, context._get_value, member)); - return (()@trusted => createRCWithContext(context, __traits(getMember, context._get_value, member)))(); + if (false) foo(__traits(getMember, T.init, member)); + return (()@trusted => createRCWithContext(__traits(getMember, context._get_value, member), context.move))(); } } /++ Returns: shared pointer constructed with current context. +/ -@system .mir_rcptr!R createRCWithContext(R, F)(return const mir_rcptr!F context, return R value) +@system .mir_rcptr!R createRCWithContext(R, F)(return R value, return const mir_rcptr!F context) if (is(R == class) || is(R == interface)) { typeof(return) ret; @@ -179,7 +257,7 @@ Returns: shared pointer constructed with current context. } ///ditto -@system .mir_rcptr!R createRCWithContext(R, F)(return const mir_rcptr!F context, return ref R value) +@system .mir_rcptr!R createRCWithContext(R, F)(return ref R value, return const mir_rcptr!F context) if (!is(R == class) && !is(R == interface)) { typeof(return) ret; @@ -197,32 +275,49 @@ Provides polymorphism abilities for classes and structures with `alias this` syn mir_rcptr!R castTo(R, T)(return mir_rcptr!T context) @trusted if (isImplicitlyConvertible!(T, R)) { - return createRCWithContext(context, cast(R)context._get_value); + import core.lifetime: move; + return createRCWithContext(cast(R)context._get_value, move(context)); } /// ditto -mir_rcptr!(const R) castTo(R, T)(return const mir_rcptr!T context) @trusted const +mir_rcptr!(const R) castTo(R, T)(return const mir_rcptr!T context) @trusted if (isImplicitlyConvertible!(const T, const R)) { - return createRCWithContext(*cast(mir_rcptr!T*)&context, cast(const R)context._get_value); + import core.lifetime: move; + return createRCWithContext(cast(const R)context._get_value, move(*cast(mir_rcptr!T*)&context)); } /// ditto -mir_rcptr!(immutable R) castTo(R, T)(return immutable mir_rcptr!T context) @trusted immutable +mir_rcptr!(immutable R) castTo(R, T)(return immutable mir_rcptr!T context) @trusted if (isImplicitlyConvertible!(immutable T, immutable R)) { - return createRCWithContext(*cast(mir_rcptr!T*)&context, cast(immutable R)context._get_value); + import core.lifetime: move; + return createRCWithContext(cast(immutable R)context._get_value, move(*cast(mir_rcptr!T*)&context)); } - /// template createRC(T) + if (!is(T == interface) && !__traits(isAbstractClass, T)) { /// mir_rcptr!T createRC(Args...)(auto ref Args args) { - import mir.functional: forward; - return mir_rcptr!T(forward!args); + typeof(return) ret; + with (ret) () @trusted { + _context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T); + if (!_context) + { + version(D_Exceptions) + { import mir.exception : toMutable; throw allocationError.toMutable; } + else + assert(0, allocationExcMsg); + } + _value = cast(typeof(_value))(_context + 1); + } (); + import core.lifetime: forward; + import mir.conv: emplace; + cast(void) emplace!T(ret._value, forward!args); + return ret; } } @@ -238,18 +333,51 @@ unittest assert(*a == 100); } +/// Classes with empty constructor +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + static class C + { + int index = 34; + + override size_t toHash() const scope @safe pure nothrow @nogc + { + return index; + } + } + assert(createRC!C.index == 34); +} + /// version(mir_test) @safe pure @nogc nothrow unittest { - static interface I { ref double bar() @safe pure nothrow @nogc; } - static abstract class D { int index; } + static interface I { + ref double bar() @safe pure nothrow @nogc; + size_t toHash() @trusted scope const pure nothrow @nogc; + } + static abstract class D + { + int index; + + override size_t toHash() const scope @safe pure nothrow @nogc + { + return index; + } + } static class C : D, I { double value; ref double bar() @safe pure nothrow @nogc { return value; } - this(double d) { value = d; } + this(double d){ value = d; } + + override size_t toHash() const scope @safe pure nothrow @nogc + { + return hashOf(value, super.toHash); + } } auto a = createRC!C(10); assert(a._counter == 1); @@ -261,7 +389,6 @@ unittest assert(a._counter == 2); auto d = a.castTo!D; //RCPtr!D - import std.stdio; assert(d._counter == 3); d.index = 234; assert(a.index == 234); @@ -302,7 +429,7 @@ unittest version(unittest): -private struct _test_unpure_system_dest_s__ { +package struct _test_unpure_system_dest_s__ { static int numStructs; int i; diff --git a/source/mir/rc/slim_ptr.d b/source/mir/rc/slim_ptr.d index 83f51e67..665c3471 100644 --- a/source/mir/rc/slim_ptr.d +++ b/source/mir/rc/slim_ptr.d @@ -9,13 +9,13 @@ import mir.rc.context; import mir.type_info; import std.traits; -private static immutable allocationExcMsg = "mir_slim_rcptr: out of memory error."; -private static immutable getExcMsg = "mir_slim_rcptr: trying to use null value."; +package static immutable allocationExcMsg = "mir_slim_rcptr: out of memory error."; +package static immutable getExcMsg = "mir_slim_rcptr: trying to use null value."; version (D_Exceptions) { import core.exception: OutOfMemoryError, InvalidMemoryOperationError; - private static immutable allocationError = new OutOfMemoryError(allocationExcMsg); + package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); } /++ @@ -23,8 +23,6 @@ Thread safe reference counting array. This implementation does not support polymorphism. -`__xdtor` if any is used to destruct objects. - The implementation never adds roots into the GC. +/ struct mir_slim_rcptr(T) @@ -34,27 +32,27 @@ struct mir_slim_rcptr(T) /// static if (is(T == class) || is(T == interface)) - private Unqual!T _value; + package Unqual!T _value; else - private T* _value; + package T* _value; - private ref inout(mir_rc_context) context() inout scope return pure nothrow @nogc @trusted @property + package ref mir_rc_context context() inout return scope pure nothrow @nogc @trusted @property { assert(_value); - return (cast(inout(mir_rc_context)*)_value)[-1]; + return (cast(mir_rc_context*)_value)[-1]; } - private void _reset() + package void _reset() { _value = null; } - inout(void)* _thisPtr() inout scope return @trusted @property + inout(void)* _thisPtr() inout return scope @trusted @property { return cast(inout(void)*) _value; } - private alias ThisTemplate = .mir_slim_rcptr; + package alias ThisTemplate = .mir_slim_rcptr; /// ditto alias opUnary(string op : "*") = _get_value; @@ -64,7 +62,7 @@ struct mir_slim_rcptr(T) static if (is(T == class) || is(T == interface)) /// pragma(inline, true) - inout(T) _get_value() scope inout @property + inout(T) _get_value() return scope inout @property { assert(this, getExcMsg); return _value; @@ -72,7 +70,7 @@ struct mir_slim_rcptr(T) else /// pragma(inline, true) - ref inout(T) _get_value() scope inout @property + ref inout(T) _get_value() return scope inout @property { assert(this, getExcMsg); return *_value; @@ -86,18 +84,59 @@ struct mir_slim_rcptr(T) rhs._value = t; } + /// + this(typeof(null)) + { + } + /// mixin CommonRCImpl; + + /// + pragma(inline, true) + bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc + { + return !this; + } + + /// ditto + bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc + { + if (_thisPtr is null) + return rhs._thisPtr is null; + if (rhs._thisPtr is null) + return false; + return _get_value == rhs._get_value; + } + + /// + auto opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc + { + if (_thisPtr is null) + return (rhs._thisPtr is null) - 1; + if (rhs._thisPtr is null) + return 1; + return _get_value.opCmp(rhs._get_value); + } + + /// + size_t toHash() @trusted scope const pure nothrow @nogc + { + if (_thisPtr is null) + return 0; + return hashOf(_get_value); + } + /// ~this() nothrow { - static if (hasDestructor!T) + static if (hasElaborateDestructor!T || hasDestructor!T) { if (false) // break @safe and pure attributes { Unqual!T* object; - (*object).__xdtor(); + (*object).__xdtor; } } if (this) @@ -107,35 +146,65 @@ struct mir_slim_rcptr(T) } } - /// - this(this) scope @trusted pure nothrow @nogc + static if (is(T == const) || is(T == immutable)) + this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc { - if (this) + if (rhs) + { + this._value = cast(typeof(this._value))rhs._value; + mir_rc_increase_counter(context); + } + } + + static if (is(T == immutable)) + this(return ref scope const typeof(this) rhs) immutable @trusted pure nothrow @nogc + { + if (rhs) + { + this._value = cast(typeof(this._value))rhs._value; + mir_rc_increase_counter(context); + } + } + + static if (is(T == immutable)) + this(return ref scope const typeof(this) rhs) const @trusted pure nothrow @nogc + { + if (rhs) { + this._value = cast(typeof(this._value))rhs._value; mir_rc_increase_counter(context); } } - static if (!is(T == interface) && !__traits(isAbstractClass, T)) + this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc { - private this(Args...)(auto ref Args args) + if (rhs) { - () @trusted { - auto context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T); - if (!context) - { - version(D_Exceptions) - throw allocationError; - else - assert(0, allocationExcMsg); - } - _value = cast(typeof(_value))(context + 1); - } (); - import mir.functional: forward; - import mir.conv: emplace; - emplace!T(_value, forward!args); + this._value = rhs._value; + mir_rc_increase_counter(context); } } + + /// + ref opAssign(typeof(null)) scope return @trusted // pure nothrow @nogc + { + this = typeof(this).init; + } + + /// + ref opAssign(return typeof(this) rhs) scope return @trusted // pure nothrow @nogc + { + this.proxySwap(rhs); + return this; + } + + /// + ref opAssign(Q)(return ThisTemplate!Q rhs) scope return @trusted // pure nothrow @nogc + if (isImplicitlyConvertible!(Q*, T*)) + { + this.proxySwap(*()@trusted{return cast(typeof(this)*)&rhs;}()); + return this; + } } /// @@ -143,12 +212,27 @@ alias SlimRCPtr = mir_slim_rcptr; /// template createSlimRC(T) + if (!is(T == interface) && !__traits(isAbstractClass, T)) { /// mir_slim_rcptr!T createSlimRC(Args...)(auto ref Args args) { + typeof(return) ret; + with (ret) () @trusted { + auto context = mir_rc_create(mir_get_type_info!T, 1, mir_get_payload_ptr!T); + if (!context) + { + version(D_Exceptions) + { import mir.exception : toMutable; throw allocationError.toMutable; } + else + assert(0, allocationExcMsg); + } + _value = cast(typeof(_value))(context + 1); + } (); import mir.functional: forward; - return mir_slim_rcptr!T(forward!args); + import mir.conv: emplace; + cast(void) emplace!T(ret._value, forward!args); + return ret; } } @@ -175,6 +259,11 @@ unittest double value; ref double bar() @safe pure nothrow @nogc { return value; } this(double d) { value = d; } + + override size_t toHash() const scope @safe pure nothrow @nogc + { + return index; + } } auto a = createSlimRC!C(10); assert(a._counter == 1); @@ -210,9 +299,26 @@ unittest assert(s.e == 3); } +/// Classes with empty constructor +version(mir_test) +@safe pure @nogc nothrow +unittest +{ + static class C + { + int index = 34; + + override size_t toHash() const scope @safe pure nothrow @nogc + { + return index; + } + } + assert(createSlimRC!C.index == 34); +} + version(unittest): -private struct _test_unpure_system_dest_s__ { +package struct _test_unpure_system_dest_s__ { static int numStructs; int i; diff --git a/source/mir/serde.d b/source/mir/serde.d new file mode 100644 index 00000000..9b964ee1 --- /dev/null +++ b/source/mir/serde.d @@ -0,0 +1,2370 @@ +/++ +This implements common de/serialization routines. + +License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki + +Macros: +T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) ++/ +module mir.serde; + +import mir.functional: naryFun; +import mir.reflection; +import std.meta: AliasSeq; +import std.traits: TemplateArgsOf, EnumMembers, isAggregateType; +import mir.internal.meta: hasUDA; +import mir.reflection: getUDA; + +version (D_Exceptions) +{ + /++ + Serde Exception + +/ + class SerdeException : Exception + { + /// + this( + string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) pure nothrow @nogc @safe + { + super(msg, file, line, next); + } + + /// + this( + string msg, + Throwable next, + string file = __FILE__, + size_t line = __LINE__, + ) pure nothrow @nogc @safe + { + this(msg, file, line, next); + } + + SerdeException toMutable() @trusted pure nothrow @nogc const + { + return cast() this; + } + + alias toMutable this; + } + + /++ + Serde Exception with formatting support + +/ + class SerdeMirException : SerdeException + { + import mir.exception: MirThrowableImpl, mirExceptionInitilizePayloadImpl; + + enum maxMsgLen = 447; + + /// + mixin MirThrowableImpl; + } +} + +/++ +Constructs annotated type. ++/ +template SerdeAnnotated(T, string annotation) +{ + /// + @serdeAlgebraicAnnotation(annotation) + @serdeProxy!T + struct SerdeAnnotated + { + /// + T value; + /// + alias value this; + } +} + +/++ +Helper enumeration for for serializer . +Use negative `int` values for user defined targets. ++/ +enum SerdeTarget : int +{ + /// + ion, + /// + json, + /// + cbor, + /// + msgpack, + /// + yaml, + /// + csv, + /// + excel, + /// + bloomberg, + /// + typedJson, +} + +/++ +Attribute for key overloading during Serialization and Deserialization. +The first argument overloads the key value during serialization unless `serdeKeyOut` is given. ++/ +struct serdeKeys +{ + /// + immutable(string)[] keys; + +@trusted pure nothrow @nogc: + /// + this(immutable(string)[] keys...) { this.keys = keys; } +} + +/++ +Attribute for key overloading during serialization. ++/ +struct serdeKeyOut +{ + /// + string key; + +@safe pure nothrow @nogc: + /// + this(string key) { this.key = key; } +} + +/++ +The attribute should be used as a hint for scripting languages to register type deserializer in the type system. + +The attribute should be applied to a type definition. ++/ +enum serdeRegister; + +/++ +The attribute can be applied to a string-like member that should be de/serialized as an annotation / attribute. + +Also, the attribute can be applied on a type to denote that the type should be used to de/serialize annotated value. + +This feature is used in $(MIR_PACKAGE mir-ion). ++/ +enum serdeAnnotation; + +/++ +Checks if the type marked with $(LREF serdeAnnotation). ++/ +template isAnnotated(T) +{ + import mir.serde: serdeAnnotation; + static if (is(T == enum) || isAggregateType!T) { + enum isAnnotated = hasUDA!(T, serdeAnnotation); + static if (isAnnotated) + static assert(__traits(getAliasThis, T).length == 1 || __traits(hasMember, T, "value"), "@serdeAnnotation " ~ T.stringof ~" requires alias this member or `value` member."); + } + else + enum isAnnotated = false; +} + +private template serdeIsAnnotationMemberIn(T) +{ + enum bool serdeIsAnnotationMemberIn(string member) + = hasUDA!(T, member, serdeAnnotation) + && !hasUDA!(T, member, serdeIgnore) + && !hasUDA!(T, member, serdeIgnoreIn); +} + +/++ ++/ +template serdeGetAnnotationMembersIn(T) +{ + import std.meta: aliasSeqOf, Filter; + static if (isAggregateType!T) + enum string[] serdeGetAnnotationMembersIn = [Filter!(serdeIsAnnotationMemberIn!T, aliasSeqOf!(DeserializableMembers!T))]; + else + enum string[] serdeGetAnnotationMembersIn = null; +} + + +/// +version(mir_test) unittest +{ + struct S + { + double data; + + @serdeAnnotation + string a; + @serdeAnnotation @serdeIgnoreIn + string b; + @serdeAnnotation @serdeIgnoreOut + string c; + @serdeAnnotation @serdeIgnore + string d; + } + + static assert(serdeGetAnnotationMembersIn!int == []); + static assert(serdeGetAnnotationMembersIn!S == ["a", "c"]); +} + +private template serdeIsAnnotationMemberOut(T) +{ + enum bool serdeIsAnnotationMemberOut(string member) + = hasUDA!(T, member, serdeAnnotation) + && !hasUDA!(T, member, serdeIgnore) + && !hasUDA!(T, member, serdeIgnoreOut); +} + +/++ ++/ +template serdeGetAnnotationMembersOut(T) +{ + import std.meta: aliasSeqOf, Filter; + import mir.ndslice.topology; + static if (isAggregateType!T) + enum string[] serdeGetAnnotationMembersOut = [Filter!(serdeIsAnnotationMemberOut!T, aliasSeqOf!(SerializableMembers!T))]; + else + enum string[] serdeGetAnnotationMembersOut = null; +} + +/// +version(mir_test) unittest +{ + struct S + { + double data; + + @serdeAnnotation + string a; + @serdeAnnotation @serdeIgnoreIn + string b; + @serdeAnnotation @serdeIgnoreOut + string c; + @serdeAnnotation @serdeIgnore + string d; + @serdeAnnotation enum string e = "e"; + static @serdeAnnotation string f() @safe pure nothrow @nogc @property { + return "f"; + } + } + + static assert(serdeGetAnnotationMembersOut!int == []); + static assert(serdeGetAnnotationMembersOut!S == ["a", "b", "f"]); +} + +/++ +An annotation / attribute for algebraic types deserialization. + +This feature is used in $(MIR_PACKAGE mir-ion) for $(GMREF mir-core, mir,algebraic). ++/ +struct serdeAlgebraicAnnotation +{ + /// + string annotation; + +@safe pure nothrow @nogc: + /// + this(string annotation) { this.annotation = annotation; } +} + +/++ ++/ +template serdeHasAlgebraicAnnotation(T) +{ + static if (isAggregateType!T || is(T == enum)) + { + static if (hasUDA!(T, serdeAlgebraicAnnotation)) + { + enum serdeHasAlgebraicAnnotation = true; + } + else + { + enum serdeHasAlgebraicAnnotation = false; + } + } + else + { + enum serdeHasAlgebraicAnnotation = false; + } +} + +/++ ++/ +template serdeGetAlgebraicAnnotation(T) +{ + static if (hasUDA!(T, serdeAlgebraicAnnotation)) + { + enum string serdeGetAlgebraicAnnotation = getUDA!(T, serdeAlgebraicAnnotation).annotation; + } + else + { + private __gshared T* aggregate; + alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); + enum serdeGetAlgebraicAnnotation = .serdeGetAlgebraicAnnotation!A; + } +} + +/++ +User defined attribute used to attach a function that returns a deserialization delegate. + +The attribute is usefull for scripting languages and dynamic algebraic types. ++/ +template serdeDynamicAlgebraic(alias getAlgebraicDeserializerByAnnotation) +{ + enum serdeDynamicAlgebraic; +} + +/// +version(mir_test) +unittest +{ + static struct _global + { + alias Deserializer = S delegate(string s, ubyte[] data) @safe pure; + Deserializer getDeserializer(string name) { return map[name]; } + Deserializer[string] map; + + @serdeDynamicAlgebraic!getDeserializer + struct S {} + + static assert(serdeIsDynamicAlgebraic!S); + static assert(__traits(isSame, serdeGetAlgebraicDeserializer!S, getDeserializer)); + } +} + +/++ ++/ +template serdeIsDynamicAlgebraic(T) +{ + static if (isAggregateType!T) + { + static if (hasUDA!(T, serdeDynamicAlgebraic)) + { + enum serdeIsDynamicAlgebraic = true; + } + else + static if (__traits(getAliasThis, T).length) + { + private __gshared T* aggregate; + alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); + enum serdeIsDynamicAlgebraic = .serdeIsDynamicAlgebraic!A; + } + else + { + enum serdeIsDynamicAlgebraic = false; + } + } + else + { + enum serdeIsDynamicAlgebraic = false; + } +} + +/++ ++/ +template serdeGetAlgebraicDeserializer(T) +{ + static if (hasUDA!(T, serdeDynamicAlgebraic)) + { + alias serdeGetAlgebraicDeserializer = TemplateArgsOf!(getUDA!(T, serdeDynamicAlgebraic))[0]; + } + else + { + private __gshared T* aggregate; + alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); + alias serdeGetAlgebraicDeserializer = .serdeGetAlgebraicDeserializer!A; + } +} + +/++ +Returns: + immutable array of the input keys for the symbol or enum value ++/ +template serdeGetKeysIn(alias symbol) +{ + static if (hasUDA!(symbol, serdeAnnotation) || hasUDA!(symbol, serdeIgnore) || hasUDA!(symbol, serdeIgnoreIn)) + enum immutable(string)[] serdeGetKeysIn = null; + else + static if (hasUDA!(symbol, serdeKeys)) + enum immutable(string)[] serdeGetKeysIn = getUDA!(symbol, serdeKeys).keys; + else + enum immutable(string)[] serdeGetKeysIn = [__traits(identifier, symbol)]; +} + +// ditto +template serdeGetKeysIn(T, string member) +{ + static if (hasUDA!(T, member, serdeAnnotation) || hasUDA!(T, member, serdeIgnore) || hasUDA!(T, member, serdeIgnoreIn)) + enum immutable(string)[] serdeGetKeysIn = null; + else + static if (hasUDA!(T, member, serdeKeys)) + enum immutable(string)[] serdeGetKeysIn = getUDA!(T, member, serdeKeys).keys; + else + enum immutable(string)[] serdeGetKeysIn = [member]; +} + +/// ditto +immutable(string)[] serdeGetKeysIn(T)(const T value) @trusted pure nothrow @nogc + if (is(T == enum)) +{ + foreach (i, member; EnumMembers!T) + {{ + alias all = __traits(getAttributes, EnumMembers!T[i]); + }} + + import std.meta: staticMap; + static immutable ret = [staticMap!(.serdeGetKeysIn, EnumMembers!T)]; + import mir.enums: getEnumIndex; + uint index = void; + if (getEnumIndex(value, index)) + return ret[index]; + assert(0); +} + +/// +version(mir_test) unittest +{ + struct S + { + int f; + + @serdeKeys("D", "t") + int d; + + @serdeIgnore + int i; + + @serdeIgnoreIn + int ii; + + @serdeIgnoreOut + int io; + + void p(int) @property {} + } + + static assert(serdeGetKeysIn!(S.f) == ["f"]); + static assert(serdeGetKeysIn!(S.d) == ["D", "t"]); + static assert(serdeGetKeysIn!(S.i) == null); + static assert(serdeGetKeysIn!(S.ii) == null); + static assert(serdeGetKeysIn!(S.io) == ["io"]); + static assert(serdeGetKeysIn!(S.p) == ["p"]); +} + +/// +version(mir_test) unittest +{ + enum E + { + @serdeKeys("A", "alpha") + a, + @serdeKeys("B", "beta") + b, + c, + } + + static assert (serdeGetKeysIn(E.a) == ["A", "alpha"], serdeGetKeysIn(E.a)); + static assert (serdeGetKeysIn(E.c) == ["c"]); +} + +/++ +Returns: + output key for the symbol or enum value ++/ +template serdeGetKeyOut(alias symbol) +{ + static if (hasUDA!(symbol, serdeAnnotation) || hasUDA!(symbol, serdeIgnore) || hasUDA!(symbol, serdeIgnoreOut)) + enum string serdeGetKeyOut = null; + else + static if (hasUDA!(symbol, serdeKeyOut)) + enum string serdeGetKeyOut = getUDA!(symbol, serdeKeyOut).key; + else + static if (hasUDA!(symbol, serdeKeys)) + enum string serdeGetKeyOut = getUDA!(symbol, serdeKeys).keys[0]; + else + enum string serdeGetKeyOut = __traits(identifier, symbol); +} + +/// ditto +template serdeGetKeyOut(T, string member) +{ + static if (hasUDA!(T, member, serdeAnnotation) || hasUDA!(T, member, serdeIgnore) || hasUDA!(T, member, serdeIgnoreOut)) + enum string serdeGetKeyOut = null; + else + static if (hasUDA!(T, member, serdeKeyOut)) + enum string serdeGetKeyOut = getUDA!(T, member, serdeKeyOut).key; + else + static if (hasUDA!(T, member, serdeKeys)) + enum string serdeGetKeyOut = getUDA!(T, member, serdeKeys).keys[0]; + else + enum string serdeGetKeyOut = member; +} + +///ditto +@safe pure nothrow @nogc +string serdeGetKeyOut(T)(const T value) + if (is(T == enum)) +{ + foreach (i, member; EnumMembers!T) + {{ + alias all = __traits(getAttributes, EnumMembers!T[i]); + }} + + import std.meta: staticMap; + import mir.enums: getEnumIndex; + static immutable ret = [staticMap!(.serdeGetKeyOut, EnumMembers!T)]; + uint index = void; + if (getEnumIndex(value, index)) + return ret[index]; + assert(0); +} + +/// +version(mir_test) unittest +{ + struct S + { + int f; + + @serdeKeys("D", "t") + int d; + + @serdeIgnore + int i; + + @serdeIgnoreIn + int ii; + + @serdeIgnoreOut + int io; + + @serdeKeys("P") + @serdeKeyOut("") + void p(int) @property {} + } + + static assert(serdeGetKeyOut!(S.f) == "f"); + static assert(serdeGetKeyOut!(S.d) == "D"); + static assert(serdeGetKeyOut!(S.i) is null); + static assert(serdeGetKeyOut!(S.ii) == "ii"); + static assert(serdeGetKeyOut!(S.io) is null); + static assert(serdeGetKeyOut!(S.p) !is null); + static assert(serdeGetKeyOut!(S.p) == ""); +} + +/// +version(mir_test) unittest +{ + enum E + { + @serdeKeys("A", "alpha") + a, + @serdeKeys("B", "beta") + @serdeKeyOut("o") + b, + c, + } + + static assert (serdeGetKeyOut(E.a) == "A"); + static assert (serdeGetKeyOut(E.b) == "o"); + static assert (serdeGetKeyOut(E.c) == "c"); +} + +/++ +Attribute used to ignore unexpected keys during an aggregate type deserialization. ++/ +enum serdeIgnoreUnexpectedKeys; + +/++ +Attribute to ignore field. + +See_also: $(LREF serdeIgnoreIn) $(LREF serdeIgnoreOut) ++/ +enum serdeIgnore; + +/++ +Attribute to ignore field during deserialization. + +See_also: $(LREF serdeIgnoreInIfAggregate) ++/ +enum serdeIgnoreIn; + +/++ +Attribute to ignore field during serialization. ++/ +enum serdeIgnoreOut; + +/++ +Attribute to ignore a field during deserialization when equals to its default value. +Do not use it on void initialized fields or aggregates with void initialized fields, recursively. ++/ +enum serdeIgnoreDefault; + +/// +version(mir_test) unittest +{ + struct S + { + @serdeIgnoreDefault + double d = 0; // skips field if 0 during deserialization + } + + + static assert(hasUDA!(S.d, serdeIgnoreDefault)); +} + +/++ ++/ + +/++ +Serialization proxy. ++/ +struct serdeProxy(T); + +/// +version(mir_test) unittest +{ + import mir.small_string; + + struct S + { + @serdeProxy!(SmallString!32) + double d; + } + + + static assert(hasUDA!(S.d, serdeProxy)); + static assert(hasUDA!(S.d, serdeProxy!(SmallString!32))); + static assert(is(serdeGetProxy!(S.d) == SmallString!32)); +} + +/++ ++/ +alias serdeGetProxy(alias symbol) = TemplateArgsOf!(getUDA!(symbol, serdeProxy))[0]; +/// ditto +alias serdeGetProxy(T, string member) = TemplateArgsOf!(getUDA!(T, member, serdeProxy))[0]; + +/// Can be applied to @serdeProxy types to make (de)serialization use +/// underlying type through casting. Useful for enums. +enum serdeProxyCast; + +/// Equivalent to @serdeProxy!T @serdeProxyCast +alias serdeEnumProxy(T) = AliasSeq!(serdeProxy!T, serdeProxyCast); + +/++ +Attributes to conditional ignore field during serialization. + +The predicate should be aplied to the member, to the aggregate type. + +See_also: $(LREF serdeIgnoreOutIfAggregate) ++/ +struct serdeIgnoreOutIf(alias pred); + +/++ ++/ +alias serdeGetIgnoreOutIf(alias symbol) = naryFun!(TemplateArgsOf!(getUDA!(symbol, serdeIgnoreOutIf))[0]); +/// ditto +alias serdeGetIgnoreOutIf(T, string member) = naryFun!(TemplateArgsOf!(getUDA!(T, member, serdeIgnoreOutIf))[0]); + +/++ +Attributes to conditional ignore field during serialization. + +The predicate should be aplied to the aggregate value, not to the member. + +See_also: $(LREF serdeIgnoreIfAggregate) $(LREF serdeIgnoreOutIf), $(LREF serdeIgnoreInIfAggregate) ++/ +struct serdeIgnoreOutIfAggregate(alias pred); + +/++ ++/ +alias serdeGetIgnoreOutIfAggregate(alias symbol) = naryFun!(TemplateArgsOf!(getUDA!(symbol, serdeIgnoreOutIfAggregate))[0]); +/// ditto +alias serdeGetIgnoreOutIfAggregate(T, string member) = naryFun!(TemplateArgsOf!(getUDA!(T, member, serdeIgnoreOutIfAggregate))[0]); + +/++ +Attributes to conditional ignore field during deserialization. + +The attribute should be combined with $(LREF serdeRealOrderedIn) applied on the aggregate. + +See_also: $(LREF serdeIgnoreIfAggregate) $(LREF serdeIgnoreOutIfAggregate) $(LREF serdeIgnoreIn) ++/ +struct serdeIgnoreInIfAggregate(alias pred); + +/++ ++/ +alias serdeGetIgnoreInIfAggregate(alias symbol) = naryFun!(TemplateArgsOf!(getUDA!(symbol, serdeIgnoreInIfAggregate))[0]); +/// ditto +alias serdeGetIgnoreInIfAggregate(T, string member) = naryFun!(TemplateArgsOf!(getUDA!(T, member, serdeIgnoreInIfAggregate))[0]); + +/++ +Attributes to conditional ignore field during serialization and deserialization. + +The attribute should be combined with $(LREF serdeRealOrderedIn) applied on the aggregate. + +The predicate should be aplied to the aggregate value, not to the member. + +See_also: $(LREF serdeIgnoreOutIfAggregate) $(LREF serdeIgnoreInIfAggregate) $ $(LREF serdeIgnore) ++/ +struct serdeIgnoreIfAggregate(alias pred); + +/++ ++/ +alias serdeGetIgnoreIfAggregate(alias symbol) = naryFun!(TemplateArgsOf!(getUDA!(symbol, serdeIgnoreIfAggregate))[0]); +/// ditto +alias serdeGetIgnoreIfAggregate(T, string member) = naryFun!(TemplateArgsOf!(getUDA!(T, member, serdeIgnoreIfAggregate))[0]); + +/++ +Allows to use flexible deserialization rules such as conversion from input string to numeric types. ++/ +enum serdeFlexible; + +/++ +Allows serialize / deserialize fields like arrays. + +A range or a container should be iterable for serialization. +Following code should compile: +------ +foreach(ref value; yourRangeOrContainer) +{ + ... +} +------ + +`put(value)` method is used for deserialization. + +See_also: $(MREF serdeIgnoreOut), $(MREF serdeIgnoreIn) ++/ +enum serdeLikeList; + +/++ +Allows serialize / deserialize fields like objects. + +Object should have `opApply` method to allow serialization. +Following code should compile: +------ +foreach(key, value; yourObject) +{ + ... +} +------ +Object should have only one `opApply` method with 2 argument to allow automatic value type deduction. + +`opIndexAssign` or `opIndex` is used for deserialization to support required syntax: +----- +yourObject["key"] = value; +----- +Multiple value types is supported for deserialization. + +See_also: $(MREF serdeIgnoreOut), $(MREF serdeIgnoreIn) ++/ +enum serdeLikeStruct; + +/++ +The attribute is used for algebraic deserialization for types like `Variant!(string, S)` +`@serdeFallbackStruct struct S {}` ++/ +enum serdeFallbackStruct; + +/++ +Force serialize / deserialize on fields instead of Range API. ++/ +enum serdeFields; + +/++ +Ignore keys for object and enum members. +Should be applied to members or enum type itself. ++/ +enum serdeIgnoreCase; + +/// +bool hasSerdeIgnoreCase(T)(T value) + if (is(T == enum)) +{ + static if (hasUDA!(T, serdeIgnoreCase)) + { + return true; + } + else + { + foreach (i, member; EnumMembers!T) + { + alias all = __traits(getAttributes, EnumMembers!T[i]); + if (value == member) + return hasUDA!(EnumMembers!T[i], serdeIgnoreCase); + } + assert(0); + } +} + +/// +version(mir_test) unittest +{ + enum E + { + @serdeIgnoreCase + a, + b, + @serdeIgnoreCase + c, + d, + } + + static assert(hasSerdeIgnoreCase(E.a)); + static assert(!hasSerdeIgnoreCase(E.b)); + static assert(hasSerdeIgnoreCase(E.c)); + static assert(!hasSerdeIgnoreCase(E.d)); +} + +/// +version(mir_test) unittest +{ + @serdeIgnoreCase + enum E + { + a, + b, + c, + d, + } + + static assert(hasSerdeIgnoreCase(E.a)); + static assert(hasSerdeIgnoreCase(E.b)); + static assert(hasSerdeIgnoreCase(E.c)); + static assert(hasSerdeIgnoreCase(E.d)); +} + +/++ +Can be applied only to strings fields. +Does not allocate new data when deserializeing. Raw data is used for strings instead of new memory allocation. +Use this attributes only for strings or arrays that would not be used after deallocation. ++/ +enum serdeScoped; + +/++ +Attribute that force deserializer to throw an exception that the field hasn't been not found in the input. ++/ +enum serdeRequired; + +/++ +Attribute that allow deserializer to do not throw an exception if the field hasn't been not found in the input. ++/ +enum serdeOptional; + +/++ +Attribute that allow deserializer to don't throw an exception that the field matches multiple keys in the object. ++/ +enum serdeAllowMultiple; + +/++ +Attributes for in transformation. +Return type of in transformation must be implicitly convertable to the type of the field. +In transformation would be applied after serialization proxy if any. + ++/ +struct serdeTransformIn(alias fun) {} + +/++ +Returns: unary function of underlaying alias of $(LREF serdeTransformIn) ++/ +alias serdeGetTransformIn(alias value) = naryFun!(TemplateArgsOf!(getUDA!(value, serdeTransformIn))[0]); +/// ditto +alias serdeGetTransformIn(T, string member) = naryFun!(TemplateArgsOf!(getUDA!(T, member, serdeTransformIn))[0]); + +/++ +Attributes for out transformation. +Return type of out transformation may be differ from the type of the field. +Out transformation would be applied before serialization proxy if any. ++/ +struct serdeTransformOut(alias fun) {} + +/++ +Returns: unary function of underlaying alias of $(LREF serdeTransformOut) ++/ +alias serdeGetTransformOut(alias value) = naryFun!(TemplateArgsOf!(getUDA!(value, serdeTransformOut))[0]); +/// ditto +alias serdeGetTransformOut(T, string member) = naryFun!(TemplateArgsOf!(getUDA!(T, member, serdeTransformOut))[0]); + +/++ ++/ +bool serdeParseEnum(E)(scope const char[] str, scope ref E res) + @safe pure nothrow @nogc + if (is(E == enum)) +{ + import mir.enums: getEnumIndexFromKey, unsafeEnumFromIndex; + import mir.utility: _expect; + + uint index = void; + if (getEnumIndexFromKey!(E, hasUDA!(E, serdeIgnoreCase), serdeGetKeysIn)(str, index)._expect(true)) + { + res = unsafeEnumFromIndex!E(index); + return true; + } + return false; +} + +version(D_Exceptions) +/// ditto +auto serdeParseEnum(E)(scope const char[] str) + @safe pure + if (is(E == enum)) +{ + import mir.utility: max; + E ret; + if (.serdeParseEnum(str, ret)) + return ret; + import mir.exception: MirException; + throw new MirException("Can't deserialzie ", E.stringof, " from string", str[0 .. max($, 128u)]); +} + +/// +version(mir_test) unittest +{ + enum E + { + @serdeKeys("A", "alpha") + a, + @serdeKeys("B", "beta") + b, + c, + } + + auto e = E.c; + assert(serdeParseEnum("A", e)); + assert(e == E.a); + assert(serdeParseEnum("alpha", e)); + assert(e == E.a); + assert(serdeParseEnum("beta", e)); + assert(e == E.b); + assert("B".serdeParseEnum!E == E.b); + assert("c".serdeParseEnum!E == E.c); + + assert(!serdeParseEnum("C", e)); + assert(!serdeParseEnum("Alpha", e)); +} + +/// Case insensitive +version(mir_test) unittest +{ + @serdeIgnoreCase // supported for the whole type + enum E + { + @serdeKeys("A", "alpha") + a, + @serdeKeys("B", "beta") + b, + c, + } + + auto e = E.c; + assert(serdeParseEnum("a", e)); + assert(e == E.a); + assert(serdeParseEnum("alpha", e)); + assert(e == E.a); + assert(serdeParseEnum("BETA", e)); + assert(e == E.b); + assert(serdeParseEnum("b", e)); + assert(e == E.b); + assert(serdeParseEnum("C", e)); + assert(e == E.c); +} + +/++ +Deserialization member type ++/ +template serdeDeserializationMemberType(T, string member) +{ + import std.traits: Unqual, Parameters; + private __gshared T* aggregate; + static if (hasField!(T, member)) + { + alias serdeDeserializationMemberType = typeof(__traits(getMember, *aggregate, member)); + } + else + static if (__traits(compiles, &__traits(getMember, *aggregate, member)()) || __traits(getOverloads, *aggregate, member).length > 1) + { + alias serdeDeserializationMemberType = typeof(__traits(getMember, *aggregate, member)()); + } + else + { + alias serdeDeserializationMemberType = Unqual!(Parameters!(__traits(getMember, *aggregate, member))[0]); + } +} + +/// ditto +template serdeDeserializationMemberType(T) +{ + /// + alias serdeDeserializationMemberType(string member) = .serdeDeserializationMemberType!(T, member); +} + + +/++ +Is deserializable member ++/ +template serdeIsDeserializable(T) +{ + /// + enum bool serdeIsDeserializable(string member) = serdeGetKeysIn!(T, member).length > 0; +} + +/// +version(mir_test) unittest +{ + + static struct S + { + @serdeIgnore + int i; + + @serdeKeys("a", "b") + int a; + } + + alias serdeIsDeserializableInS = serdeIsDeserializable!S; + static assert (!serdeIsDeserializableInS!"i"); + static assert (serdeIsDeserializableInS!"a"); +} + +/++ +Serialization member type ++/ +template serdeSerializationMemberType(T, string member) +{ + import std.traits: Unqual, Parameters; + private __gshared T* aggregate; + static if (hasField!(T, member)) + { + alias serdeSerializationMemberType = typeof(__traits(getMember, *aggregate, member)); + } + else + { + alias serdeSerializationMemberType = typeof(__traits(getMember, *aggregate, member)()); + } +} + +/// ditto +template serdeSerializationMemberType(T) +{ + /// + alias serdeSerializationMemberType(string member) = .serdeSerializationMemberType!(T, member); +} + + +/++ +Is deserializable member ++/ +template serdeIsSerializable(T) +{ + /// + enum bool serdeIsSerializable(string member) = serdeGetKeyOut!(T, member) !is null; +} + +/// +version(mir_test) unittest +{ + + static struct S + { + @serdeIgnore + int i; + + @serdeKeys("a", "b") + int a; + } + + alias serdeIsSerializableInS = serdeIsSerializable!S; + static assert (!serdeIsSerializableInS!"i"); + static assert (serdeIsSerializableInS!"a"); +} + +/++ +Final proxy type ++/ +template serdeGetFinalProxy(T) +{ + import mir.timestamp: Timestamp; + import std.traits: isAggregateType; + static if (isAggregateType!T || is(T == enum)) + { + static if (hasUDA!(T, serdeProxy)) + { + alias serdeGetFinalProxy = .serdeGetFinalProxy!(serdeGetProxy!T); + } + else + static if (isAggregateType!T && is(typeof(Timestamp(T.init))) && __traits(getAliasThis, T).length == 0) + { + alias serdeGetFinalProxy = string; + } + else + { + alias serdeGetFinalProxy = T; + } + } + else + { + alias serdeGetFinalProxy = T; + } +} + +/// +version(mir_test) unittest +{ + + @serdeProxy!string + static struct A {} + + @serdeProxy!A + static struct B {} + + @serdeProxy!B + static struct C {} + + static assert (is(serdeGetFinalProxy!C == string), serdeGetFinalProxy!C.stringof); + static assert (is(serdeGetFinalProxy!string == string)); +} + +/++ +Final deep proxy type ++/ +template serdeGetFinalDeepProxy(T) +{ + import mir.timestamp: Timestamp; + import std.traits: Unqual, isAggregateType, isArray, ForeachType; + static if (isAggregateType!T || is(T == enum)) + { + static if (hasUDA!(T, serdeProxy)) + { + alias serdeGetFinalDeepProxy = .serdeGetFinalDeepProxy!(serdeGetProxy!T); + } + else + static if (isAggregateType!T && is(typeof(Timestamp(T.init)))) + { + alias serdeGetFinalDeepProxy = string; + } + else + static if (__traits(hasMember, T, "serdeKeysProxy")) + { + alias serdeGetFinalDeepProxy = .serdeGetFinalDeepProxy!(T.serdeKeysProxy); + } + else + // static if (is(T == enum)) + // { + // alias serdeGetFinalDeepProxy = typeof(null); + // } + // else + { + alias serdeGetFinalDeepProxy = T; + } + } + else + static if (isArray!T) + { + alias E = Unqual!(ForeachType!T); + static if (isAggregateType!E || is(E == enum)) + alias serdeGetFinalDeepProxy = .serdeGetFinalDeepProxy!E; + else + alias serdeGetFinalDeepProxy = T; + } + else + static if (is(immutable T == immutable V[K], K, V)) + { + alias E = serdeGetFinalDeepProxy!(Unqual!V); + static if (isAggregateType!E || is(E == enum)) + alias serdeGetFinalDeepProxy = E; + else + alias serdeGetFinalDeepProxy = T; + } + else + { + alias serdeGetFinalDeepProxy = T; + } +} + +/// +version(mir_test) unittest +{ + + @serdeProxy!string + static struct A {} + + enum E {a,b,c} + + @serdeProxy!(A[E]) + static struct B {} + + @serdeProxy!(B[]) + static struct C {} + + static assert (is(serdeGetFinalDeepProxy!C == A[E])); + static assert (is(serdeGetFinalDeepProxy!string == string)); +} + +/++ +Final proxy type deserializable members ++/ +template serdeFinalProxyDeserializableMembers(T) +{ + import std.meta: Filter, aliasSeqOf; + alias P = serdeGetFinalProxy!T; + static if (isAggregateType!P || is(P == enum)) + enum string[] serdeFinalProxyDeserializableMembers = [Filter!(serdeIsDeserializable!P, aliasSeqOf!(DeserializableMembers!P))]; + else + // static if (is(P == enum)) + // enum string[] serdeFinalProxyDeserializableMembers = serdeGetKeysIn!P; + // else + enum string[] serdeFinalProxyDeserializableMembers = null; +} + +/// +version(mir_test) unittest +{ + + static struct A + { + @serdeIgnore + int i; + + @serdeKeys("a", "b") + int m; + } + + @serdeProxy!A + static struct B {} + + @serdeProxy!B + static struct C {} + + static assert (serdeFinalProxyDeserializableMembers!C == ["m"]); +} + +/++ +Final deep proxy type serializable members ++/ +template serdeFinalDeepProxySerializableMembers(T) +{ + import std.traits: isAggregateType; + import std.meta: Filter, aliasSeqOf; + alias P = serdeGetFinalDeepProxy!T; + static if (isAggregateType!P || is(P == enum)) + enum string[] serdeFinalDeepProxySerializableMembers = [Filter!(serdeIsSerializable!P, aliasSeqOf!(SerializableMembers!P))]; + else + // static if (is(P == enum)) + // enum string[] serdeFinalDeepProxySerializableMembers = [serdeGetKeyOut!P]; + // else + enum string[] serdeFinalDeepProxySerializableMembers = null; +} + +/// +version(mir_test) unittest +{ + + static struct A + { + @serdeIgnore + int i; + + @serdeKeys("a", "b") + int m; + } + + @serdeProxy!(A[string]) + static struct B {} + + @serdeProxy!(B[]) + static struct C {} + + static assert (serdeFinalDeepProxySerializableMembers!C == ["m"]); +} + +/++ +Final proxy type deserializable members ++/ +template serdeFinalProxySerializableMembers(T) +{ + import std.meta: Filter, aliasSeqOf; + alias P = serdeGetFinalProxy!T; + static if (isAggregateType!P || is(P == enum)) + enum string[] serdeFinalProxySerializableMembers = [Filter!(serdeIsSerializable!P, aliasSeqOf!(SerializableMembers!P))]; + else + // static if (is(P == enum)) + // enum string[] serdeFinalProxySerializableMembers = [serdeGetKeyOut!P]; + // else + enum string[] serdeFinalProxySerializableMembers = null; +} + +/// +version(mir_test) unittest +{ + + static struct A + { + @serdeIgnore + int i; + + @serdeKeys("a", "b") + int m; + } + + @serdeProxy!A + static struct B {} + + @serdeProxy!B + static struct C {} + + static assert (serdeFinalProxySerializableMembers!C == ["m"]); +} + +/++ +Final deep proxy type serializable members ++/ +template serdeFinalDeepProxyDeserializableMembers(T) +{ + import std.traits: isAggregateType; + import std.meta: Filter, aliasSeqOf; + alias P = serdeGetFinalDeepProxy!T; + static if (isAggregateType!P || is(P == enum)) + enum string[] serdeFinalDeepProxyDeserializableMembers = [Filter!(serdeIsDeserializable!P, aliasSeqOf!(DeserializableMembers!P))]; + else + // static if (is(P == enum)) + // enum string[] serdeFinalDeepProxyDeserializableMembers = serdeGetKeysIn!P; + // else + enum string[] serdeFinalDeepProxyDeserializableMembers = null; +} + +/// +version(mir_test) unittest +{ + static struct A + { + @serdeIgnore + int i; + + @serdeKeys("a", "b") + int m; + } + + @serdeProxy!(A[string]) + static struct B {} + + @serdeProxy!(B[]) + static struct C {} + + static assert (serdeFinalDeepProxyDeserializableMembers!C == ["m"]); +} + +/++ +Deserialization member final proxy type ++/ +template serdeFinalDeserializationMemberType(T, string member) +{ + static if (hasUDA!(T, member, serdeProxy)) + { + alias serdeFinalDeserializationMemberType = serdeGetFinalProxy!(serdeGetProxy!(T, member)); + } + else + { + alias serdeFinalDeserializationMemberType = serdeGetFinalProxy!(serdeDeserializationMemberType!(T, member)); + } +} + +/// ditto +template serdeFinalDeserializationMemberType(T) +{ + /// + alias serdeFinalDeserializationMemberType(string member) = .serdeFinalDeserializationMemberType!(T, member); +} + +/// +version(mir_test) unittest +{ + + static struct A + { + + } + + @serdeProxy!A + static struct B {} + + @serdeProxy!B + static struct C {} + + + @serdeProxy!double + struct E {} + + struct D + { + C c; + + @serdeProxy!E + int d; + } + + static assert (is(serdeFinalDeserializationMemberType!(D, "c") == A)); + static assert (is(serdeFinalDeserializationMemberType!(D, "d") == double)); +} + +/++ +Deserialization members final proxy types ++/ +template serdeDeserializationFinalProxyMemberTypes(T) +{ + import std.meta: NoDuplicates, staticMap, aliasSeqOf; + alias serdeDeserializationFinalProxyMemberTypes = NoDuplicates!(staticMap!(serdeGetFinalProxy, staticMap!(serdeFinalDeserializationMemberType!T, aliasSeqOf!(serdeFinalProxyDeserializableMembers!T)))); +} + +/// +version(mir_test) unittest +{ + + static struct A {} + + @serdeProxy!A + static struct B {} + + @serdeProxy!B + static struct C {} + + @serdeProxy!B + static struct E {} + + static struct D + { + C c; + + @serdeProxy!E + int d; + } + + import std.meta: AliasSeq; + static assert (is(serdeDeserializationFinalProxyMemberTypes!D == AliasSeq!A)); +} + +/++ +Serialization member final proxy type ++/ +template serdeFinalSerializationMemberType(T, string member) +{ + static if (hasUDA!(T, member, serdeProxy)) + { + alias serdeFinalSerializationMemberType = serdeGetFinalProxy!(serdeGetProxy!(T, member)); + } + else + { + alias serdeFinalSerializationMemberType = serdeGetFinalProxy!(serdeSerializationMemberType!(T, member)); + } +} + +/// ditto +template serdeFinalSerializationMemberType(T) +{ + /// + alias serdeFinalSerializationMemberType(string member) = .serdeFinalSerializationMemberType!(T, member); +} + +/// +version(mir_test) unittest +{ + + static struct A + { + + } + + @serdeProxy!A + static struct B {} + + @serdeProxy!B + static struct C {} + + + @serdeProxy!double + struct E {} + + struct D + { + C c; + + @serdeProxy!E + int d; + } + + static assert (is(serdeFinalSerializationMemberType!(D, "c") == A), serdeFinalSerializationMemberType!(D, "c")); + static assert (is(serdeFinalSerializationMemberType!(D, "d") == double)); +} + +/++ +Serialization members final proxy types ++/ +template serdeSerializationFinalProxyMemberTypes(T) +{ + import std.meta: NoDuplicates, staticMap, aliasSeqOf; + alias serdeSerializationFinalProxyMemberTypes = NoDuplicates!(staticMap!(serdeGetFinalProxy, staticMap!(serdeFinalSerializationMemberType!T, aliasSeqOf!(serdeFinalProxySerializableMembers!T)))); +} + +/// +version(mir_test) unittest +{ + + static struct A {} + + @serdeProxy!A + static struct B {} + + @serdeProxy!B + static struct C {} + + @serdeProxy!B + static struct E {} + + static struct D + { + C c; + + @serdeProxy!E + int d; + } + + import std.meta: AliasSeq; + static assert (is(serdeSerializationFinalProxyMemberTypes!D == AliasSeq!A)); +} + +/++ +Deserialization members final deep proxy types ++/ +template serdeDeserializationFinalDeepProxyMemberTypes(T) +{ + import std.meta: NoDuplicates, staticMap, aliasSeqOf; + import mir.algebraic: isVariant; + static if (isVariant!T) + alias serdeDeserializationFinalDeepProxyMemberTypes = NoDuplicates!(T, staticMap!(serdeGetFinalDeepProxy, T.AllowedTypes)); + else + static if (isAlgebraicAliasThis!T) + { + private __gshared T* aggregate; + alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); + alias serdeDeserializationFinalDeepProxyMemberTypes = .serdeDeserializationFinalDeepProxyMemberTypes!A; + } + else + alias serdeDeserializationFinalDeepProxyMemberTypes = NoDuplicates!(staticMap!(serdeGetFinalDeepProxy, staticMap!(serdeFinalDeserializationMemberType!T, aliasSeqOf!(serdeFinalDeepProxyDeserializableMembers!T)))); +} + +/// +version(mir_test) unittest +{ + + static struct A {} + + @serdeProxy!(A[]) + static struct B {} + + enum R {a, b, c} + + @serdeProxy!(B[R]) + static struct C {} + + @serdeProxy!(B[string]) + static struct E {} + + static struct D + { + C c; + + @serdeProxy!E + int d; + } + + import std.meta: AliasSeq; + static assert (is(serdeDeserializationFinalDeepProxyMemberTypes!D == AliasSeq!A), serdeDeserializationFinalDeepProxyMemberTypes!D); +} + +/++ +Serialization members final deep proxy types ++/ +template serdeSerializationFinalDeepProxyMemberTypes(T) +{ + import std.meta: NoDuplicates, staticMap, aliasSeqOf; + import mir.algebraic: isVariant; + static if (isVariant!T) + alias serdeSerializationFinalDeepProxyMemberTypes = NoDuplicates!(T, staticMap!(serdeGetFinalDeepProxy, T.AllowedTypes)); + else + static if (isAlgebraicAliasThis!T) + { + private __gshared T* aggregate; + alias A = typeof(__traits(getMember, aggregate, __traits(getAliasThis, T))); + alias serdeSerializationFinalDeepProxyMemberTypes = .serdeSerializationFinalDeepProxyMemberTypes!A; + } + else + alias serdeSerializationFinalDeepProxyMemberTypes = NoDuplicates!(staticMap!(serdeGetFinalDeepProxy, staticMap!(serdeFinalSerializationMemberType!T, aliasSeqOf!(serdeFinalDeepProxySerializableMembers!T)))); +} + +/// +version(mir_test) unittest +{ + + static struct A {} + + @serdeProxy!(A[]) + static struct B {} + + enum R {a, b, c} + + @serdeProxy!(B[R]) + static struct C {} + + @serdeProxy!(B[string]) + static struct E {} + + static struct D + { + C c; + + @serdeProxy!E + int d; + } + + import std.meta: AliasSeq; + static assert (is(serdeSerializationFinalDeepProxyMemberTypes!D == AliasSeq!A), serdeSerializationFinalDeepProxyMemberTypes!D); +} + +private template serdeDeserializationFinalProxyMemberTypesRecurseImpl(T...) +{ + import std.meta: NoDuplicates, staticMap; + alias F = NoDuplicates!(T, staticMap!(serdeDeserializationFinalProxyMemberTypes, T)); + static if (F.length == T.length) + alias serdeDeserializationFinalProxyMemberTypesRecurseImpl = T; + else + alias serdeDeserializationFinalProxyMemberTypesRecurseImpl = .serdeDeserializationFinalProxyMemberTypesRecurseImpl!F; +} + +/++ +Deserialization members final proxy types (recursive) ++/ +alias serdeDeserializationFinalProxyMemberTypesRecurse(T) = serdeDeserializationFinalProxyMemberTypesRecurseImpl!(serdeGetFinalProxy!T); + +/// +version(mir_test) unittest +{ + + static struct A { double g; } + + @serdeProxy!A + static struct B {} + + @serdeProxy!B + static struct C {} + + @serdeProxy!B + static struct E {} + + static struct D + { + C c; + + @serdeProxy!E + int d; + } + + @serdeProxy!D + static struct F {} + + import std.meta: AliasSeq; + static assert (is(serdeDeserializationFinalProxyMemberTypesRecurse!F == AliasSeq!(D, A, double))); +} + +private template serdeSerializationFinalDeepProxyMemberTypesRecurseImpl(T...) +{ + import std.meta: NoDuplicates, staticMap; + alias F = NoDuplicates!(T, staticMap!(serdeSerializationFinalDeepProxyMemberTypes, T)); + static if (F.length == T.length) + alias serdeSerializationFinalDeepProxyMemberTypesRecurseImpl = T; + else + alias serdeSerializationFinalDeepProxyMemberTypesRecurseImpl = .serdeSerializationFinalDeepProxyMemberTypesRecurseImpl!F; +} + +private template serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl(T...) +{ + import std.meta: NoDuplicates, staticMap; + alias F = NoDuplicates!(T, staticMap!(serdeDeserializationFinalDeepProxyMemberTypes, T)); + static if (F.length == T.length) + alias serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl = T; + else + alias serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl = .serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl!F; +} + +/++ +Deserialization members final deep proxy types (recursive) ++/ +alias serdeDeserializationFinalDeepProxyMemberTypesRecurse(T) = serdeDeserializationFinalDeepProxyMemberTypesRecurseImpl!(serdeGetFinalDeepProxy!T); + +/// +version(mir_test) unittest +{ + + static struct A { double g; } + + @serdeProxy!(A[]) + static struct B {} + + @serdeProxy!(B[string]) + static struct C {} + + @serdeProxy!B + static struct E {} + + static struct D + { + C c; + + @serdeProxy!(E[]) + int d; + } + + @serdeProxy!D + static struct F {} + + import std.meta: AliasSeq; + static assert (is(serdeDeserializationFinalDeepProxyMemberTypesRecurse!F == AliasSeq!(D, A, double))); +} + +/++ +Serialization members final deep proxy types (recursive) ++/ +alias serdeSerializationFinalDeepProxyMemberTypesRecurse(T) = serdeSerializationFinalDeepProxyMemberTypesRecurseImpl!(serdeGetFinalDeepProxy!T); + +/// +version(mir_test) unittest +{ + + static struct A { double g; } + + @serdeProxy!(A[]) + static struct B {} + + @serdeProxy!(B[string]) + static struct C {} + + @serdeProxy!B + static struct E {} + + static struct D + { + C c; + + @serdeProxy!(E[]) + int d; + } + + @serdeProxy!D + static struct F {} + + import std.meta: AliasSeq; + static assert (is(serdeSerializationFinalDeepProxyMemberTypesRecurse!F == AliasSeq!(D, A, double)), serdeSerializationFinalDeepProxyMemberTypesRecurse!F); +} + +package string[] sortUniqKeys()(string[] keys) + @safe pure nothrow +{ + import mir.algorithm.iteration: uniq; + import mir.array.allocation: array; + import mir.ndslice.sorting: sort; + + return keys + .sort!((a, b) { + if (sizediff_t d = a.length - b.length) + return d < 0; + return a < b; + }) + .uniq + .array; +} + + +private template serdeGetKeysIn2(T) +{ + // T* value; + enum string[] serdeGetKeysIn2(string member) = serdeGetKeysIn!(T, member); +} + +private template serdeGetKeyOut2(T) +{ + enum string[] serdeGetKeyOut2(string member) = serdeGetKeyOut!(T, member) is null ? null : [serdeGetKeyOut!(T, member)]; +} + +private template serdeFinalDeepProxyDeserializableMemberKeys(T) +{ + import std.meta: staticMap, aliasSeqOf; + import std.traits: isAggregateType; + + static if (isAggregateType!T) + { + import mir.algebraic: isVariant; + static if (isVariant!T) + enum string[] serdeFinalDeepProxyDeserializableMemberKeys = getAlgebraicAnnotationsOfVariant!T; + else + enum string[] serdeFinalDeepProxyDeserializableMemberKeys = [staticMap!(aliasSeqOf, staticMap!(serdeGetKeysIn2!T, aliasSeqOf!(serdeFinalDeepProxyDeserializableMembers!T)))]; + } + else + static if (is(T == enum)) + { + enum string[] serdeFinalDeepProxyDeserializableMemberKeys = enumAllKeysIn!T; + } + else + enum string[] serdeFinalDeepProxyDeserializableMemberKeys = null; +} + +package template getAlgebraicAnnotationsOfVariant(T) +{ + import std.meta: staticMap, Filter; + enum string[] getAlgebraicAnnotationsOfVariant = [staticMap!(serdeGetAlgebraicAnnotation, Filter!(serdeHasAlgebraicAnnotation, T.AllowedTypes))]; +} + +private template serdeFinalDeepProxySerializableMemberKeys(T) +{ + import std.meta: staticMap, aliasSeqOf; + import std.traits: isAggregateType; + + static if (isAggregateType!T) + { + import mir.algebraic: isVariant; + static if (isVariant!T) + enum string[] serdeFinalDeepProxySerializableMemberKeys = getAlgebraicAnnotationsOfVariant!T; + else + enum string[] serdeFinalDeepProxySerializableMemberKeys = [staticMap!(aliasSeqOf, staticMap!(serdeGetKeyOut2!T, aliasSeqOf!(serdeFinalDeepProxySerializableMembers!T)))]; + } + else + static if (is(T == enum)) + { + enum string[] serdeFinalDeepProxySerializableMemberKeys = enumAllKeysOut!T; + } + else + enum string[] serdeFinalDeepProxySerializableMemberKeys = null; +} + +private template serdeGetAlgebraicAnnotations(T) +{ + static if (isAggregateType!T || is(T == enum)) + static if (hasUDA!(T, serdeAlgebraicAnnotation)) + enum string[] serdeGetAlgebraicAnnotations = [getUDA!(T, serdeAlgebraicAnnotation).annotation]; + else + enum string[] serdeGetAlgebraicAnnotations = null; + else + enum string[] serdeGetAlgebraicAnnotations = null; +} + +package template serdeIsComplexVariant(T) +{ + import mir.algebraic: isVariant, isNullable; + static if (isVariant!T) + { + enum serdeIsComplexVariant = (T.AllowedTypes.length - isNullable!T) > 1; + } + else + { + enum bool serdeIsComplexVariant = false; + } +} + +package template isAlgebraicAliasThis(T) +{ + static if (__traits(getAliasThis, T).length) + { + import mir.algebraic: isVariant; + private __gshared T* aggregate; + alias A = AliasSeq!(typeof(__traits(getMember, aggregate, __traits(getAliasThis, T)))); + static if (A.length != 1) + enum isAlgebraicAliasThis = false; + else + static if (isVariant!(A[0])) + enum isAlgebraicAliasThis = true; + else + enum isAlgebraicAliasThis = serdeIsDynamicAlgebraic!(A[0]); + } + else + { + enum isAlgebraicAliasThis = false; + } +} + +/++ +Serialization members final proxy keys (recursive) ++/ +template serdeGetSerializationKeysRecurse(T) +{ + import std.meta: staticMap, aliasSeqOf; + enum string[] serdeGetSerializationKeysRecurse = [staticMap!(aliasSeqOf, staticMap!(serdeFinalDeepProxySerializableMemberKeys, serdeSerializationFinalDeepProxyMemberTypesRecurse!T))].sortUniqKeys; +} + +/// +version(mir_test) unittest +{ + enum Y + { + a, + b, + c, + } + + static struct A { double g; float d; } + + @serdeProxy!A + static struct B { int f; } + + @serdeProxy!(B[Y][string]) + static union C { int f; } + + @serdeProxy!(B[]) + static interface E { int f() @property; } + + enum N { a, b } + + static class D + { + C c; + + @serdeProxy!(E[]) + int d; + + N e; + } + + @serdeAlgebraicAnnotation("$F") + @serdeProxy!D + static struct F { int f; } + + static assert (serdeGetSerializationKeysRecurse!F == ["a", "b", "c", "d", "e", "g"]); + + import mir.algebraic; + static assert (serdeGetSerializationKeysRecurse!(Nullable!(F, int)) == ["a", "b", "c", "d", "e", "g", "$F"]); +} + +/++ +Deserialization members final proxy keys (recursive) ++/ +template serdeGetDeserializationKeysRecurse(T) +{ + import std.meta: staticMap, aliasSeqOf; + enum string[] serdeGetDeserializationKeysRecurse = [staticMap!(aliasSeqOf, staticMap!(serdeFinalDeepProxyDeserializableMemberKeys, serdeDeserializationFinalDeepProxyMemberTypesRecurse!T))].sortUniqKeys; +} + +/// +version(mir_test) unittest +{ + + static struct A { double g; float d; } + + @serdeProxy!A + static struct B { int f; } + + @serdeProxy!(B[string]) + static union C { int f; } + + @serdeProxy!(B[]) + static interface E { int f() @property; } + + enum N { a, b } + + static class D + { + C c; + + @serdeProxy!(E[]) + int d; + + N e; + } + + @serdeAlgebraicAnnotation("$F") + @serdeProxy!D + static struct F { int f; } + + static assert (serdeGetDeserializationKeysRecurse!N == ["a", "b"], serdeGetDeserializationKeysRecurse!N); + + static assert (serdeGetDeserializationKeysRecurse!F == ["a", "b", "c", "d", "e", "g"]); + + import mir.algebraic; + static assert (serdeGetDeserializationKeysRecurse!(Nullable!(F, int)) == ["a", "b", "c", "d", "e", "g", "$F"]); +} + +/++ +UDA used to force deserializer to initilize members in the order of their definition in the target object/structure. + +The attribute force deserializer to create a dummy type (recursively), initializer its fields and then assign them to +to the object members (fields and setters) in the order of their definition. + +See_also: $(LREF SerdeOrderedDummy), $(LREF serdeRealOrderedIn), $(LREF serdeIgnoreInIfAggregate). ++/ +enum serdeOrderedIn; + +/++ +UDA used to force deserializer to initilize members in the order of their definition in the target object/structure. + +Unlike $(LREF serdeOrderedIn) `serdeRealOrderedDummy` force deserialzier to iterate all DOM keys for each object deserialization member. +It is slower but more universal approach. + +See_also: $(LREF serdeOrderedIn), $(LREF serdeIgnoreInIfAggregate) ++/ +enum serdeRealOrderedIn; + + +/++ +UDA used to force deserializer to skip the member final deserialization. +A user should finalize the member deserialize using the dummy object provided in `serdeFinalizeWithDummy(ref SerdeOrderedDummy!(typeof(this)) dummy)` struct method +and dummy method `serdeFinalizeTargetMember`. ++/ +enum serdeFromDummyByUser; + +/++ +UDA used to force serializer to output members in the alphabetical order of their output keys. ++/ +enum serdeAlphabetOut; + +/++ +A dummy structure usefull $(LREF serdeOrderedIn) support. ++/ +struct SerdeOrderedDummy(T, bool __optionalByDefault = false) + if (is(serdeGetFinalProxy!T == T) && isAggregateType!T) +{ + + @serdeIgnore + SerdeFlags!(typeof(this)) __serdeFlags; + + static if (__optionalByDefault) + alias __serdeOptionalRequired = serdeRequired; + else + alias __serdeOptionalRequired = serdeOptional; + + this()(T value) + { + static foreach (member; serdeFinalProxyDeserializableMembers!T) + { + static if (hasField!(T, member)) + { + static if (__traits(compiles, {__traits(getMember, this, member) = __traits(getMember, value, member);})) + __traits(getMember, this, member) = __traits(getMember, value, member); + } + } + } + +public: + + static foreach (i, member; serdeFinalProxyDeserializableMembers!T) + { + static if (hasField!(T, member)) + { + static if (hasUDA!(T, member, serdeProxy)) + { + mixin("@(__traits(getAttributes, T." ~ member ~ ")) serdeDeserializationMemberType!(T, `" ~ member ~ "`) " ~ member ~ ";"); + } + else + static if (isAggregateType!(typeof(__traits(getMember, T, member)))) + { + static if (hasUDA!(typeof(__traits(getMember, T, member)), serdeProxy)) + { + mixin("@(__traits(getAttributes, T." ~ member ~ ")) serdeDeserializationMemberType!(T, `" ~ member ~ "`) " ~ member ~ " = T.init." ~ member ~ ";"); + } + else + static if (__traits(compiles, { + mixin("enum SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`)) " ~ member ~ " = SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`))(T.init." ~ member ~ ");"); + })) + { + mixin("@(__traits(getAttributes, T." ~ member ~ ")) SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`)) " ~ member ~ " = SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`))(T.init." ~ member ~ ");"); + } + else + { + mixin("@(__traits(getAttributes, T." ~ member ~ ")) SerdeOrderedDummy!(serdeDeserializationMemberType!(T, `" ~ member ~ "`)) " ~ member ~ ";"); + } + } + else + { + mixin("@(__traits(getAttributes, T." ~ member ~ ")) serdeDeserializationMemberType!(T, `" ~ member ~ "`) " ~ member ~ " = T.init." ~ member ~ ";"); + } + } + else + { + mixin("@(__traits(getAttributes, T." ~ member ~ ")) serdeDeserializationMemberType!(T, `" ~ member ~ "`) " ~ member ~ ";"); + } + } + + /// Initialize target members + void serdeFinalizeWithFlags(ref scope const SerdeFlags!(typeof(this)) flags) + { + __serdeFlags = flags; + } + + /// Initialize target members + void serdeFinalizeTarget(ref T value, ref scope SerdeFlags!T flags) + { + import std.traits: hasElaborateAssign; + static foreach (member; serdeFinalProxyDeserializableMembers!T) + __traits(getMember, flags, member) = __traits(getMember, __serdeFlags, member); + static foreach (member; serdeFinalProxyDeserializableMembers!T) + static if (!hasUDA!(T, member, serdeFromDummyByUser)) + {{ + if (hasUDA!(T, member, __serdeOptionalRequired) == __optionalByDefault || __traits(getMember, __serdeFlags, member)) + { + static if (is(typeof(__traits(getMember, this, member)) : SerdeOrderedDummy!I, I)) + { + alias M = typeof(__traits(getMember, value, member)); + SerdeFlags!M memberFlags; + __traits(getMember, this, member).serdeFinalizeTarget(__traits(getMember, value, member), memberFlags); + static if (__traits(hasMember, M, "serdeFinalizeWithFlags")) + { + __traits(getMember, value, member).serdeFinalizeWithFlags(memberFlags); + } + static if (__traits(hasMember, M, "serdeFinalize")) + { + __traits(getMember, value, member).serdeFinalize(); + } + } + else + { + static if (hasElaborateAssign!(typeof(__traits(getMember, this, member)))) + { + import core.lifetime: move; + __traits(getMember, value, member) = move(__traits(getMember, this, member)); + } + else + __traits(getMember, value, member) = __traits(getMember, this, member); + } + } + }} + static if (__traits(hasMember, T, "serdeFinalizeWithDummy")) + { + value.serdeFinalizeWithDummy(this); + } + } + + /// Initialize target member + void serdeFinalizeTargetMember(string member)(ref T value) + { + if (hasUDA!(T, member, __serdeOptionalRequired) == __optionalByDefault || __traits(getMember, __serdeFlags, member)) + { + static if (is(typeof(__traits(getMember, this, member)) : SerdeOrderedDummy!I, I)) + { + alias M = typeof(__traits(getMember, value, member)); + SerdeFlags!M memberFlags; + __traits(getMember, this, member).serdeFinalizeTarget(__traits(getMember, value, member), memberFlags); + static if (__traits(hasMember, M, "serdeFinalizeWithFlags")) + { + __traits(getMember, value, member).serdeFinalizeWithFlags(memberFlags); + } + static if (__traits(hasMember, M, "serdeFinalize")) + { + __traits(getMember, value, member).serdeFinalize(); + } + } + else + { + static if (hasElaborateAssign!(typeof(__traits(getMember, this, member)))) + { + import core.lifetime: move; + __traits(getMember, value, member) = move(__traits(getMember, this, member)); + } + else + __traits(getMember, value, member) = __traits(getMember, this, member); + } + } + } +} + +/// +version(mir_test) unittest +{ + import std.traits; + + static struct S + { + private double _d; + + @serdeProxy!int + void d(double v) @property { _d = v; } + + string s; + } + + static assert(is(typeof(SerdeOrderedDummy!S.init.d) == double), SerdeOrderedDummy!S.init.d); + static assert(is(typeof(SerdeOrderedDummy!S.init.s) == string)); + static assert(hasUDA!(S.d, serdeProxy)); + static assert(hasUDA!(SerdeOrderedDummy!S.d, serdeProxy)); +} + +/++ +Exact amount of total serializable members in a structure/class. +The UDA is used to help CBOR and MsgPack serializers to construct finite length maps. ++/ +struct serdeMembersExactly +{ + /// + int n; +} + +/++ +A dummy structure passed to `.serdeFinalizeWithFlags` finalizer method. ++/ +struct SerdeFlags(T) +{ + static if (is(T : SerdeOrderedDummy!I, I)) + static foreach(member; serdeFinalProxyDeserializableMembers!I) + mixin("bool " ~ member ~ ";"); + else + static foreach(member; serdeFinalProxyDeserializableMembers!T) + mixin("bool " ~ member ~ ";"); +} + +/++ +The UDA used on struct or class definitions to denote an discriminated field and its tag +for algebraic deserialization. + +Discriminated field is ignored during the annotated structure/class deseriliazation. ++/ +struct serdeDiscriminatedField +{ + /// Name of the field in the JSON like data object. + string field; + /// Value of the field for the current type. + string tag; +} + +template deserializeValueMemberImpl(alias deserializeValue, alias deserializeScoped) +{ + /// + SerdeException deserializeValueMemberImpl(string member, Data, T, Context...)(Data data, scope ref T value, scope ref SerdeFlags!T requiredFlags, scope ref Context context) + { + import core.lifetime: move; + import mir.conv: to; + + enum likeList = hasUDA!(T, member, serdeLikeList); + enum likeStruct = hasUDA!(T, member, serdeLikeStruct); + enum hasProxy = hasUDA!(T, member, serdeProxy); + enum hasScoped = hasUDA!(T, member, serdeScoped); + + static assert (likeList + likeStruct <= 1, T.stringof ~ "." ~ member ~ " can't have both @serdeLikeStruct and @serdeLikeList attributes"); + static assert (hasProxy >= likeStruct, T.stringof ~ "." ~ member ~ " should have a Proxy type for deserialization"); + static assert (hasProxy >= likeList, T.stringof ~ "." ~ member ~ " should have a Proxy type for deserialization"); + + alias Member = serdeDeserializationMemberType!(T, member); + + static if (hasProxy) + alias Temporal = serdeGetProxy!(T, member); + else + alias Temporal = Member; + + static if (hasScoped) + static if (__traits(compiles, { Temporal temporal; deserializeScoped(data, temporal); })) + alias impl = deserializeScoped; + else + alias impl = deserializeValue; + else + alias impl = deserializeValue; + + static immutable excm(string member) = new SerdeException("ASDF deserialisation: multiple keys for member '" ~ member ~ "' in " ~ T.stringof ~ " are not allowed."); + + static if (!hasUDA!(T, member, serdeAllowMultiple)) + if (__traits(getMember, requiredFlags, member)) + return excm!member; + + __traits(getMember, requiredFlags, member) = true; + + static if (likeList) + { + foreach(elem; data.byElement) + { + Temporal temporal; + if (auto exc = impl(elem, temporal, context)) + return exc; + __traits(getMember, value, member).put(move(temporal)); + } + } + else + static if (likeStruct) + { + foreach(v; data.byKeyValue(context)) + { + Temporal temporal; + if (auto exc = impl(v.value, temporal, context)) + return exc; + __traits(getMember, value, member)[v.key.idup] = move(temporal); + } + } + else + static if (hasProxy) + { + Temporal temporal; + if (auto exc = impl(data, temporal, context)) + return exc; + __traits(getMember, value, member) = to!(serdeDeserializationMemberType!(T, member))(move(temporal)); + } + else + static if (hasField!(T, member)) + { + if (auto exc = impl(data, __traits(getMember, value, member), context)) + return exc; + } + else + { + Member temporal; + if (auto exc = impl(data, temporal, context)) + return exc; + __traits(getMember, value, member) = move(temporal); + } + + static if (hasUDA!(T, member, serdeTransformIn)) + { + alias transform = serdeGetTransformIn!(T, member, member); + static if (hasField!(T, member)) + { + transform(__traits(getMember, value, member)); + } + else + { + auto temporal = __traits(getMember, value, member); + transform(temporal); + __traits(getMember, value, member) = move(temporal); + } + } + + return null; + } +} + +private: + +auto fastLazyToUpper()(return scope const(char)[] name) +{ + import mir.ndslice.topology: map; + return name.map!fastToUpper; +} + +auto fastToUpper()(char a) +{ // std.ascii may not be inlined + return 'a' <= a && a <= 'z' ? cast(char)(a ^ 0x20) : a; +} + +@safe pure nothrow @nogc +char[] fastToUpperInPlace()(return scope char[] a) +{ + foreach(ref char e; a) + e = e.fastToUpper; + return a; +} + +template enumAllKeysIn(T) + if (is(T == enum)) +{ + import std.traits: EnumMembers; + import std.meta: staticMap, aliasSeqOf; + enum string[] enumAllKeysIn = [staticMap!(aliasSeqOf, staticMap!(.serdeGetKeysIn, EnumMembers!T))]; +} + +template enumAllKeysOut(T) + if (is(T == enum)) +{ + import std.traits: EnumMembers; + import std.meta: staticMap, aliasSeqOf; + enum string[] enumAllKeysOut = [staticMap!(.serdeGetKeyOut, EnumMembers!T)]; +} diff --git a/source/mir/series.d b/source/mir/series.d index fd024824..87ca3bce 100644 --- a/source/mir/series.d +++ b/source/mir/series.d @@ -6,8 +6,8 @@ It is aimed to construct index or time-series using Mir and Phobos algorithms. Public_imports: $(MREF mir,ndslice,slice). -Copyright: Copyright © 2017, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -15,26 +15,30 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.series; -public import mir.ndslice.slice; -public import mir.ndslice.sorting: sort; import mir.ndslice.iterator: IotaIterator; import mir.ndslice.sorting: transitionIndex; import mir.qualifier; +import mir.serde: serdeIgnore, serdeFields; import std.traits; +/// +public import mir.ndslice.slice; +/// +public import mir.ndslice.sorting: sort; /++ See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). +/ @safe version(mir_test) unittest { + import mir.test; import mir.ndslice; import mir.series; import mir.array.allocation: array; import mir.algorithm.setops: multiwayUnion; - import std.datetime: Date; - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; + import mir.date: Date; + import core.lifetime: move; import std.exception: collectExceptionMsg; ////////////////////////////////////// @@ -54,7 +58,7 @@ See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). Date(2017, 05, 01)]; auto data1 = [10.0, 20, 50]; - auto series1 = index1.series(data1); + auto series1 = index1.series(data1); ////////////////////////////////////// // asSlice method @@ -62,7 +66,7 @@ See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). assert(series0 .asSlice // ref qualifier is optional - .map!((ref key, ref value) => key.month == value) + .map!((ref key, ref value) => key.yearMonthDay.month == value) .all); ////////////////////////////////////// @@ -80,29 +84,25 @@ See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). // Exceptions handlers assert(series0.get(refDate) == 3); assert(series0.get(refDate, new Exception("My exception msg")) == 3); - assert(series0.getVerbose(refDate) == 3); - assert(series0.getExtraVerbose(refDate, "My exception msg") == 3); - - assert(collectExceptionMsg!Exception( - series0.get(missingDate) - ) == "Series double[Date]: Missing required key"); - - assert(collectExceptionMsg!Exception( - series0.get(missingDate, new Exception("My exception msg")) - ) == "My exception msg"); - - assert(collectExceptionMsg!Exception( - series0.getVerbose(missingDate) - ) == "Series double[Date]: Missing 2016-Mar-01 key"); - - assert(collectExceptionMsg!Exception( - series0.getExtraVerbose(missingDate, "My exception msg") - ) == "My exception msg. Series double[Date]: Missing 2016-Mar-01 key"); + assert(series0.getVerbose(refDate) == 3); + assert(series0.getExtraVerbose(refDate, "My exception msg") == 3); + + collectExceptionMsg!Exception(series0.get(missingDate)).should + == "Series double[Date]: Missing required key"; + + collectExceptionMsg!Exception(series0.get(missingDate, new Exception("My exception msg"))).should + == "My exception msg"; + + collectExceptionMsg!Exception(series0.getVerbose(missingDate)).should + == "Series double[Date]: Missing 2016-03-01 key"; + + collectExceptionMsg!Exception(series0.getExtraVerbose(missingDate, "My exception msg")).should + == "My exception msg. Series double[Date]: Missing 2016-03-01 key"; // assign with get* - series0.get(refDate) = 100; - assert(series0.get(refDate) == 100); - series0.get(refDate) = 3; + series0.get(refDate) = 100; + assert(series0.get(refDate) == 100); + series0.get(refDate) = 3; // tryGet double val; @@ -139,7 +139,7 @@ See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). auto data = slice!double([index.length, 2], 0); // initialized to 0 value auto series = index.series(data); - series[0 .. $, 0][].opIndexAssign(series0); // fill first column + series[0 .. $, 0][] = series0; // fill first column series[0 .. $, 1][] = series1; // fill second column assert(data == [ @@ -151,6 +151,7 @@ See_also: $(LREF unionSeries), $(LREF troykaSeries), $(LREF troykaGalop). } /// +version(mir_test) unittest{ import mir.series; @@ -189,11 +190,11 @@ unittest{ import mir.ndslice.slice; import mir.ndslice.internal: is_Slice, isIndex; -import mir.math.common: optmath; +import mir.math.common: fmamath; import std.meta; -@optmath: +@fmamath: /++ Plain index/time observation data structure. @@ -203,14 +204,8 @@ struct mir_observation(Index, Data) { /// Date, date-time, time, or index. Index index; - /// An alias for time-series index. - alias time = index; - /// An alias for key-value representation. - alias key = index; /// Value or ndslice. Data data; - /// An alias for key-value representation. - alias value = data; } /// ditto @@ -219,7 +214,8 @@ alias Observation = mir_observation; /// Convenient function for $(LREF Observation) construction. auto observation(Index, Data)(Index index, Data data) { - return mir_observation!(Index, Data)(index, data); + alias R = mir_observation!(Index, Data); + return R(index, data); } /++ @@ -260,9 +256,10 @@ Plain index series data structure. Index is assumed to be sorted. $(LREF sort) can be used to normalise a series. +/ +@serdeFields struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous) { - private enum doUnittest = is(typeof(this) == Series!(int*, double*)); + private enum doUnittest = is(typeof(this) == mir_series!(int*, double*)); /// alias IndexIterator = IndexIterator_; @@ -271,45 +268,91 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co alias Iterator = Iterator_; /// - enum size_t N = N_; + @serdeIgnore enum size_t N = N_; /// - enum SliceKind kind = kind_; + @serdeIgnore enum SliceKind kind = kind_; - /// - Slice!(Iterator, N, kind) _data; + /++ + Index series is assumed to be sorted. - /// - IndexIterator _index; + `IndexIterator` is an iterator on top of date, date-time, time, or numbers or user defined types with defined `opCmp`. + For example, `Date*`, `DateTime*`, `immutable(long)*`, `mir.ndslice.iterator.IotaIterator`. + +/ + auto index() @property @trusted + { + return _index.sliced(this.data._lengths[0]); + } - /// Index / Key / Time type aliases - alias Index = typeof(this.front.index); /// ditto - alias Key = Index; + auto index() @property @trusted const + { + return _index.lightConst.sliced(this.data._lengths[0]); + } + /// ditto - alias Time = Index; - /// Data / Value type aliases - alias Data = typeof(this.front.data); + auto index() @property @trusted immutable + { + return _index.lightImmutable.sliced(this.data._lengths[0]); + } + /// ditto - alias Value = Data; + void index()(Slice!IndexIterator index) @property @trusted + { + import core.lifetime: move; + this._index = move(index._iterator); + } + + /// ditto + static if (doUnittest) + @safe version(mir_test) unittest + { + import mir.ndslice.slice: sliced; + auto s = ["a", "b"].series([5, 6]); + assert(s.index == ["a", "b"]); + s.index = ["c", "d"].sliced; + assert(s.index == ["c", "d"]); + } + + /++ + Data is any ndslice with only one constraints, + `data` and `index` lengths should be equal. + +/ + Slice!(Iterator, N, kind) data; + +@serdeIgnore: + + /// + IndexIterator _index; - /// An alias for time-series index. - alias time = index; - /// An alias for key-value representation. - alias key = index; - /// An alias for key-value representation. - alias value = data; + /// Index / Key / Time type aliases + alias Index = typeof(typeof(this).init.index.front); + /// Data / Value type aliases + alias Data = typeof(typeof(this).init.data.front); private enum defaultMsg() = "Series " ~ Unqual!(this.Data).stringof ~ "[" ~ Unqual!(this.Index).stringof ~ "]: Missing"; private static immutable defaultExc() = new Exception(defaultMsg!() ~ " required key"); -@optmath: + /// + void serdeFinalize()() @trusted scope + { + import mir.algorithm.iteration: any; + import mir.ndslice.topology: pairwise; + import std.traits: Unqual; + if (length <= 1) + return; + auto mutableOf = cast(Series!(Unqual!Index*, Unqual!Data*)) this.lightScope(); + if (any(pairwise!"a > b"(mutableOf.index))) + sort(mutableOf); + } + +@fmamath: /// this()(Slice!IndexIterator index, Slice!(Iterator, N, kind) data) { assert(index.length == data.length, "Series constructor: index and data lengths must be equal."); - _data = data; + this.data = data; _index = index._iterator; } @@ -317,92 +360,50 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co /// Construct from null this(typeof(null)) { - _data = _data.init; + this.data = this.data.init; _index = _index.init; } /// bool opEquals(RIndexIterator, RIterator, size_t RN, SliceKind rkind, )(Series!(RIndexIterator, RIterator, RN, rkind) rhs) const { - return this.lightScopeIndex == rhs.lightScopeIndex && this._data.lightScope == rhs._data.lightScope; - } - - /++ - Index series is assumed to be sorted. - - `IndexIterator` is an iterator on top of date, date-time, time, or numbers or user defined types with defined `opCmp`. - For example, `Date*`, `DateTime*`, `immutable(long)*`, `mir.ndslice.iterator.IotaIterator`. - +/ - auto index()() @property @trusted - { - return _index.sliced(_data._lengths[0]); - } - - /// ditto - auto index()() @property @trusted const - { - return _index.lightConst.sliced(_data._lengths[0]); - } - - /// ditto - auto index()() @property @trusted immutable - { - return _index.lightImmutable.sliced(_data._lengths[0]); + return this.lightScopeIndex == rhs.lightScopeIndex && this.data.lightScope == rhs.data.lightScope; } - private auto lightScopeIndex()() @property @trusted + private auto lightScopeIndex()() return scope @trusted { - return .lightScope(_index).sliced(_data._lengths[0]); + return .lightScope(_index).sliced(this.data._lengths[0]); } - private auto lightScopeIndex()() @property @trusted const + private auto lightScopeIndex()() return scope @trusted const { - return .lightScope(_index).sliced(_data._lengths[0]); + return .lightScope(_index).sliced(this.data._lengths[0]); } - private auto lightScopeIndex()() @property @trusted immutable - { - return .lightScope(_index).sliced(_data._lengths[0]); - } - - /++ - Data is any ndslice with only one constraints, - `data` and `index` lengths should be equal. - +/ - auto data()() @property @trusted + private auto lightScopeIndex()() return scope @trusted immutable { - return _data; - } - - /// ditto - auto data()() @property @trusted const - { - return _data[]; - } - - /// ditto - auto data()() @property @trusted immutable - { - return _data[]; + return .lightScope(_index).sliced(this.data._lengths[0]); } /// typeof(this) opBinary(string op : "~")(typeof(this) rhs) { - return unionSeries(this.lightScope, rhs.lightScope); + scope typeof(this.lightScope)[2] lhsAndRhs = [this.lightScope, rhs.lightScope]; + return unionSeriesImplPrivate!false(lhsAndRhs); } /// ditto - auto opBinary(string op : "~")(const typeof(this) rhs) const + auto opBinary(string op : "~")(const typeof(this) rhs) const @trusted { - return unionSeries(this.lightScope, rhs.lightScope); + scope typeof(this.lightScope)[2] lhsAndRhs = [this.lightScope, rhs.lightScope]; + return unionSeriesImplPrivate!false(lhsAndRhs); } static if (doUnittest) /// @safe pure nothrow version(mir_test) unittest { - import std.datetime: Date; + import mir.date: Date; ////////////////////////////////////// // Constructs two time-series. @@ -431,7 +432,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co static if (doUnittest) @safe pure nothrow version(mir_test) unittest { - import std.datetime: Date; + import mir.date: Date; ////////////////////////////////////// // Constructs two time-series. @@ -501,8 +502,8 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co void opIndexOpAssign(string op, IndexIterator_, Iterator_, size_t N_, SliceKind kind_) (auto ref Series!(IndexIterator_, Iterator_, N_, kind_) rSeries) { - auto l = this.lightScope; - auto r = rSeries.lightScope; + scope l = this.lightScope; + scope r = rSeries.lightScope; if (r.empty) return; if (l.empty) @@ -561,7 +562,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co } /++ - This function uses a search with policy sp to find the largest left subrange on which + This function uses a search with policy sp to find the largest left subrange on which `t < key` is true for all `t`. The search schedule and its complexity are documented in `std.range.SearchPolicy`. +/ @@ -578,7 +579,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co /++ - This function uses a search with policy sp to find the largest left subrange on which + This function uses a search with policy sp to find the largest right subrange on which `t > key` is true for all `t`. The search schedule and its complexity are documented in `std.range.SearchPolicy`. +/ @@ -605,7 +606,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co if (!is(Value : const(Exception))) { size_t idx = lightScopeIndex.transitionIndex(key); - return idx < _data._lengths[0] && _index[idx] == key ? _data[idx] : _default; + return idx < this.data._lengths[0] && _index[idx] == key ? this.data[idx] : _default; } /// ditto @@ -626,14 +627,14 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co if (!is(Value : const(Exception))) { size_t idx = lightScopeIndex.transitionIndex(key); - return idx < _data._lengths[0] && _index[idx] == key ? _data[idx] : _default; + return idx < this.data._lengths[0] && _index[idx] == key ? this.data[idx] : _default; } /// ditto auto get(Index, Value)(auto ref scope const Index key, Value _default) const if (!is(Value : const(Exception))) { - import mir.functional: forward; + import core.lifetime: forward; return this.lightScope.get(key, forward!_default); } @@ -641,7 +642,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co auto get(Index, Value)(auto ref scope const Index key, Value _default) immutable if (!is(Value : const(Exception))) { - import mir.functional: forward; + import core.lifetime: forward; return this.lightScope.get(key, forward!_default); } @@ -658,22 +659,23 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co auto ref get(Index)(auto ref scope const Index key) @trusted { size_t idx = lightScopeIndex.transitionIndex(key); - if (idx < _data._lengths[0] && _index[idx] == key) + if (idx < this.data._lengths[0] && _index[idx] == key) { - return _data[idx]; + return this.data[idx]; } - throw defaultExc!(); + import mir.exception : toMutable; + throw defaultExc!().toMutable; } /// ditto auto ref get(Index)(auto ref scope const Index key, lazy const Exception exc) @trusted { size_t idx = lightScopeIndex.transitionIndex(key); - if (idx < _data._lengths[0] && _index[idx] == key) + if (idx < this.data._lengths[0] && _index[idx] == key) { - return _data[idx]; + return this.data[idx]; } - throw exc; + { import mir.exception : toMutable; throw exc.toMutable; } } /// ditto @@ -759,19 +761,19 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co bool contains(Index)(auto ref scope const Index key) const @trusted { size_t idx = lightScopeIndex.transitionIndex(key); - return idx < _data._lengths[0] && _index[idx] == key; + return idx < this.data._lengths[0] && _index[idx] == key; } /// auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) @trusted { size_t idx = lightScopeIndex.transitionIndex(key); - bool cond = idx < _data._lengths[0] && _index[idx] == key; - static if (__traits(compiles, &_data[size_t.init])) + bool cond = idx < this.data._lengths[0] && _index[idx] == key; + static if (__traits(compiles, &this.data[size_t.init])) { if (cond) - return &_data[idx]; - return null; + return &this.data[idx]; + return null; } else { @@ -782,13 +784,15 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co /// ditto auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) const { - return key in this.lightScope; + auto val = key in this.lightScope; + return val; } /// ditto auto opBinaryRight(string op : "in", Index)(auto ref scope const Index key) immutable { - return key in this.lightScope; + auto val = key in this.lightScope; + return val; } /++ @@ -799,9 +803,9 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co bool tryGet(Index, Value)(Index key, scope ref Value val) @trusted { size_t idx = lightScopeIndex.transitionIndex(key); - auto cond = idx < _data._lengths[0] && _index[idx] == key; + auto cond = idx < this.data._lengths[0] && _index[idx] == key; if (cond) - val = _data[idx]; + val = this.data[idx]; return cond; } @@ -825,9 +829,9 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co bool tryGetNext(Index, Value)(auto ref scope const Index key, scope ref Value val) { size_t idx = lightScopeIndex.transitionIndex(key); - auto cond = idx < _data._lengths[0]; + auto cond = idx < this.data._lengths[0]; if (cond) - val = _data[idx]; + val = this.data[idx]; return cond; } @@ -852,11 +856,11 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co bool tryGetNextUpdateKey(Index, Value)(scope ref Index key, scope ref Value val) @trusted { size_t idx = lightScopeIndex.transitionIndex(key); - auto cond = idx < _data._lengths[0]; + auto cond = idx < this.data._lengths[0]; if (cond) { key = _index[idx]; - val = _data[idx]; + val = this.data[idx]; } return cond; } @@ -883,7 +887,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co size_t idx = lightScopeIndex.transitionIndex!"a <= b"(key) - 1; auto cond = 0 <= sizediff_t(idx); if (cond) - val = _data[idx]; + val = this.data[idx]; return cond; } @@ -912,7 +916,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co if (cond) { key = _index[idx]; - val = _data[idx]; + val = this.data[idx]; } return cond; } @@ -937,9 +941,9 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co bool tryGetFirst(Index, Value)(auto ref scope const Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) @trusted { size_t idx = lightScopeIndex.transitionIndex(lowerBound); - auto cond = idx < _data._lengths[0] && _index[idx] <= upperBound; + auto cond = idx < this.data._lengths[0] && _index[idx] <= upperBound; if (cond) - val = _data[idx]; + val = this.data[idx]; return cond; } @@ -964,11 +968,11 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co bool tryGetFirstUpdateLower(Index, Value)(ref Index lowerBound, auto ref scope const Index upperBound, scope ref Value val) @trusted { size_t idx = lightScopeIndex.transitionIndex(lowerBound); - auto cond = idx < _data._lengths[0] && _index[idx] <= upperBound; + auto cond = idx < this.data._lengths[0] && _index[idx] <= upperBound; if (cond) { lowerBound = _index[idx]; - val = _data[idx]; + val = this.data[idx]; } return cond; } @@ -995,7 +999,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co size_t idx = lightScopeIndex.transitionIndex!"a <= b"(upperBound) - 1; auto cond = 0 <= sizediff_t(idx) && _index[idx] >= lowerBound; if (cond) - val = _data[idx]; + val = this.data[idx]; return cond; } @@ -1024,7 +1028,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co if (cond) { upperBound = _index[idx]; - val = _data[idx]; + val = this.data[idx]; } return cond; } @@ -1079,7 +1083,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co size_t length(size_t dimension = 0)() const @property if (dimension < N) { - return _data.length!dimension; + return this.data.length!dimension; } /// ditto @@ -1093,7 +1097,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co } else { - return index.front.observation(data.front); + return Observation!(Index, Data)(index.front, data.front); } } @@ -1104,11 +1108,11 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co assert(!empty!dimension); static if (dimension) { - return index.series(_data.back!dimension); + return index.series(this.data.back!dimension); } else { - return index.back.observation(_data.back); + return index.back.observation(this.data.back); } } @@ -1119,7 +1123,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co assert(!empty!dimension); static if (dimension == 0) _index++; - _data.popFront!dimension; + this.data.popFront!dimension; } /// ditto @@ -1127,7 +1131,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co if (dimension < N) { assert(!empty!dimension); - _data.popBack!dimension; + this.data.popBack!dimension; } /// ditto @@ -1137,7 +1141,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co assert(length!dimension >= n); static if (dimension == 0) _index += n; - _data.popFrontExactly!dimension(n); + this.data.popFrontExactly!dimension(n); } /// ditto @@ -1145,7 +1149,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co if (dimension < N) { assert(length!dimension >= n); - _data.popBackExactly!dimension(n); + this.data.popBackExactly!dimension(n); } /// ditto @@ -1175,10 +1179,10 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co "Series.opSlice!" ~ dimension.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); enum errorMsg = ": difference between the right and the left bounds" ~ " must be less than or equal to the length of the given dimension."; - assert(j - i <= _data._lengths[dimension], + assert(j - i <= this.data._lengths[dimension], "Series.opSlice!" ~ dimension.stringof ~ errorMsg); } - body + do { return typeof(return)(j - i, typeof(return).Iterator(i)); } @@ -1186,7 +1190,7 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co /// ditto size_t opDollar(size_t dimension = 0)() const { - return _data.opDollar!dimension; + return this.data.opDollar!dimension; } /// ditto @@ -1223,42 +1227,42 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co } /// - ref opAssign(typeof(this) rvalue) return @trusted + ref opAssign(typeof(this) rvalue) scope return @trusted { import mir.utility: swap; - this._data._structure = rvalue._data._structure; - swap(this._data._iterator, rvalue._data._iterator); + this.data._structure = rvalue.data._structure; + swap(this.data._iterator, rvalue.data._iterator); swap(this._index, rvalue._index); return this; } /// ditto - ref opAssign(RIndexIterator, RIterator)(Series!(RIndexIterator, RIterator, N, kind) rvalue) return + ref opAssign(RIndexIterator, RIterator)(Series!(RIndexIterator, RIterator, N, kind) rvalue) scope return if (isAssignable!(IndexIterator, RIndexIterator) && isAssignable!(Iterator, RIterator)) { - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; - this._data._structure = rvalue._data._structure; - this._data._iterator = rvalue._data._iterator.move; + import core.lifetime: move; + this.data._structure = rvalue.data._structure; + this.data._iterator = rvalue.data._iterator.move; this._index = rvalue._index.move; return this; } /// ditto - ref opAssign(RIndexIterator, RIterator)(auto ref const Series!(RIndexIterator, RIterator, N, kind) rvalue) return + ref opAssign(RIndexIterator, RIterator)(auto ref const Series!(RIndexIterator, RIterator, N, kind) rvalue) scope return if (isAssignable!(IndexIterator, LightConstOf!RIndexIterator) && isAssignable!(Iterator, LightConstOf!RIterator)) { return this = rvalue.opIndex; } /// ditto - ref opAssign(RIndexIterator, RIterator)(auto ref immutable Series!(RIndexIterator, RIterator, N, kind) rvalue) return + ref opAssign(RIndexIterator, RIterator)(auto ref immutable Series!(RIndexIterator, RIterator, N, kind) rvalue) scope return if (isAssignable!(IndexIterator, LightImmutableOf!RIndexIterator) && isAssignable!(Iterator, LightImmutableOf!RIterator)) { return this = rvalue.opIndex; } /// ditto - ref opAssign(typeof(null)) return + ref opAssign(typeof(null)) scope return { return this = this.init; } @@ -1270,33 +1274,33 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co } /// - Series!(LightScopeOf!IndexIterator, LightScopeOf!Iterator, N, kind) lightScope()() @trusted scope return @property + Series!(LightScopeOf!IndexIterator, LightScopeOf!Iterator, N, kind) lightScope()() return scope @trusted @property { - return typeof(return)(lightScopeIndex, _data.lightScope); + return typeof(return)(lightScopeIndex, this.data.lightScope); } /// ditto - Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() @trusted scope return const @property + Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() return scope @trusted const @property { - return typeof(return)(lightScopeIndex, _data.lightScope); + return typeof(return)(lightScopeIndex, this.data.lightScope); } /// ditto - Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() @trusted scope return immutable @property + Series!(LightConstOf!(LightScopeOf!IndexIterator), LightConstOf!(LightScopeOf!Iterator), N, kind) lightScope()() return scope @trusted immutable @property { - return typeof(return)(lightScopeIndex, _data.lightScope); + return typeof(return)(lightScopeIndex, this.data.lightScope); } /// - Series!(LightConstOf!IndexIterator, LightConstOf!Iterator, N, kind) lightConst()() scope return const @property @trusted + Series!(LightConstOf!IndexIterator, LightConstOf!Iterator, N, kind) lightConst()() const @property @trusted { - return index.series(data); + return index[].series(data[]); } /// - Series!(LightImmutableOf!IndexIterator, LightImmutableOf!Iterator, N, kind) lightImmutable()() scope return immutable @property @trusted + Series!(LightImmutableOf!IndexIterator, LightImmutableOf!Iterator, N, kind) lightImmutable()() immutable @property @trusted { - return index.series(data); + return index[].series(data[]); } /// @@ -1306,72 +1310,20 @@ struct mir_series(IndexIterator_, Iterator_, size_t N_ = 1, SliceKind kind_ = Co } /// - void toString(Writer, Spec)(auto ref Writer w, const ref Spec f) const + void toString(Writer)(scope ref Writer w) scope const @safe { - import std.format: formatValue, formatElement; - import std.range: put; - - if (f.spec != 's' && f.spec != '(') - throw new Exception("incompatible format character for Mir Series argument: %" ~ f.spec); - - enum defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator; - auto fmtSpec = f.spec == '(' ? f.nested : defSpec; - - if (f.spec == 's') - put(w, f.seqBefore); - if (length) for (size_t i = 0;;) - { - auto fmt = Spec(fmtSpec); - fmt.writeUpToNextSpec(w); - if (f.flDash) - { - formatValue(w, index[i], fmt); - fmt.writeUpToNextSpec(w); - formatValue(w, data[i], fmt); - } - else - { - formatElement(w, index[i], fmt); - fmt.writeUpToNextSpec(w); - formatElement(w, data[i], fmt); - } - if (f.sep !is null) - { - fmt.writeUpToNextSpec(w); - if (++i != length) - put(w, f.sep); - else - break; - } - else - { - if (++i != length) - fmt.writeUpToNextSpec(w); - else - break; - } - } - if (f.spec == 's') - put(w, f.seqAfter); + import mir.format: print; + scope ls = lightScope; + print(w, "{ index: "); + print(w, ls.index); + print(w, ", data: "); + print(w, ls.data); + print(w, " }"); } - version(mir_test) - /// - unittest - { - import mir.series: series, sort; - auto s = ["b", "a"].series([9, 8]).sort; - - import std.conv : to; - assert(s.to!string == `["a":8, "b":9]`); - - import std.format : format; - assert("%s".format(s) == `["a":8, "b":9]`); - assert("%(%s %s | %)".format(s) == `"a" 8 | "b" 9`); - assert("%-(%s,%s\n%)\n".format(s) == "a,8\nb,9\n"); - } } + /// ditto alias Series = mir_series; @@ -1396,11 +1348,11 @@ alias Series = mir_series; assert(cseries.upperBound(2) == cseries[2 .. $]); // slicing type deduction for const / immutable series - static assert(is(typeof(series[]) == + static assert(is(typeof(series[]) == Series!(int*, double*))); - static assert(is(typeof(cseries[]) == + static assert(is(typeof(cseries[]) == Series!(const(int)*, const(double)*))); - static assert(is(typeof((cast(immutable) series)[]) == + static assert(is(typeof((cast(immutable) series)[]) == Series!(immutable(int)*, immutable(double)*))); /// slicing @@ -1426,7 +1378,7 @@ alias Series = mir_series; /// 2-dimensional data @safe pure version(mir_test) unittest { - import std.datetime: Date; + import mir.date: Date; import mir.ndslice.topology: canonical, iota; size_t row_length = 5; @@ -1478,10 +1430,21 @@ alias Series = mir_series; auto fun(Map a = null) { - + } } + version(mir_test) +/// +@safe unittest +{ + import mir.series: series, sort; + auto s = ["b", "a"].series([9, 8]).sort; + + import mir.format : text; + assert(s.text == `{ index: [a, b], data: [8, 9] }`); +} + /++ Convenient function for $(LREF Series) construction. See_also: $(LREF assocArray) @@ -1531,7 +1494,7 @@ Returns: See_also: $(LREF assocArray) */ Series!(K*, V*) series(RK, RV, K = RK, V = RV)(RV[RK] aa) - if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) + if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) { import mir.conv: to; const size_t length = aa.length; @@ -1559,7 +1522,7 @@ Series!(K*, V*) series(RK, RV, K = RK, V = RV)(RV[RK] aa) { import mir.conv: emplaceRef; emplaceRef!K(it.index.front, kv.key.to!K); - emplaceRef!V(it._data.front, kv.value.to!V); + emplaceRef!V(it.data.front, kv.value.to!V); it.popFront; } .sort(ret); @@ -1571,21 +1534,21 @@ Series!(K*, V*) series(RK, RV, K = RK, V = RV)(RV[RK] aa) /// ditto Series!(RK*, RV*) series(K, V, RK = const K, RV = const V)(const V[K] aa) - if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) + if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) { return .series!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); } /// ditto Series!(RK*, RV*) series( K, V, RK = immutable K, RV = immutable V)(immutable V[K] aa) - if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) + if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) { return .series!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); } /// ditto auto series(K, V)(V[K]* aa) - if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) + if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) { return series(*a); } @@ -1624,7 +1587,7 @@ Returns: See_also: $(LREF assocArray) */ auto rcseries(RK, RV, K = RK, V = RV)(RV[RK] aa) - if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) + if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) { import mir.rc.array; import mir.conv: to; @@ -1636,10 +1599,10 @@ auto rcseries(RK, RV, K = RK, V = RV)(RV[RK] aa) { import mir.conv: emplaceRef; emplaceRef!K(it.lightScopeIndex.front, kv.key.to!K); - emplaceRef!V(it._data.front, kv.value.to!V); + emplaceRef!V(it.data.front, kv.value.to!V); it.popFront; } - static if (__VERSION__ >= 2085) import core.lifetime: move; else import std.algorithm.mutation: move; + import core.lifetime: move; .sort(ret.lightScope); static if (is(typeof(ret) == R)) return ret; @@ -1649,7 +1612,7 @@ auto rcseries(RK, RV, K = RK, V = RV)(RV[RK] aa) /// ditto auto rcseries(K, V, RK = const K, RV = const V)(const V[K] aa) - if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) + if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) { return .rcseries!(K, V, RK, RV)((()@trusted => cast(V[K]) aa)()); } @@ -1663,7 +1626,7 @@ auto rcseries( K, V, RK = immutable K, RV = immutable V)(immutable V[K] aa) /// ditto auto rcseries(K, V)(V[K]* aa) - if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) + if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) { return rcseries(*a); } @@ -1732,7 +1695,7 @@ Series!(K*, V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K] a /// ditto Series!(K*, V*) makeSeries(Allocator, K, V)(auto ref Allocator allocator, V[K]* aa) - if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) + if (is(typeof(K.init < K.init)) && is(typeof(Unqual!K.init < Unqual!K.init))) { return makeSeries(allocator, *a); } @@ -1752,7 +1715,7 @@ pure nothrow version(mir_test) unittest auto indexArray = s.index.field; auto dataArray = s.data.field; - + assert(s.index == [1, 2, 3]); assert(s.data == [1.5, 2.9, 3.3]); assert(s.data[s.findIndex(2)] == 2.9); @@ -1777,8 +1740,8 @@ then the result will contain the value of the last pair for that key in r. auto assocArray(IndexIterator, Iterator, size_t N, SliceKind kind) (Series!(IndexIterator, Iterator, N, kind) series) { - alias SK = series.Key; - alias SV = series.Value; + alias SK = series.Index; + alias SV = series.Data; alias UK = Unqual!SK; alias UV = Unqual!SV; static if (isImplicitlyConvertible!(SK, UK)) @@ -1825,7 +1788,7 @@ Returns: size_t findIndex(IndexIterator, Iterator, size_t N, SliceKind kind, Index)(Series!(IndexIterator, Iterator, N, kind) series, auto ref scope const Index key) { auto idx = series.lightScopeIndex.transitionIndex(key); - if (idx < series._data._lengths[0] && series.index[idx] == key) + if (idx < series.data._lengths[0] && series.index[idx] == key) { return idx; } @@ -1855,7 +1818,7 @@ Returns: size_t find(IndexIterator, Iterator, size_t N, SliceKind kind, Index)(Series!(IndexIterator, Iterator, N, kind) series, auto ref scope const Index key) { auto idx = series.lightScopeIndex.transitionIndex(key); - auto bidx = series._data._lengths[0] - idx; + auto bidx = series.data._lengths[0] - idx; if (bidx && series.index[idx] == key) { return bidx; @@ -1891,7 +1854,7 @@ Params: +/ template troykaGalop(alias lfun, alias cfun, alias rfun) { - import std.range.primitives: isInputRange; + import mir.primitives: isInputRange; /++ Params: @@ -2229,21 +2192,49 @@ Merges multiple (time) series into one. Makes exactly one memory allocation for two series union and two memory allocation for three and more series union. -Params: - seriesTuple = variadic static array of composed of series, each series must be sorted. Returns: sorted GC-allocated series. See_also $(LREF Series.opBinary) $(LREF makeUnionSeries) */ -auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C)(Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...) - if (C > 1) +auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( + Series!(IndexIterator, Iterator, N, kind) a, + Series!(IndexIterator, Iterator, N, kind) b, + ) @safe +{ + import core.lifetime: move; + Series!(IndexIterator, Iterator, N, kind)[2] ar = [move(a), move(b)]; + return unionSeriesImplPrivate!false(move(ar)); +} + +/// ditto +auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( + Series!(IndexIterator, Iterator, N, kind) a, + Series!(IndexIterator, Iterator, N, kind) b, + Series!(IndexIterator, Iterator, N, kind) c, + ) @safe +{ + import core.lifetime: move; + Series!(IndexIterator, Iterator, N, kind)[3] ar = [move(a), move(b), move(c)]; + return unionSeriesImplPrivate!false(move(ar)); +} + +/// ditto +auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( + Series!(IndexIterator, Iterator, N, kind) a, + Series!(IndexIterator, Iterator, N, kind) b, + Series!(IndexIterator, Iterator, N, kind) c, + Series!(IndexIterator, Iterator, N, kind) d, + ) @safe { - return unionSeriesImplPrivate!false(seriesTuple); + import core.lifetime: move; + Series!(IndexIterator, Iterator, N, kind)[4] ar = [move(a), move(b), move(c), move(d)]; + return unionSeriesImplPrivate!false(move(ar)); } + /// @safe pure nothrow version(mir_test) unittest { - import std.datetime: Date; + import mir.date: Date; ////////////////////////////////////// // Constructs two time-series. @@ -2272,7 +2263,7 @@ auto unionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C)(Se /// @safe pure nothrow version(mir_test) unittest { - import std.datetime: Date; + import mir.date: Date; ////////////////////////////////////// // Constructs three time-series. @@ -2362,15 +2353,42 @@ auto makeUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C /** Merges multiple (time) series into one. -Params: - seriesTuple = variadic static array of composed of series. Returns: sorted manually allocated series. See_also $(LREF unionSeries) */ -auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C)(Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple...) - if (C > 1) +auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( + Series!(IndexIterator, Iterator, N, kind) a, + Series!(IndexIterator, Iterator, N, kind) b, + ) @safe +{ + import core.lifetime: move; + Series!(IndexIterator, Iterator, N, kind)[2] ar = [move(a), move(b)]; + return unionSeriesImplPrivate!true(move(ar)); +} + +///ditto +auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( + Series!(IndexIterator, Iterator, N, kind) a, + Series!(IndexIterator, Iterator, N, kind) b, + Series!(IndexIterator, Iterator, N, kind) c, + ) @safe +{ + import core.lifetime: move; + Series!(IndexIterator, Iterator, N, kind)[3] ar = [move(a), move(b), move(c)]; + return unionSeriesImplPrivate!true(move(ar)); +} + +///ditto +auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind)( + Series!(IndexIterator, Iterator, N, kind) a, + Series!(IndexIterator, Iterator, N, kind) b, + Series!(IndexIterator, Iterator, N, kind) c, + Series!(IndexIterator, Iterator, N, kind) d, + ) @safe { - return unionSeriesImplPrivate!true(seriesTuple); + import core.lifetime: move; + Series!(IndexIterator, Iterator, N, kind)[4] ar = [move(a), move(b), move(c), move(d)]; + return unionSeriesImplPrivate!true(move(ar)); } /// @@ -2407,23 +2425,23 @@ auto rcUnionSeries(IndexIterator, Iterator, size_t N, SliceKind kind, size_t C)( Initialize preallocated series using union of multiple (time) series. Doesn't make any allocations. -Params: +Params: seriesTuple = dynamic array composed of series. uninitSeries = uninitialized series with exactly required length. */ pragma(inline, false) auto unionSeriesImpl(I, E, IndexIterator, Iterator, size_t N, SliceKind kind, UI, UE)( - Series!(IndexIterator, Iterator, N, kind)[] seriesTuple, + scope Series!(IndexIterator, Iterator, N, kind)[] seriesTuple, Series!(UI*, UE*, N) uninitSeries, - ) + ) @trusted { import mir.conv: emplaceRef; import mir.algorithm.setops: multiwayUnion; enum N = N; alias I = DeepElementType!(typeof(seriesTuple[0].index)); - alias E = DeepElementType!(typeof(seriesTuple[0]._data)); + alias E = DeepElementType!(typeof(seriesTuple[0].data)); if(uninitSeries.length) { @@ -2433,9 +2451,9 @@ auto unionSeriesImpl(I, E, auto obs = u.front; emplaceRef!I(uninitSeries.index.front, obs.index); static if (N == 1) - emplaceRef!E(uninitSeries._data.front, obs.data); + emplaceRef!E(uninitSeries.data.front, obs.data); else - each!(emplaceRef!E)(uninitSeries._data.front, obs.data); + each!(emplaceRef!E)(uninitSeries.data.front, obs.data); u.popFront; uninitSeries.popFront; } @@ -2443,7 +2461,7 @@ auto unionSeriesImpl(I, E, } } -private auto unionSeriesImplPrivate(bool rc, IndexIterator, Iterator, size_t N, SliceKind kind, size_t C, Allocator...)(ref Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple, ref Allocator allocator) +private auto unionSeriesImplPrivate(bool rc, IndexIterator, Iterator, size_t N, SliceKind kind, size_t C, Allocator...)(scope Series!(IndexIterator, Iterator, N, kind)[C] seriesTuple, ref Allocator allocator) @safe if (C > 1 && Allocator.length <= 1) { import mir.algorithm.setops: unionLength; @@ -2457,7 +2475,7 @@ private auto unionSeriesImplPrivate(bool rc, IndexIterator, Iterator, size_t N, foreach (i; Iota!C) indeces[i] = seriesTuple[i].index; - immutable len = indeces[].unionLength; + immutable len = (()@trusted => indeces[].unionLength)(); alias I = typeof(seriesTuple[0].index.front); alias E = typeof(seriesTuple[0].data.front); @@ -2470,12 +2488,12 @@ private auto unionSeriesImplPrivate(bool rc, IndexIterator, Iterator, size_t N, static if (N > 1) { - auto shape = seriesTuple[0]._data._lengths; + auto shape = seriesTuple[0].data._lengths; shape[0] = len; foreach (ref sl; seriesTuple[1 .. $]) foreach (i; Iota!(1, N)) - if (seriesTuple._data[0]._lengths[i] != sl._data._lengths[i]) + if (seriesTuple.data[0]._lengths[i] != sl.data._lengths[i]) assert(0, "shapes mismatch"); } else @@ -2519,7 +2537,7 @@ private auto unionSeriesImplPrivate(bool rc, IndexIterator, Iterator, size_t N, } else { - unionSeriesImpl!(I, E)(seriesTuple, ret.lightScope); + unionSeriesImpl!(I, E)((()@trusted => seriesTuple[])(), ret.lightScope); } return () @trusted {return *cast(R*) &ret; }(); @@ -2531,7 +2549,7 @@ Params: aa = associative array series = series Returns: - associative array + associative array */ ref V[K] insertOrAssign(V, K, IndexIterator, Iterator, size_t N, SliceKind kind)(return ref V[K] aa, auto ref Series!(IndexIterator, Iterator, N, kind) series) @property { @@ -2558,7 +2576,7 @@ Params: aa = associative array series = series Returns: - associative array + associative array */ ref V[K] insert(V, K, IndexIterator, Iterator, size_t N, SliceKind kind)(return ref V[K] aa, auto ref Series!(IndexIterator, Iterator, N, kind) series) @property { @@ -2580,96 +2598,3 @@ ref V[K] insert(V, K, IndexIterator, Iterator, size_t N, SliceKind kind)(return a.insert = s; assert(a.series == series([1, 2, 3, 4], [3.0, 20, 30, 2])); } - - -static if (__VERSION__ < 2078) -//////////////////// OBJECT.d -{ - -private: - -extern (C) -{ - // from druntime/src/rt/aaA.d - - // size_t _aaLen(in void* p) pure nothrow @nogc; - private void* _aaGetY(void** paa, const TypeInfo_AssociativeArray ti, in size_t valuesize, in void* pkey) pure nothrow; - // inout(void)* _aaGetRvalueX(inout void* p, in TypeInfo keyti, in size_t valuesize, in void* pkey); - inout(void)[] _aaValues(inout void* p, in size_t keysize, in size_t valuesize, const TypeInfo tiValArray) pure nothrow; - inout(void)[] _aaKeys(inout void* p, in size_t keysize, const TypeInfo tiKeyArray) pure nothrow; - void* _aaRehash(void** pp, in TypeInfo keyti) pure nothrow; - void _aaClear(void* p) pure nothrow; - - // alias _dg_t = extern(D) int delegate(void*); - // int _aaApply(void* aa, size_t keysize, _dg_t dg); - - // alias _dg2_t = extern(D) int delegate(void*, void*); - // int _aaApply2(void* aa, size_t keysize, _dg2_t dg); - - // private struct AARange { void* impl; size_t idx; } - alias AARange = ReturnType!(object._aaRange); - AARange _aaRange(void* aa) pure nothrow @nogc @safe; - bool _aaRangeEmpty(AARange r) pure nothrow @nogc @safe; - void* _aaRangeFrontKey(AARange r) pure nothrow @nogc @safe; - void* _aaRangeFrontValue(AARange r) pure nothrow @nogc @safe; - void _aaRangePopFront(ref AARange r) pure nothrow @nogc @safe; - -} - -auto byKeyValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe -{ - import core.internal.traits : substInout; - - static struct Result - { - AARange r; - - pure nothrow @nogc: - @property bool empty() @safe { return _aaRangeEmpty(r); } - @property auto front() - { - static struct Pair - { - // We save the pointers here so that the Pair we return - // won't mutate when Result.popFront is called afterwards. - private void* keyp; - private void* valp; - - @property ref key() inout - { - auto p = (() @trusted => cast(substInout!K*) keyp) (); - return *p; - }; - @property ref value() inout - { - auto p = (() @trusted => cast(substInout!V*) valp) (); - return *p; - }; - } - return Pair(_aaRangeFrontKey(r), - _aaRangeFrontValue(r)); - } - void popFront() @safe { return _aaRangePopFront(r); } - @property Result save() { return this; } - } - - return Result(_aaToRange(aa)); -} - -auto byKeyValue(T : V[K], K, V)(T* aa) pure nothrow @nogc -{ - return (*aa).byKeyValue(); -} - -// this should never be made public. -private AARange _aaToRange(T: V[K], K, V)(ref T aa) pure nothrow @nogc @safe -{ - // ensure we are dealing with a genuine AA. - static if (is(const(V[K]) == const(T))) - alias realAA = aa; - else - const(V[K]) realAA = aa; - return _aaRange(() @trusted { return cast(void*)realAA; } ()); -} - -} diff --git a/source/mir/small_array.d b/source/mir/small_array.d new file mode 100644 index 00000000..a070ac45 --- /dev/null +++ b/source/mir/small_array.d @@ -0,0 +1,345 @@ +/++ +$(H1 Small Array) + +The module contains self-contained generic small array implementaton. + +$(LREF SmallArray) supports ASDF - Json Serialisation Library. + +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki ++/ +module mir.small_array; + +private static immutable errorMsg = "Cannot create SmallArray: input range exceeds maximum allowed length."; +version(D_Exceptions) + private static immutable exception = new Exception(errorMsg); + +/// +template SmallArray(T, uint maxLength) + if (maxLength) +{ + import mir.serde: serdeProxy, serdeScoped; + import std.traits: Unqual, isIterable, isImplicitlyConvertible; + + static if (isImplicitlyConvertible!(const T, T)) + alias V = const T; + else + alias V = T; + + /// + @serdeScoped + @serdeProxy!(V[]) + struct SmallArray + { + uint _length; + T[maxLength] _data; + + /// + alias serdeKeysProxy = T; + + @safe pure @nogc: + + /// Constructor + this(typeof(null)) + { + } + + /// ditto + this(scope V[] array) + { + this.opAssign(array); + } + + /// ditto + this(const SmallArray array) nothrow + { + this.opAssign(array); + } + + /// ditto + this(uint n)(const SmallArray!(T, n) array) + { + this.opAssign(array); + } + + /// ditto + this(uint n)(ref const SmallArray!(T, n) array) + { + this.opAssign(array); + } + + /// ditto + this(Range)(auto ref Range array) + if (isIterable!Range) + { + foreach(ref c; array) + { + if (_length > _data.length) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } + else assert(0, errorMsg); + } + _data[_length++] = c; + } + } + + /// `=` operator + ref typeof(this) opAssign(typeof(null)) scope return + { + _length = 0; + _data = T.init; + return this; + } + + /// ditto + ref typeof(this) opAssign(V[] array) scope return @trusted + { + if (array.length > maxLength) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } + else assert(0, errorMsg); + } + _data[0 .. _length] = T.init; + _length = cast(uint) array.length; + _data[0 .. _length] = array; + return this; + } + + /// ditto + ref typeof(this) opAssign(ref const SmallArray rhs) scope return nothrow + { + _length = rhs._length; + _data = rhs._data; + return this; + } + + /// ditto + ref typeof(this) opAssign(const SmallArray rhs) scope return nothrow + { + _length = rhs._length; + _data = rhs._data; + return this; + } + + /// ditto + ref typeof(this) opAssign(uint n)(ref const SmallArray!(T, n) rhs) scope return + if (n != maxLength) + { + static if (n < maxLength) + { + _data[0 .. n] = rhs._data; + if (_length > n) + _data[n .. _length] = T.init; + } + else + { + if (rhs._length > maxLength) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } + else assert(0, errorMsg); + } + _data = rhs._data[0 .. maxLength]; + } + _length = rhs._length; + return this; + } + + /// ditto + ref typeof(this) opAssign(uint n)(const SmallArray!(T, n) rhs) scope return + if (n != maxLength) + { + static if (n < maxLength) + { + _data[0 .. n] = rhs._data; + if (_length > n) + _data[n .. _length] = T.init; + } + else + { + if (rhs._length > maxLength) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } + else assert(0, errorMsg); + } + _data = rhs._data[0 .. maxLength]; + } + _length = rhs._length; + return this; + } + + /// + void trustedAssign(V[] array) @trusted + { + _data[0 .. _length] = T.init; + _length = cast(uint) array.length; + _data[0 .. _length] = array; + } + + /// + ref typeof(this) append(T elem) + { + import core.lifetime: move; + if (_length == maxLength) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } + else assert(0, errorMsg); + } + _data[_length++] = move(elem); + return this; + } + + /// + void trustedAppend(T elem) + { + import core.lifetime: move; + _data[_length++] = move(elem); + } + + /// + ref typeof(this) append(V[] array) + { + if (_length + array.length > maxLength) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } + else assert(0, errorMsg); + } + _data[_length .. _length + array.length] = array; + _length += array.length; + return this; + } + + /// ditto + alias put = append; + + /// ditto + alias opOpAssign(string op : "~") = append; + + /// + SmallArray concat(V[] array) scope const + { + SmallArray c = this; + c.append(array); + return c; + } + + /// ditto + alias opBinary(string op : "~") = concat; + + /++ + Returns an scope common array. + + The property is used as common array representation self alias. + + The alias helps with `[]`, `[i]`, `[i .. j]`, `==`, and `!=` operations and implicit conversion to strings. + +/ + inout(T)[] opIndex() inout @trusted scope return + { + return _data[0 .. _length]; + } + + /// + ref inout(T) opIndex(size_t index) inout scope return + { + return opIndex[index]; + } + + scope const: + + /// + bool empty() @property + { + return _length == 0; + } + + /// + size_t length() @property + { + return _length; + } + + /// Hash implementation + size_t toHash() + { + return hashOf(opIndex); + } + + /// Comparisons operator overloads + bool opEquals(scope ref const SmallArray rhs) + { + return opIndex == rhs.opIndex; + } + + /// ditto + bool opEquals(SmallArray rhs) + { + return opIndex == rhs.opIndex; + } + + /// ditto + bool opEquals()(const scope V[] array) + { + return opIndex == array; + } + + /// ditto + bool opEquals(uint rhsMaxLength)(auto ref scope SmallArray!(T, rhsMaxLength) array) + { + return opIndex == array.opIndex; + } + + /// ditto + auto opCmp()(const scope V[] array) + { + return __cmp(opIndex, array); + } + + /// ditto + auto opCmp(uint rhsMaxLength)(auto ref scope const SmallArray!(T, rhsMaxLength) array) + { + return __cmp(opIndex, array.opIndex); + } + } +} + +/// +@safe pure @nogc version(mir_test) unittest +{ + SmallArray!(char, 16) s16; + assert(s16.empty); + + auto s8 = SmallArray!(char, 8)("Hellow!!"); + assert(!s8.empty); + assert(s8 == "Hellow!!"); + + s16 = s8; + assert(s16 == "Hellow!!"); + s16[7] = '@'; + s8 = null; + assert(s8.empty); + assert(s8 == null); + s8 = s16; + assert(s8 == "Hellow!@"); + + auto s8_2 = s8; + assert(s8_2 == "Hellow!@"); + assert(s8_2 == s8); + + assert(s8 < "Hey"); + assert(s8 > "Hellow!"); + + assert(s8.opCmp("Hey") < 0); + assert(s8.opCmp(s8) == 0); +} + +/// Concatenation +@safe pure @nogc version(mir_test) unittest +{ + auto a = SmallArray!(char, 16)("asdf"); + a ~= " "; + auto b = a ~ "qwerty"; + static assert(is(typeof(b) == SmallArray!(char, 16))); + assert(b == "asdf qwerty"); + b.put('!'); + b.put("!"); + assert(b == "asdf qwerty!!"); +} diff --git a/source/mir/small_string.d b/source/mir/small_string.d index 952e2560..6d342abb 100644 --- a/source/mir/small_string.d +++ b/source/mir/small_string.d @@ -6,74 +6,55 @@ The module contains self-contained generic small string implementaton. $(LREF SmallString) supports ASDF - Json Serialisation Library. See also `include/mir/small_series.h` for a C++ version of this type. -Both C++ and D versions has the same ABI and name mangling. +Both C++ and D implementations have the same ABI and name mangling. -Copyright: Copyright © 2019, Symmetry Investments, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki +/ module mir.small_string; -private extern (C) @system nothrow @nogc pure size_t strnlen_s(scope const char* s, size_t n); +import mir.serde: serdeScoped, serdeProxy; private static immutable errorMsg = "Cannot create SmallString: input string exceeds maximum allowed length."; version(D_Exceptions) private static immutable exception = new Exception(errorMsg); -extern(C++, "mir"): - /++ Self-contained generic Small String implementaton. +/ +extern(C++, "mir") +@serdeScoped @serdeProxy!(const(char)[]) struct SmallString(uint maxLength) if (maxLength) { - import core.stdc.string: memcmp, memcpy, strlen; - import std.traits: isIterable; + import core.stdc.string: memcmp, memcpy; + import std.traits: Unqual, isIterable; // maxLength bytes - private char[maxLength] _data = '\0'; - -extern(D): - - static SmallString deserialize(S)(S data) - { - import asdf.serialization: deserialize; - return SmallString(data.deserialize!(const(char)[])); - } - - void serialize(S)(ref S serializer) - { - serializer.putValue(asArray); - } + char[maxLength] _data = '\0'; -@safe pure @nogc: +extern(D) @safe pure @nogc: /// Constructor - this(typeof(null)) - { - } - - /// ditto - this(scope const(char)[] str) + this(typeof(null)) scope { - this.opAssign(str); } /// ditto - this(uint n)(SmallString!n str) + this(scope const(char)[] str) scope { this.opAssign(str); } /// ditto - this(uint n)(ref scope const SmallString!n str) + this(uint n)(auto ref scope const SmallString!n str) scope { this.opAssign(str); } /// ditto - this(Range)(auto ref Range str) + this(Range)(auto ref scope Range str) scope if (isIterable!Range) { size_t i = 0; @@ -81,7 +62,7 @@ extern(D): { if (i > _data.length) { - version(D_Exceptions) throw exception; + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } else assert(0, errorMsg); } _data[i++] = c; @@ -89,66 +70,54 @@ extern(D): } /// `=` operator - ref typeof(this) opAssign(typeof(null)) return + ref typeof(this) opAssign(typeof(null)) scope return { _data = '\0'; return this; } /// ditto - ref typeof(this) opAssign(scope const(char)[] str) return @trusted + ref typeof(this) opAssign(scope const(char)[] str) scope return @trusted { + _data = '\0'; if (str.length > _data.sizeof) { - version(D_Exceptions) throw exception; + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } else assert(0, errorMsg); } if (__ctfe) - (cast(ubyte[])_data)[0 .. str.length] = cast(ubyte[]) str; + _data[0 .. str.length] = str; else memcpy(_data.ptr, str.ptr, str.length); return this; } /// ditto - ref typeof(this) opAssign(ref scope const SmallString rhs) return - { - _data = rhs._data; - return this; - } - - /// ditto - ref typeof(this) opAssign(SmallString rhs) return - { - _data = rhs._data; - return this; - } - - /// ditto - ref typeof(this) opAssign(uint n)(ref scope const SmallString!n rhs) return + ref typeof(this) opAssign(uint n)(auto ref scope const SmallString!n rhs) scope return if (n != maxLength) { static if (n < maxLength) { + _data = '\0'; version (LDC) cast(char[n])(_data[0 .. n]) = rhs._data; else _data[0 .. n] = rhs._data; - _data[n .. maxLength] = '\0'; } else { if (rhs._data[maxLength]) { - version(D_Exceptions) throw exception; + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } else assert(0, errorMsg); } - _data = cast(char[0 .. maxLength])(rhs._data[0 .. maxLength]); + _data = cast(char[maxLength])(rhs._data[0 .. maxLength]); } + return this; } /// ditto - ref typeof(this) opAssign(uint n)(SmallString!n rhs) return + ref typeof(this) opAssign(uint n)(const SmallString!n rhs) scope return if (n != maxLength) { static if (n < maxLength) @@ -163,7 +132,7 @@ extern(D): { if (rhs._data[maxLength]) { - version(D_Exceptions) throw exception; + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } else assert(0, errorMsg); } _data = cast(char[maxLength])(rhs._data[0 .. maxLength]); @@ -171,91 +140,179 @@ extern(D): return this; } + /// ditto + void trustedAssign(scope const(char)[] str) return @trusted nothrow + { + _data = '\0'; + if (__ctfe) + _data[0 .. str.length] = str; + else + memcpy(_data.ptr, str.ptr, str.length); + } + + /// + ref typeof(this) append(char c) @trusted + { + auto length = opIndex.length; + if (length == maxLength) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } + else assert(0, errorMsg); + } + _data[length] = c; + return this; + } + + /// + ref typeof(this) append(scope const(char)[] str) @trusted + { + auto length = opIndex.length; + if (length + str.length > maxLength) + { + version(D_Exceptions) { import mir.exception : toMutable; throw exception.toMutable; } + else assert(0, errorMsg); + } + if (__ctfe) + _data[length .. str.length + length] = str; + else + memcpy(_data.ptr + length, str.ptr, str.length); + return this; + } + + /// ditto + alias put = append; + + /// ditto + alias opOpAssign(string op : "~") = append; + + /// + SmallString concat(scope const(char)[] str) scope const + { + SmallString c = this; + c.append(str); + return c; + } + + /// ditto + alias opBinary(string op : "~") = concat; + + /// + extern (D) size_t toHash() const nothrow @safe + { + return hashOf(this[]); + } + scope nothrow: /++ Returns an scope common string. - The property is used as common string representation self alias. + The method implement with `[]` operation. + +/ + inout(char)[] opIndex() inout @trusted scope return + { + import mir.string: scanLeftAny; + return _data[0 .. $ - _data.scanLeftAny('\0').length]; + } - The alias helps with `[]`, `[i]`, `[i .. j]`, `==`, and `!=` operations and implicit conversion to strings. + /// + ref inout(char) opIndex(size_t index) inout scope return + { + return opIndex[index]; + } + + /++ + Returns a common scope string. + + The method implement with `[i .. j]` operation. +/ - inout(char)[] asArray() inout @trusted return @property + inout(char)[] opIndex(size_t[2] range) inout @trusted scope return + in (range[0] <= range[1]) + in (range[1] <= this.length) { - size_t i; - if (__ctfe) - while (i < maxLength && _data[i]) i++; - else - i = _data[$ - 1] ? _data.length : strlen(_data.ptr); - return _data[0 .. i]; + return opIndex()[range[0] .. range[1]]; } - alias asArray this; +scope const: -const: + /// ditto + size_t[2] opSlice(size_t pos : 0)(size_t i, size_t j) { + return [i, j]; + } + + /// + bool empty() @property + { + return _data[0] == 0; + } /// - const(char)[] toString() return + size_t length() @property { - return asArray; + return opIndex.length; } + /// ditto + alias opDollar(size_t pos : 0) = length; + + /// + alias toString = opIndex; + /// Comparisons operator overloads - int opCmp(ref scope const typeof(this) rhs) @trusted + bool opEquals(ref scope const SmallString rhs) { - if (__ctfe) - { - foreach (i, ref c; _data) - if (auto d = c - rhs._data[i]) - return d; - return 0; - } - else - { - return memcmp(this._data.ptr, rhs._data.ptr, _data.sizeof); - } + return _data == rhs._data; + } + /// ditto + bool opEquals(SmallString rhs) + { + return _data == rhs._data; } /// ditto - int opCmp(scope const(char)[] str) + bool opEquals(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs) + if (rhsMaxLength != maxLength) { - import mir.algorithm.iteration: cmp; - return cast(int) cmp(asArray, str); + return opIndex == rhs.opIndex; } - /++ - Checks if the string is empty (null). - +/ - bool opCast(T : bool)() + /// ditto + bool opEquals()(scope const(char)[] str) { - return cast(bool) _data[0] != 0; + return opIndex == str; } - /// Hash implementation - size_t toHash() + /// ditto + int opCmp(uint rhsMaxLength)(auto ref scope const SmallString!rhsMaxLength rhs) + { + return __cmp(opIndex, rhs.opIndex); + } + + /// ditto + int opCmp()(scope const(char)[] str) { - return hashOf(_data[]); + return __cmp(opIndex, str); } } /// -@safe pure version(mir_test) unittest +@safe pure @nogc version(mir_test) unittest { SmallString!16 s16; - assert(!s16); + assert(s16.empty); auto s8 = SmallString!8("Hellow!!"); - assert(s8); - assert(s8 == "Hellow!!", s8[]); + assert(s8 == "Hellow!!"); + assert(s8[] == "Hellow!!"); + assert(s8[0 .. $] == "Hellow!!"); + assert(s8[1 .. 4] == "ell"); s16 = s8; - assert(s16 == "Hellow!!", s16); + assert(s16 == "Hellow!!"); s16[7] = '@'; s8 = null; - assert(!s8); - assert(s8 == null); - assert(s8 !is null); + assert(s8.empty); s8 = s16; assert(s8 == "Hellow!@"); @@ -269,3 +326,23 @@ const: assert(s8.opCmp("Hey") < 0); assert(s8.opCmp(s8) == 0); } + +/// Concatenation +@safe pure @nogc version(mir_test) unittest +{ + auto a = SmallString!16("asdf"); + a ~= " "; + auto b = a ~ "qwerty"; + static assert(is(typeof(b) == SmallString!16)); + assert(b == "asdf qwerty"); + b.put('!'); + b.put("!"); + assert(b == "asdf qwerty!!"); +} + +@safe pure @nogc nothrow version(mir_test) unittest +{ + import mir.conv: emplaceRef; + SmallString!32 a, b; + emplaceRef!(const SmallString!32)(a, cast(const)b); +} diff --git a/source/mir/stdio.d b/source/mir/stdio.d new file mode 100644 index 00000000..f4b4b455 --- /dev/null +++ b/source/mir/stdio.d @@ -0,0 +1,344 @@ +/++ +A simple I/O routines around ``. + +The implementation is CTFE-friendly. ++/ +module mir.stdio; + +static import core.stdc.stdio; + +import mir.exception: toMutable; + +/// Writes values in a text form +void writeln(string separator = "", Args...)(auto ref const Args args) + if (Args.length > 0) +{ + dout.write!separator(args); + dout << endl; +} + +/// ditto +void write(string separator = "", Args...)(auto ref const Args args) + if (Args.length > 0) +{ + dout.write!separator(args); +} + +/// Writes values in a text form using nothrow $(LREF tout) +void dump(string separator = " ", Args...)(auto ref const Args args) + if (Args.length > 0) +{ + tout.write!separator(args); + tout << endl; +} + +/// Standart output +File dout()() @trusted nothrow @nogc @property +{ + version(LDC) + pragma(inline, true); + return File(__ctfe ? null : core.stdc.stdio.stdout); +} + +/// ditto +File derr()() @trusted nothrow @nogc @property +{ + version(LDC) + pragma(inline, true); + return File(__ctfe ? null : core.stdc.stdio.stderr); +} + +/// +version(mir_test) +@safe @nogc +unittest +{ + dout << "mir.stdio.dout test! - @nogc I/O" << endl; + derr << "mir.stdio.derr test! - @nogc I/O" << endl; +} + +/++ +Nothrow standart output to use in pair with `debug` expression in nothrow +and pure code for testing purpose. + +See_also: $(LREF AssumeNothrowFile) ++/ +AssumeNothrowFile tout()() @trusted nothrow @nogc @property +{ + version(LDC) + pragma(inline, true); + return AssumeNothrowFile(__ctfe ? null : core.stdc.stdio.stdout); +} + +/// ditto +AssumeNothrowFile terr()() @trusted nothrow @nogc @property +{ + version(LDC) + pragma(inline, true); + return AssumeNothrowFile(__ctfe ? null : core.stdc.stdio.stderr); +} + +/// +version(mir_test) +pure @safe @nogc nothrow +unittest +{ + debug tout << "mir.stdio.tout test! - @nogc nothrow I/O" << endl; + debug terr << "mir.stdio.terr test! - @nogc nothrow I/O" << endl; +} + +/++ +When used as `file << endl` it adds new line flushes the stream. ++/ +enum NewLine +{ + lf = "\n", + lf_cf = "\r\n", +} + +/// ditto +enum endl = NewLine.lf; + +/++ ++/ +struct File +{ + /// + core.stdc.stdio.FILE* fp; + + mixin FileMembers; + +@trusted @nogc: + + /++ + Throws: $(LREF FileException) + +/ + void rawWrite(scope const(void)[] data) scope + in (__ctfe || fp !is null) + { + if (__ctfe) + return; + core.stdc.stdio.fwrite(data.ptr, 1, data.length, fp); + if (core.stdc.stdio.ferror(fp)) + throw writeException.toMutable; + } + + /++ + Throws: $(LREF FileException) + +/ + void flush() scope + in (__ctfe || fp !is null) + { + if (__ctfe) + return; + core.stdc.stdio.fflush(fp); + if (core.stdc.stdio.ferror(fp)) + throw writeException.toMutable; + } +} + +/++ +Nothrow File implementation for testing purposes. +See_also: $(LREF tout), $(LREF terr) ++/ +struct AssumeNothrowFile +{ + /// + core.stdc.stdio.FILE* fp; + + mixin FileMembers; + +@trusted @nogc nothrow: + + /++ + Throws: $(LREF FileError) + +/ + void rawWrite(scope const(void)[] data) scope + in (__ctfe || fp !is null) + { + if (__ctfe) + return; + core.stdc.stdio.fwrite(data.ptr, 1, data.length, fp); + if (core.stdc.stdio.ferror(fp)) + throw writeError.toMutable; + } + + /++ + Throws: $(LREF FileError) + +/ + void flush() scope + in (__ctfe || fp !is null) + { + if (__ctfe) + return; + core.stdc.stdio.fflush(fp); + if (core.stdc.stdio.ferror(fp)) + throw writeError.toMutable; + } +} + +/// +mixin template FileMembers() +{ + /// + void put(C)(const C data) scope + if (is(C == char) || is(C == wchar) | is(C == dchar)) + { + C[1] array = [data]; + this.rawWrite(array); + } + + /// + void put(C)(scope const(C)[] data) scope + if (is(C == char) || is(C == wchar) | is(C == dchar)) + { + this.rawWrite(data); + } + + /// + template opBinary(string op : "<<") + { + /// + ref opBinary(T)(auto ref const T value) scope return + { + if (__ctfe) + return this; + import mir.format: print; + print!char(this, value); + return this; + } + + /// Prints new line and flushes the stream + ref opBinary(NewLine endl) scope return + { + if (__ctfe) + return this; + this.put(endl); + this.flush; + return this; + } + } + + /// Writes values in a text form + void writeln(string separator = "", Args...)(auto ref const Args args) scope + if (Args.length > 0) + { + write(args); + this << endl; + } + + /// ditto + void write(string separator = "", Args...)(auto ref const Args args) scope + if (Args.length > 0) + { + pragma(inline, false); + if (__ctfe) + return; + import mir.format: print, printStaticString; + foreach (i, ref arg; args) + { + print!char(this, arg); + static if (separator.length && i + 1 < args.length) + { + printStaticString!char(this, separator); + } + } + } +} + +/++ +File Exception ++/ +class FileException : Exception +{ + /// + this( + string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) pure nothrow @nogc @safe + { + super(msg, file, line, next); + } + + /// + this( + string msg, + Throwable next, + string file = __FILE__, + size_t line = __LINE__, + ) pure nothrow @nogc @safe + { + this(msg, file, line, next); + } + + FileException toMutable() @trusted pure nothrow @nogc const + { + return cast() this; + } + + alias toMutable this; +} + +/++ +File Error ++/ +class FileError : Error +{ + /// + this( + string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) pure nothrow @nogc @safe + { + super(msg, file, line, next); + } + + /// + this( + string msg, + Throwable next, + string file = __FILE__, + size_t line = __LINE__, + ) pure nothrow @nogc @safe + { + this(msg, file, line, next); + } + + FileError toMutable() @trusted pure nothrow @nogc const + { + return cast() this; + } + + alias toMutable this; +} + +private static immutable writeException = new FileException("Error on file write"); +private static immutable flushException = new FileException("Error on file flush"); +private static immutable writeError = new FileError("Error on file write"); +private static immutable flushError = new FileError("Error on file flush"); + +version(mir_test) +@safe unittest +{ + static struct ParameterSpec + { + string name; + string type; + string default_; + } + + static struct FunctionOverloadSpec + { + ParameterSpec[] parameters; + string doc; + string test; + size_t minArgs; + } + + import mir.algebraic; + import mir.stdio; + if (false) + Algebraic!(FunctionOverloadSpec, ParameterSpec).init.writeln; // error +} diff --git a/source/mir/string.d b/source/mir/string.d new file mode 100644 index 00000000..3fd8e5f6 --- /dev/null +++ b/source/mir/string.d @@ -0,0 +1,393 @@ +/++ +$(H1 String routines) + +The module contains SIMD-accelerated string routines. + +Copyright: 2022 Ilia Ki, Symmetry Investments + +Authors: Ilia Ki ++/ +module mir.string; + +import std.traits: isSomeChar; + +private alias Representation(T : char) = byte; +private alias Representation(T : wchar) = short; +private alias Representation(T : dchar) = int; + +private enum size_t ScanVecSize = 16; + +/// +bool containsAny(C, size_t L) + (scope const(C)[] str, const C[L] chars...) + @trusted pure nothrow @nogc + if (isSomeChar!C && L) +{ + enum size_t NF = ScanVecSize / C.sizeof; + + alias U = Representation!C; + + // version(none) + version (MirNoSIMD) {} + else + version (LittleEndian) + version (LDC) + static if (L <= 8) + static if (is(__vector(U[NF]))) + if (!__ctfe) + { + import mir.bitop: cttzp; + + static foreach (F; 1 .. 2 + (C.sizeof == 1)) + {{ + enum N = NF / F; + alias V = __vector(U[N]); + + V[L] charsv; + static foreach (i; 0 .. L) + charsv[i] = chars[i]; + + while (str.length >= N) + { + auto a = cast(V) *cast(const U[N]*) str.ptr; + + import mir.internal.ldc_simd: mask = equalMask; + + V[L] masked; + static foreach (i; 0 .. L) + masked[i] = mask!(__vector(U[N]))(a, charsv[i]); + + static foreach (i; 0 .. L) + static if (i == 0) + V m = masked[i]; + else + m |= masked[i]; + + static if (U[N].sizeof == size_t.sizeof) + { + size_t[U[N].sizeof / size_t.sizeof] words = [(cast(__vector(size_t[U[N].sizeof / size_t.sizeof])) m).array[0]]; + } + else + { + auto words = (cast(__vector(size_t[U[N].sizeof / size_t.sizeof])) m).array; + } + + foreach (word; words) + if (word) + return true; + + str = str[N .. $]; + + static if (F != 1) + break; + } + + }} + } + + foreach (C c; str) + static foreach (i; 0 .. L) + if (c == chars[i]) + return true; + return false; +} + +/// +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.test: should; + + assert(" hello world ".containsAny('w')); + assert(!" hello world ".containsAny('W')); + assert(" hello world ".containsAny('W', 'e')); + assert(" hello world ".containsAny("We")); +} + +/// +template scanLeftAny(string op = "==") + if (op == "==" || op == "!=") +{ + /// + inout(C)[] + scanLeftAny(C, size_t L) + (return scope inout(C)[] str, const C[L] chars...) + @trusted pure nothrow @nogc + if (isSomeChar!C && L) + { + enum size_t NF = ScanVecSize / C.sizeof; + + alias U = Representation!C; + + // version(none) + version (MirNoSIMD) {} + else + version (LittleEndian) + version (LDC) + static if (L <= 8) + static if (is(__vector(U[NF]))) + if (!__ctfe) + { + import mir.bitop: cttzp; + + static foreach (F; 1 .. 2 + (C.sizeof == 1)) + {{ + enum N = NF / F; + alias V = __vector(U[N]); + V[L] charsv; + static foreach (i; 0 .. L) + charsv[i] = chars[i]; + + while (str.length >= N) + { + auto a = cast(V) *cast(const U[N]*) str.ptr; + + import mir.internal.ldc_simd: mask = equalMask; + + V[L] masked; + static foreach (i; 0 .. L) + masked[i] = mask!(__vector(U[N]))(a, charsv[i]); + + static foreach (i; 0 .. L) + static if (i == 0) + V m = masked[i]; + else + m |= masked[i]; + + static if (op == "!=") + m = ~m; + + static if (U[N].sizeof == size_t.sizeof) + { + size_t[U[N].sizeof / size_t.sizeof] words = [(cast(__vector(size_t[U[N].sizeof / size_t.sizeof])) m).array[0]]; + } + else + { + auto words = (cast(__vector(size_t[U[N].sizeof / size_t.sizeof])) m).array; + } + + size_t p; + + static foreach (i; 0 .. words.length) + { + p += cttzp(words[i]); + if (words[i]) + { + static if (F == 1) + goto L; + else + goto M; + } + } + str = str[N .. $]; + static if (F == 1) + continue; + else + break; + + static if (F == 1) + {L:} + else + {M:} + return str[p / (U.sizeof * 8) .. $]; + } + }} + } + + Loop: for (; str.length; str = str[1 .. $]) + { + auto c = str[0]; + static foreach (i; 0 .. L) + { + if (c == chars[i]) + { + static if (op == "==") + break Loop; + else + continue Loop; + } + } + static if (op == "==") + continue Loop; + else + break Loop; + } + return str; + } +} + +/// +alias stripLeft = scanLeftAny!"!="; + +/// +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.test: should; + + " hello world ".stripLeft(' ').should == "hello world "; + " hello world ".scanLeftAny('w').should == "world "; + " hello world ".scanLeftAny('!').should == ""; + "\t\n\thello world\n\t___".stripLeft('\n', '\t').should == "hello world\n\t___"; + "hello world".stripLeft(' ').should == "hello world"; + "hello world ".stripLeft(' ').should == "hello world "; + + " _____________ hello world " + .stripLeft(' ', '_').should == "hello world "; +} + +/// +template scanRightAny(string op = "==") + if (op == "==" || op == "!=") +{ + /// + inout(C)[] + scanRightAny(C, size_t L) + (return scope inout(C)[] str, const C[L] chars...) + @trusted pure nothrow @nogc + if (isSomeChar!C && L) + { + enum size_t NF = ScanVecSize / C.sizeof; + + alias U = Representation!C; + + // version(none) + version (MirNoSIMD) {} + else + version (LittleEndian) + version (LDC) + static if (L <= 8) + static if (is(__vector(U[NF]))) + if (!__ctfe) + { + import mir.bitop: ctlzp; + + static foreach (F; 1 .. 2 + (C.sizeof == 1)) + {{ + enum N = NF / F; + + alias V = __vector(U[N]); + V[L] charsv; + static foreach (i; 0 .. L) + charsv[i] = chars[i]; + + while (str.length >= N) + { + auto a = cast(V) *cast(const U[N]*) (str.ptr + str.length - N); + + import mir.internal.ldc_simd: mask = equalMask; + + V[L] masked; + static foreach (i; 0 .. L) + masked[i] = mask!(__vector(U[N]))(a, charsv[i]); + + static foreach (i; 0 .. L) + static if (i == 0) + V m = masked[i]; + else + m |= masked[i]; + + static if (op == "!=") + m = ~m; + + static if (U[N].sizeof == size_t.sizeof) + { + size_t[U[N].sizeof / size_t.sizeof] words = [(cast(__vector(size_t[U[N].sizeof / size_t.sizeof])) m).array[0]]; + } + else + { + auto words = (cast(__vector(size_t[U[N].sizeof / size_t.sizeof])) m).array; + } + size_t p; + + static foreach (i; 0 .. words.length) + { + p += ctlzp(words[$ - 1 - i]); + if (words[$ - 1 - i]) + { + static if (F == 1) + goto L; + else + goto M; + } + } + str = str[0 .. $ - N]; + static if (F == 1) + continue; + else + break; + + static if (F == 1) + {L:} + else + {M:} + return str[0 .. $ - p / (U.sizeof * 8)]; + } + }} + } + + Loop: for (; str.length; str = str[0 .. $ - 1]) + { + auto c = str[$ - 1]; + static foreach (i; 0 .. L) + { + if (c == chars[i]) + { + static if (op == "==") + break Loop; + else + continue Loop; + } + } + static if (op == "==") + continue Loop; + else + break Loop; + } + return str; + } +} + +/// +alias stripRight = scanRightAny!"!="; + +/// +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.test: should; + + " hello world ".stripRight(' ').should == " hello world"; + " hello world ".scanRightAny('w').should == " hello w"; + " hello world ".scanRightAny('!').should == ""; + "___\t\n\thello world\n\t".stripRight('\n', '\t').should == "___\t\n\thello world"; + "hello world".stripRight(' ').should == "hello world"; + " hello world".stripRight(' ').should == " hello world"; + + " hello world _____________ " + .stripRight(' ', '_').should == " hello world"; +} + +/// +inout(C)[] + strip(C, size_t L) + (return scope inout(C)[] str, const C[L] chars...) + @safe pure nothrow @nogc + if (isSomeChar!C && L) +{ + return str.stripLeft(chars).stripRight(chars); +} + +/// +version(mir_test) +@safe pure nothrow @nogc +unittest +{ + import mir.test: should; + + " hello world! ".strip(' ') .should == "hello world!"; + " hello world!!! ".strip(" !").should == "hello world"; +} diff --git a/source/mir/string_map.d b/source/mir/string_map.d new file mode 100644 index 00000000..c05cb11b --- /dev/null +++ b/source/mir/string_map.d @@ -0,0 +1,1297 @@ +/++ +$(H1 Ordered string-value associative array) +Macros: +AlgebraicREF = $(GREF_ALTTEXT mir-core, $(TT $1), $1, mir, algebraic)$(NBSP) ++/ + +module mir.string_map; + +import std.traits; +import mir.internal.meta: basicElementType; + +/++ +Checks if the type is instance of $(LREF StringMap). ++/ +enum isStringMap(T) = is(Unqual!T == StringMap!V, V); + +version(mir_test) +/// +unittest +{ + static assert(isStringMap!(StringMap!int)); + static assert(isStringMap!(const StringMap!int)); + static assert(!isStringMap!int); +} + +private alias U = uint; + +/++ +Ordered string-value associative array with extremely fast lookup. + +Params: + T = mutable value type, can be instance of $(AlgebraicREF Algebraic) for example. + U = an unsigned type that can hold an index of keys. `U.max` must be less then the maximum possible number of struct members. ++/ +struct StringMap(T) + // if (!is(typeof(T.opPostMove))) +{ + import mir.utility: _expect; + import core.lifetime: move; + import mir.conv: emplaceRef; + import mir.algebraic: Algebraic; + + /// + static struct KeyValue + { + /// + string key; + /// + T value; + } + + /// `hashOf` Implementation. Doesn't depend on order + static if (is(T == Algebraic!Union, Union) && is(Union == union)) + size_t toHash() scope @trusted const nothrow pure @nogc + { + if (implementation is null) + return 0; + size_t hash; + foreach (i, index; implementation.indices) + { + hash = hashOf(implementation._keys[index], hash); + static if (__traits(hasMember, T, "toHash")) + hash = hashOf(implementation._values[index].toHash, hash); + else + hash = hashOf(implementation._values[index], hash); + } + return hash; + } + else + size_t toHash() scope @trusted const nothrow // pure @nogc + { + if (implementation is null) + return 0; + size_t hash; + foreach (i, index; implementation.indices) + { + hash = hashOf(implementation._keys[index], hash); + static if (__traits(hasMember, T, "toHash")) + hash = hashOf(implementation._values[index].toHash, hash); + else + hash = hashOf(implementation._values[index], hash); + } + return hash; + } + + /// `==` implementation. Doesn't depend on order + // current implementation is workaround for linking bugs when used in self referencing algebraic types + bool opEquals(V)(scope const StringMap!V rhs) scope const @trusted pure @nogc nothrow + { + import std.traits: isAggregateType; + // NOTE: moving this to template restriction fails with recursive template instanation + if (implementation is null) + return rhs.length == 0; + if (rhs.implementation is null) + return length == 0; + if (implementation._length != rhs.implementation._length) + return false; + foreach (const i, const index; implementation.indices) + if (implementation._keys[index] != rhs.implementation._keys[rhs.implementation._indices[i]] || + implementation._values[index] != rhs.implementation._values[rhs.implementation._indices[i]]) + return false; + return true; + } + + /// ditto + bool opEquals(K, V)(scope const const(V)[const(K)] rhs) scope const + if (is(typeof(K.init == string.init) : bool) && + is(typeof(V.init == T.init) : bool)) + { + if (implementation is null) + return rhs.length == 0; + if (implementation._length != rhs.length) + return false; + foreach (const i; 0 .. implementation._length) + { + if (const valuePtr = implementation.keys[i] in rhs) + { + if (*valuePtr != implementation.values[i]) + return false; + } + else + return false; + } + return true; + } + + // // linking bug + // version(none) + // { + // /++ + // +/ + // bool opEquals()(typeof(null)) @safe pure nothrow @nogc const + // { + // return implementation is null; + // } + + // version(mir_test) static if (is(T == int)) + // /// + // @safe pure unittest + // { + // StringMap!int map; + // assert(map == null); + // map = StringMap!int(["key" : 1]); + // assert(map != null); + // map.remove("key"); + // assert(map != null); + // } + // } + + /++ + Reset the associative array + +/ + ref opAssign()(typeof(null)) return @safe pure nothrow @nogc + { + implementation = null; + return this; + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!int map = ["key" : 1]; + map = null; + } + + /++ + Initialize the associative array with default value. + +/ + this()(typeof(null) aa) @safe pure nothrow @nogc + { + implementation = null; + } + + version(mir_test) static if (is(T == int)) + /// Usefull for default funcion argument. + @safe pure unittest + { + StringMap!int map = null; // + } + + /++ + Constructs an associative array using keys and values from the builtin associative array + Complexity: `O(n log(n))` + +/ + this()(T[string] aa) @trusted pure nothrow + { + this(aa.keys, aa.values); + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!int map = ["key" : 1]; + assert(map.findPos("key") == 0); + } + + /// + string toString()() const scope + { + import mir.format: stringBuf; + stringBuf buffer; + toString(buffer); + return buffer.data.idup; + } + + ///ditto + void toString(W)(ref scope W w) const scope + { + bool next; + w.put('['); + import mir.format: printEscaped, EscapeFormat, print; + foreach (i, ref value; values) + { + if (next) + w.put(`, `); + next = true; + w.put('\"'); + printEscaped!(char, EscapeFormat.ion)(w, keys[i]); + w.put(`": `); + print(w, value); + } + w.put(']'); + } + + /++ + Constructs an associative array using keys and values. + Params: + keys = mutable array of keys + values = mutable array of values + Key and value arrays must have the same length. + + Complexity: `O(n log(n))` + +/ + this()(string[] keys, T[] values) @trusted pure nothrow + { + assert(keys.length == values.length); + implementation = new Impl(keys, values); + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + auto keys = ["ba", "a"]; + auto values = [1.0, 3.0]; + auto map = StringMap!double(keys, values); + assert(map.keys is keys); + assert(map.values is values); + } + + /++ + Returns: number of elements in the table. + +/ + size_t length()() @safe pure nothrow @nogc const @property + { + return implementation ? implementation.length : 0; + } + + /++ + Returns: number of elements in the table. + +/ + bool empty()() @safe pure nothrow @nogc const @property + { + return !implementation || implementation.length == 0; + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!double map; + assert(map.length == 0); + map["a"] = 3.0; + assert(map.length == 1); + map["c"] = 4.0; + assert(map.length == 2); + assert(map.remove("c")); + assert(map.length == 1); + assert(!map.remove("c")); + assert(map.length == 1); + assert(map.remove("a")); + assert(map.length == 0); + } + + /++ + Returns a dynamic array, the elements of which are the keys in the associative array. + Doesn't allocate a new copy. + + The keys returned are guaranteed to be in the ordered inserted as long as no + key removals followed by at least one key insertion has been performed. + + Complexity: `O(1)` + +/ + const(string)[] keys()() @safe pure nothrow @nogc const @property + { + return implementation ? implementation.keys : null; + } + /// + alias byKey = keys; + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!double map; + assert(map.keys == []); + map["c"] = 4.0; + assert(map.keys == ["c"]); + map["a"] = 3.0; + assert(map.keys == ["c", "a"]); + map.remove("c"); + assert(map.keys == ["a"]); + map.remove("a"); + assert(map.keys == []); + map["c"] = 4.0; + assert(map.keys == ["c"]); + } + + /++ + Returns a dynamic array, the elements of which are the values in the associative array. + Doesn't allocate a new copy. + + The values returned are guaranteed to be in the ordered inserted as long as no + key removals followed by at least one key insertion has been performed. + + Complexity: `O(1)` + +/ + inout(T)[] values()() @safe pure nothrow @nogc inout @property + { + return implementation ? implementation.values : null; + } + + /// ditto + alias byValue = values; + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!double map; + assert(map.byKeyValue == StringMap!double.KeyValue[].init); + map["c"] = 4.0; + map["a"] = 3.0; + assert(map.byKeyValue == [StringMap!double.KeyValue("c", 4.0), StringMap!double.KeyValue("a", 3.0)]); + } + + /** Return a range over all elements (key-values pairs) currently stored in the associative array. + + The elements returned are guaranteed to be in the ordered inserted as + long as no key removals nor no value mutations has been performed. + */ + auto byKeyValue(this This)() @trusted pure nothrow @nogc + { + import mir.ndslice.topology: map; + return this.opIndex.map!KeyValue; + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!double map; + assert(map.values == []); + map["c"] = 4.0; + assert(map.values == [4.0]); + map["a"] = 3.0; + assert(map.values == [4.0, 3.0]); + map.values[0]++; + assert(map.values == [5.0, 3.0]); + map.remove("c"); + assert(map.values == [3.0]); + map.remove("a"); + assert(map.values == []); + map["c"] = 4.0; + assert(map.values == [4.0]); + } + + /// + auto opIndex(this This)() @trusted pure nothrow @nogc + { + import mir.ndslice.topology: zip; + return keys.zip(values); + } + + /// + auto dup(this This)() @trusted + { + return StringMap(keys.dup, values.dup); + } + + /++ + (Property) Gets the current capacity of an associative array. + The capacity is the size that the underlaynig slices can grow to before the underlying arrays may be reallocated or extended. + + Complexity: `O(1)` + +/ + size_t capacity()() @safe pure nothrow const @property + { + import mir.utility: min; + + return !implementation ? 0 : min( + implementation.keys.capacity, + implementation.values.capacity, + implementation.indices.capacity, + ); + } + + version(mir_test) static if (is(T == int)) + /// + unittest + { + StringMap!double map; + assert(map.capacity == 0); + map["c"] = 4.0; + assert(map.capacity >= 1); + map["a"] = 3.0; + assert(map.capacity >= 2); + map.remove("c"); + map.assumeSafeAppend; + assert(map.capacity >= 2); + } + + /++ + Reserves capacity for an associative array. + The capacity is the size that the underlaying slices can grow to before the underlying arrays may be reallocated or extended. + +/ + size_t reserve()(size_t newcapacity) @trusted pure nothrow + { + import mir.utility: min; + + if (_expect(!implementation, false)) + { + implementation = new Impl; + } + + auto keysV = implementation.keys; + auto keysVCaacity = keysV.reserve(newcapacity); + implementation._keys = keysV.ptr; + + auto valuesV = implementation.values; + auto valuesCapacity = valuesV.reserve(newcapacity); + implementation._values = valuesV.ptr; + + auto indicesV = implementation.indices; + auto indicesCapacity = indicesV.reserve(newcapacity); + implementation._indices = indicesV.ptr; + + return min( + keysVCaacity, + valuesCapacity, + indicesCapacity, + ); + } + + version(mir_test) static if (is(T == int)) + /// + unittest + { + StringMap!double map; + auto capacity = map.reserve(10); + assert(capacity >= 10); + assert(map.capacity == capacity); + map["c"] = 4.0; + assert(map.capacity == capacity); + map["a"] = 3.0; + assert(map.capacity >= 2); + assert(map.remove("c")); + capacity = map.reserve(20); + assert(capacity >= 20); + assert(map.capacity == capacity); + } + + /++ + Assume that it is safe to append to this associative array. + Appends made to this associative array after calling this function may append in place, even if the array was a slice of a larger array to begin with. + Use this only when it is certain there are no elements in use beyond the associative array in the memory block. If there are, those elements will be overwritten by appending to this associative array. + + Warning: Calling this function, and then using references to data located after the given associative array results in undefined behavior. + + Returns: The input is returned. + +/ + ref inout(typeof(this)) assumeSafeAppend()() nothrow inout return + { + if (implementation) + { + implementation.keys.assumeSafeAppend; + implementation.values.assumeSafeAppend; + implementation.indices.assumeSafeAppend; + } + return this; + } + + version(mir_test) static if (is(T == int)) + /// + unittest + { + StringMap!double map; + map["c"] = 4.0; + map["a"] = 3.0; + assert(map.capacity >= 2); + map.remove("c"); + assert(map.capacity == 0); + map.assumeSafeAppend; + assert(map.capacity >= 2); + } + + /++ + Removes all remaining keys and values from an associative array. + + Complexity: `O(1)` + +/ + void clear()() @safe pure nothrow @nogc + { + if (implementation) + { + implementation._length = 0; + implementation._lengthTable = implementation._lengthTable[0 .. 0]; + } + + } + + version(mir_test) static if (is(T == int)) + /// + unittest + { + StringMap!double map; + map["c"] = 4.0; + map["a"] = 3.0; + map.clear; + assert(map.length == 0); + assert(map.capacity == 0); + map.assumeSafeAppend; + assert(map.capacity >= 2); + } + + /++ + `remove(key)` does nothing if the given key does not exist and returns false. If the given key does exist, it removes it from the AA and returns true. + + Complexity: `O(log(s))` (not exist) or `O(n)` (exist), where `s` is the count of the strings with the same length as they key. + +/ + bool remove()(scope const(char)[] key) @trusted pure nothrow @nogc + { + size_t index; + if (implementation && implementation.findIndex(key, index)) + { + implementation.removeAt(index); + return true; + } + return false; + } + + version(mir_test) static if (is(T == int)) + /// + unittest + { + StringMap!double map; + map["a"] = 3.0; + map["c"] = 4.0; + assert(map.remove("c")); + assert(!map.remove("c")); + assert(map.remove("a")); + assert(map.length == 0); + assert(map.capacity == 0); + assert(map.assumeSafeAppend.capacity >= 2); + } + + /++ + Finds position of the key in the associative array . + + Return: An index starting from `0` that corresponds to the key or `-1` if the associative array doesn't contain the key. + + Complexity: `O(log(s))`, where `s` is the number of the keys with the same length as the input key. + +/ + ptrdiff_t findPos()(scope const(char)[] key) @trusted pure nothrow @nogc const + { + if (!implementation) + return -1; + size_t index; + if (!implementation.findIndex(key, index)) + return -1; + return implementation._indices[index]; + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!double map; + map["c"] = 3.0; + map["La"] = 4.0; + map["a"] = 5.0; + + assert(map.findPos("C") == -1); + assert(map.findPos("c") == 0); + assert(map.findPos("La") == 1); + assert(map.findPos("a") == 2); + + map.remove("c"); + + assert(map.findPos("c") == -1); + assert(map.findPos("La") == 0); + assert(map.findPos("a") == 1); + } + + /++ + Complexity: `O(log(s))`, where `s` is the number of the keys with the same length as the input key. + +/ + inout(T)* opBinaryRight(string op : "in")(scope const(char)[] key) pure nothrow @nogc inout + { + if (!implementation) + return null; + size_t index; + if (!implementation.findIndex(key, index)) + return null; + assert (index < length); + index = implementation.indices[index]; + assert (index < length); + return &implementation.values[index]; + } + + version(mir_test) static if (is(T == int)) + /// + @safe nothrow pure unittest + { + StringMap!double map; + assert(("c" in map) is null); + map["c"] = 3.0; + assert(*("c" in map) == 3.0); + } + + /++ + Complexity: `O(log(s))`, where `s` is the number of the keys with the same length as the input key. + +/ + ref inout(T) opIndex()(scope const(char)[] key) @trusted pure inout //@nogc + { + size_t index; + if (implementation && implementation.findIndex(key, index)) + { + assert (index < length); + index = implementation._indices[index]; + assert (index < length); + return implementation._values[index]; + } + import mir.exception: MirException; + throw new MirException("No member: ", key); + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!double map; + map["c"] = 3.0; + map["La"] = 4.0; + map["a"] = 5.0; + + map["La"] += 10; + assert(map["La"] == 14.0); + } + + /++ + Complexity: `O(log(s))` (exist) or `O(n)` (not exist), where `s` is the count of the strings with the same length as they key. + +/ + ref T opIndexAssign(R)(auto ref R value, string key) @trusted pure nothrow + { + import core.lifetime: forward, move; + T v; + v = forward!value; + return opIndexAssign(move(v), key); + } + + /// ditto + ref T opIndexAssign()(T value, string key) @trusted pure nothrow + { + size_t index; + if (_expect(!implementation, false)) + { + implementation = new Impl; + } + else + { + if (key.length + 1 < implementation.lengthTable.length) + { + if (implementation.findIndex(key, index)) + { + assert (index < length); + index = implementation._indices[index]; + assert (index < length); + move(cast()value, cast()(implementation._values[index])); + return implementation._values[index]; + } + assert (index <= length); + } + else + { + index = length; + } + } + assert (index <= length); + implementation.insertAt(key, move(cast()value), index); + index = implementation._indices[index]; + return implementation._values[index]; + } + + /++ + Looks up key; if it exists returns corresponding value else evaluates and returns defaultValue. + + Complexity: `O(log(s))`, where `s` is the number of the keys with the same length as the input key. + +/ + inout(T) get()(scope const(char)[] key, lazy inout(T) defaultValue) inout + { + size_t index; + if (implementation && implementation.findIndex(key, index)) + { + assert (index < length); + index = implementation.indices[index]; + assert (index < length); + return implementation.values[index]; + } + return defaultValue; + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!int map; + map["c"] = 3; + assert(map.get("c", 1) == 3); + assert(map.get("C", 1) == 1); + } + + /++ + Looks up key; if it exists returns corresponding value else evaluates value, adds it to the associative array and returns it. + + Complexity: `O(log(s))` (exist) or `O(n)` (not exist), where `s` is the count of the strings with the same length as they key. + +/ + ref T require()(string key, lazy T value = T.init) + { + size_t index; + if (_expect(!implementation, false)) + { + implementation = new Impl; + } + else + { + if (key.length + 1 < implementation.lengthTable.length) + { + if (implementation.findIndex(key, index)) + { + assert (index < length); + index = implementation.indices[index]; + assert (index < length); + return implementation.values[index]; + } + assert (index <= length); + } + else + { + index = length; + } + } + assert (index <= length); + implementation.insertAt(key, value, index); + index = implementation.indices[index]; + return implementation.values[index]; + } + + version(mir_test) static if (is(T == int)) + /// + @safe pure unittest + { + StringMap!int map = ["aa": 1]; + int add3(ref int x) { x += 3; return x; } + assert(add3(map.require("aa", 10)) == 4); + assert(add3(map.require("bb", 10)) == 13); + assert(map.require("a", 100)); + assert(map.require("aa") == 4); + assert(map.require("bb") == 13); + assert(map.keys == ["aa", "bb", "a"]); + } + + /++ + Converts to a builtin associative array. + + Complexity: `O(n)`. + +/ + template toAA() + { + static if (__traits(compiles, (ref const T a) { T b; b = a;})) + { + /// + T[string] toAA()() const + { + T[string] ret; + foreach (i; 0 .. length) + { + ret[implementation.keys[i]] = implementation.values[i]; + } + return ret; + } + } + else + { + /// + T[string] toAA()() + { + T[string] ret; + foreach (i; 0 .. length) + { + ret[implementation.keys[i]] = implementation.values[i]; + } + return ret; + } + + /// + const(T)[string] toAA()() const + { + const(T)[string] ret; + foreach (i; 0 .. length) + { + ret[implementation.keys[i]] = implementation.values[i]; + } + return ret; + } + } + } + + /// + version(mir_test) static if (is(T == int)) + @safe pure nothrow unittest + { + StringMap!int map = ["k": 1]; + int[string] aa = map.toAA; + assert(aa["k"] == 1); + } + + private static struct Impl + { + import core.lifetime: move; + import mir.conv: emplaceRef; + + size_t _length; + string* _keys; + T* _values; + U* _indices; + U[] _lengthTable; + + /++ + +/ + this()(string[] keys, T[] values) @trusted pure nothrow + { + assert(keys.length == values.length); + if (keys.length == 0) + return; + _length = keys.length; + _keys = keys.ptr; + _values = values.ptr; + _indices = new U[keys.length].ptr; + size_t maxKeyLength; + foreach(ref key; keys) + if (key.length > maxKeyLength) + maxKeyLength = key.length; + _lengthTable = new U[maxKeyLength + 2]; + sortIndices(); + } + + private void sortIndices() pure nothrow + { + import mir.ndslice.sorting: sort; + import mir.ndslice.topology: indexed; + import mir.string_table: smallerStringFirst; + foreach (i, ref index; indices) + index = cast(U)i; + + indices.sort!((a, b) => smallerStringFirst(keys[a], keys[b])); + auto sortedKeys = _keys.indexed(indices); + size_t maxKeyLength = sortedKeys[$ - 1].length; + + size_t ski; + foreach (length; 0 .. maxKeyLength + 1) + { + while(ski < sortedKeys.length && sortedKeys[ski].length == length) + ski++; + _lengthTable[length + 1] = cast(U)ski; + } + } + + void insertAt()(string key, T value, size_t i) @trusted + { + pragma(inline, false); + + assert(i <= length); + { + auto a = keys; + a ~= key; + _keys = a.ptr; + } + { + auto a = values; + a ~= move(cast()value); + _values = a.ptr; + } + { + auto a = indices; + a ~= 0; + _indices = a.ptr; + + if (__ctfe) + { + foreach_reverse (idx; i .. length) + { + _indices[idx + 1] = _indices[idx]; + } + } + else + { + import core.stdc.string: memmove; + memmove(_indices + i + 1, _indices + i, (length - i) * U.sizeof); + } + assert(length <= U.max); + _indices[i] = cast(U)length; + _length++; + } + { + if (key.length + 2 <= lengthTable.length) + { + ++lengthTable[key.length + 1 .. $]; + } + else + { + auto oldLen = _lengthTable.length; + _lengthTable.length = key.length + 2; + auto oldVal = oldLen ? _lengthTable[oldLen - 1] : 0; + _lengthTable[oldLen .. key.length + 1] = oldVal; + _lengthTable[key.length + 1] = oldVal + 1; + } + } + } + + void removeAt()(size_t i) + { + assert(i < length); + auto j = _indices[i]; + assert(j < length); + { + --_lengthTable[_keys[j].length + 1 .. $]; + } + { + if (__ctfe) + { + foreach (idx; i .. length) + { + _indices[idx] = _indices[idx + 1]; + _indices[idx] = _indices[idx + 1]; + } + } + else + { + import core.stdc.string: memmove; + memmove(_indices + i, _indices + i + 1, (length - 1 - i) * U.sizeof); + } + foreach (ref elem; indices[0 .. $ - 1]) + if (elem > j) + elem--; + } + { + if (__ctfe) + { + foreach_reverse (idx; j .. length - 1) + { + _keys[idx] = _keys[idx + 1]; + _values[idx] = move(_values[idx + 1]); + } + } + else + { + import core.stdc.string: memmove; + destroy!false(_values[j]); + memmove(_keys + j, _keys + j + 1, (length - 1 - j) * string.sizeof); + memmove(_values + j, _values + j + 1, (length - 1 - j) * T.sizeof); + emplaceRef(_values[length - 1]); + } + } + _length--; + _lengthTable = _lengthTable[0 .. length ? _keys[_indices[length - 1]].length + 2 : 0]; + } + + size_t length()() @safe pure nothrow @nogc const @property + { + return _length; + } + + inout(string)[] keys()() @trusted inout @property pure @nogc nothrow + { + return _keys[0 .. _length]; + } + + inout(T)[] values()() @trusted inout @property pure @nogc nothrow + { + return _values[0 .. _length]; + } + + inout(U)[] indices()() @trusted inout @property pure @nogc nothrow + { + return _indices[0 .. _length]; + } + + inout(U)[] lengthTable()() @trusted inout @property pure @nogc nothrow + { + return _lengthTable; + } + + auto sortedKeys()() @trusted const @property + { + import mir.ndslice.topology: indexed; + return _keys.indexed(indices); + } + + bool findIndex()(scope const(char)[] key, ref size_t index) @trusted pure nothrow @nogc const + { + import mir.utility: _expect; + if (_expect(key.length + 1 < _lengthTable.length, true)) + { + + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + // 0 1 2 3 4 5 6 8 9 10 12 16 + + auto low = _lengthTable[key.length] + 0u; + auto high = _lengthTable[key.length + 1] + 0u; + while (low < high) + { + const mid = (low + high) / 2; + + import core.stdc.string: memcmp; + int r = void; + + if (__ctfe) + r = __cmp(key, _keys[_indices[mid]]); + else + r = memcmp(key.ptr, _keys[_indices[mid]].ptr, key.length); + + if (r == 0) + { + index = mid; + return true; + } + if (r > 0) + low = mid + 1; + else + high = mid; + } + index = low; + } + return false; + } + } + + /// Sorts table according to the keys + ref sort(alias less = "a < b")() return + { + import mir.functional: naryFun; + import mir.ndslice.sorting: sort; + import mir.ndslice.topology: zip; + if (length) { + zip(implementation.keys, implementation.values).sort!((l, r) => naryFun!less(l.a, r.a)); + implementation.sortIndices; + } + return this; + } + + import std.traits: isAssociativeArray, isAggregateType; + // static if (!isAssociativeArray!(.basicElementType!T) && (!isAggregateType!(.basicElementType!T) || __traits(hasMember, .basicElementType!T, "opCmp"))) + /// `opCmp` Implementation. Doesn't depend on order + int opCmp()(scope const typeof(this) rhs) scope const @trusted // pure nothrow @nogc + { + if (sizediff_t d = length - rhs.length) + return d < 0 ? -1 : 1; + if (length == 0) + return 0; + + foreach (i, index; implementation.indices) + if (auto d = __cmp(implementation._keys[index], rhs.implementation._keys[rhs.implementation._indices[i]])) + return d; + foreach (i, index; implementation.indices) + static if (__traits(compiles, __cmp(implementation._values[index], rhs.implementation._values[rhs.implementation._indices[i]]))) + { + if (auto d = __cmp(implementation._values[index], rhs.implementation._values[rhs.implementation._indices[i]])) + return d; + } + else + static if (__traits(hasMember, T, "opCmp")) + { + if (auto d = implementation._values[index].opCmp(rhs.implementation._values[rhs.implementation._indices[i]])) + return d; + } + else + { + return + implementation._values[index] < rhs.implementation._values[rhs.implementation._indices[i]] ? -1 : + implementation._values[index] > rhs.implementation._values[rhs.implementation._indices[i]] ? +1 : 0; + } + return false; + } + + private Impl* implementation; + + /// + static if (is(T == const) || is(T == immutable)) + { + alias serdeKeysProxy = Unqual!T; + } + else + { + alias serdeKeysProxy = T; + } +} + +version(mir_test) +/// +@safe unittest +{ + class C + { + this(int x) { this.x = x; } + int x; + bool opEquals(scope const C rhs) const scope @safe pure nothrow @nogc + { + return x == rhs.x; + } + + override size_t toHash() @safe const scope pure nothrow @nogc + { + return x; + } + } + StringMap!(const C) table; + const v0 = new C(42); + const v1 = new C(43); + table["0"] = v0; + table["1"] = v1; + assert(table.keys == ["0", "1"]); + assert(table.values == [v0, v1]); // TODO: qualify unittest as `pure` when this is inferred `pure` + static assert(is(typeof(table.values) == const(C)[])); +} + +version(mir_test) +/// +@safe pure unittest +{ + StringMap!int table; + table["L"] = 3; + table["A"] = 2; + table["val"] = 1; + assert(table.keys == ["L", "A", "val"]); + assert(table.values == [3, 2, 1]); + assert(table["A"] == 2); + table.values[2] += 10; + assert(table["A"] == 2); + assert(table["L"] == 3); + assert(table["val"] == 11); + assert(table.keys == ["L", "A", "val"]); + assert(table.values == [3, 2, 11]); + table.remove("A"); + assert(table.keys == ["L", "val"]); + assert(table.values == [3, 11]); + assert(table["L"] == 3); + assert(table["val"] == 11); + + assert(table == table); + + // sorting + table["A"] = 2; + table.sort; + assert(table.keys == ["A", "L", "val"]); + assert(table.values == [2, 3, 11]); + assert(table["A"] == 2); + assert(table["L"] == 3); + assert(table["val"] == 11); +} + +version(mir_test) +/// +@safe pure unittest +{ + static void testEquals(X, Y)() + { + X x; + Y y; + assert(x == y); + + x["L"] = 3; + assert(x != y); + x["A"] = 2; + assert(x != y); + x["val"] = 1; + assert(x != y); + + y["val"] = 1; + assert(x != y); + y["L"] = 3; + assert(x != y); + y["A"] = 2; + assert(x == y); + + x = X.init; + assert(x != y); + + y = Y.init; + assert(x == y); + } + + testEquals!(StringMap!int, StringMap!uint)(); + testEquals!(StringMap!int, uint[string])(); + testEquals!(uint[string], StringMap!int)(); +} + +version(mir_test) +@safe pure unittest +{ + import mir.algebraic_alias.json: JsonAlgebraic; + import mir.string_map: StringMap; + + StringMap!JsonAlgebraic token; + token[`access_token`] = "secret-data"; + token[`expires_in`] = 3599; + token[`token_type`] = "Bearer"; + + assert(token[`access_token`] == "secret-data"); + assert(token[`expires_in`] == 3599); + assert(token[`token_type`] == "Bearer"); // mir/string_map.d(511): No member: token_type + + const tkType = `token_type` in token; + + assert((*tkType) == "Bearer"); // *tkType contains value 3599 +} + +/// +template intersectionMap(alias merger) +{ + /// + StringMap!V intersectionMap(V)(StringMap!V a, StringMap!V b) + { + import mir.functional : naryFun; + typeof(return) res; + foreach (key, ref value; a) + if (auto bValPtr = key in b) + res[key] = naryFun!merger(value, *bValPtr); + return res; + } +} + +/// +version(mir_test) +@safe pure +unittest { + import mir.test: should; + import mir.string_map : StringMap; + auto m0 = StringMap!int(["foo", "bar"], [1, 2]); + auto m1 = StringMap!int(["foo"], [2]); + auto m2 = StringMap!int(["foo"], [3]); + intersectionMap!"a + b"(m0, m1).should == m2; +} + +/// +template unionMap(alias merger) +{ + /// + StringMap!V unionMap(V)(StringMap!V a, StringMap!V b) + { + import mir.functional : naryFun; + typeof(return) res; + foreach (key, ref value; a) + if (auto bValPtr = key in b) + res[key] = naryFun!merger(value, *bValPtr); + else + res[key] = value; + foreach (key, ref value; b) + if (key !in res) + res[key] = value; + return res; + } +} + +/// +version(mir_test) +@safe pure +unittest { + import mir.test: should; + import mir.string_map : StringMap; + auto m0 = StringMap!int(["foo", "bar"], [1, 2]); + auto m1 = StringMap!int(["foo"], [2]); + auto m2 = StringMap!int(["foo", "bar"], [3, 2]); + unionMap!"a + b"(m0, m1).should == m2; +} diff --git a/source/mir/test.d b/source/mir/test.d new file mode 100644 index 00000000..282936f6 --- /dev/null +++ b/source/mir/test.d @@ -0,0 +1,166 @@ +/++ +Testing utilities + +Authors: Ilya Yaroshenko ++/ +module mir.test; + +import mir.exception: MirError; + +private noreturn assumeAllAttrAndCall(scope const void delegate() t) + @nogc pure nothrow @trusted { + (cast(const void delegate() @safe pure nothrow @nogc) t)(); + assert(0); +} + +/// +struct ShouldApprox(T, F = T) + if ((__traits(isFloating, T) || __traits(hasMember, T, "approxEqual")) && __traits(isFloating, F)) +{ + /// + T value; + /// + F maxRelDiff = 0x1p-20f; + /// + F maxAbsDiff = 0x1p-20f; + + /// + void opEquals(T expected, string file = __FILE__, int line = __LINE__) @safe pure nothrow @nogc + { + import mir.format: stringBuf, getData; + import mir.math.common: approxEqual; + if (value.approxEqual(expected, maxRelDiff, maxAbsDiff)) + return; + auto buf = stringBuf; + assumeAllAttrAndCall({ + throw new MirError(buf + << "expected approximately " << expected + << ", got " << value + << ", maxRelDiff = " << maxRelDiff + << ", maxAbsDiff = " << maxAbsDiff + << getData, file, line); + }); + assert(0); + } +} + +/// ditto +ShouldApprox!T shouldApprox(T)(const T value, const T maxRelDiff = T(0x1p-20f), const T maxAbsDiff = T(0x1p-20f)) + if (__traits(isFloating, T)) +{ + return typeof(return)(value, maxRelDiff, maxAbsDiff); +} + +/// +version(mir_test) +unittest +{ + 1.0.shouldApprox == 1 + 9e-7; + shouldApprox(1 + 9e-7, 1e-6, 1e-6) == 1; +} + +/// ditto +ShouldApprox!(T, F) shouldApprox(T, F)(const T value, const F maxRelDiff = double(0x1p-20f), const F maxAbsDiff = double(0x1p-20f)) + if (__traits(hasMember, T, "approxEqual") && __traits(isFloating, F)) +{ + return typeof(return)(value, maxRelDiff, maxAbsDiff); +} + +/// +version(mir_test) +unittest +{ + static struct C { + double re, im; + auto approxEqual(C rhs, double maxRelDiff, double maxAbsDiff) + { + import mir.math.common: approxEqual; + return approxEqual(re, rhs.re, maxRelDiff, maxAbsDiff) && approxEqual(im, rhs.im, maxRelDiff, maxAbsDiff); + } + } + C(1.0, 1.0).shouldApprox == C(1 + 9e-7, 1 - 9e-7); +} + +/// +struct Should(T) +{ + /// + T value; + + static if(!is(immutable T == immutable ubyte[])) + /// + void opEquals(R)(const R expected, string file = __FILE__, int line = __LINE__) @trusted + { + import mir.format: stringBuf, getData; + static if (__traits(isFloating, R)) + { + if (expected != expected && value != value) + return; + } + if (value == expected) + return; + auto buf = stringBuf; + buf << "mir.test.should:\n"; + buf << "expected " << expected << "\n" + << " got " << value; + assumeAllAttrAndCall({ throw new MirError(buf << getData, file, line); }); + } + else + /// ditto + void opEquals(scope const ubyte[] expected, string file = __FILE__, int line = __LINE__) @trusted + pure nothrow @nogc + { + import mir.format: stringBuf, getData; + if (value == expected) + return; + auto buf = stringBuf; + import mir.format: printHexArray; + import mir.ndslice.topology: map; + buf << "mir.test.should:\n"; + buf << "expected "; + buf.printHexArray(expected); + buf << "\n"; + buf << " got "; + buf.printHexArray( value); + assumeAllAttrAndCall({ throw new MirError(buf << getData, file, line); }); + } +} + +/// ditto +Should!T should(T)(T value) +{ + return typeof(return)(value); +} + +/// +version(mir_test) +unittest +{ + 1.0.should == 1; + should(1) == 1; + + ubyte[] val = [0, 2, 3]; + val.should == [0, 2, 3]; +} + +/// +void should(alias fun = "a == b", T, R)(const T value, const R expected, string file = __FILE__, int line = __LINE__) +{ + import mir.functional; + import mir.format: stringBuf, getData; + if (naryFun!fun(value, expected)) + return; + auto buf = stringBuf; + buf << fun.stringof + << " returns false" + << " for a = " << value + << ", b = " << expected; + throw new MirError(buf << getData, file, line); +} + +/// +version(mir_test) +unittest +{ + 1.0.should!"a < b"(1.3); +} diff --git a/source/mir/timestamp.d b/source/mir/timestamp.d new file mode 100644 index 00000000..b4dc0df2 --- /dev/null +++ b/source/mir/timestamp.d @@ -0,0 +1,1593 @@ +/++ +Timestamp ++/ +module mir.timestamp; + +private alias isDigit = (dchar c) => uint(c - '0') < 10; +import mir.serde: serdeIgnore, serdeRegister; + +version(D_Exceptions) +/// +class DateTimeException : Exception +{ + /// + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + /// ditto + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} + +version(D_Exceptions) +{ + private static immutable InvalidMonth = new DateTimeException("Timestamp: Invalid Month"); + private static immutable InvalidDay = new DateTimeException("Timestamp: Invalid Day"); + private static immutable InvalidISOString = new DateTimeException("Timestamp: Invalid ISO String"); + private static immutable InvalidISOExtendedString = new DateTimeException("Timestamp: Invalid ISO Extended String"); + private static immutable InvalidYamlString = new DateTimeException("Timestamp: Invalid YAML String"); + private static immutable InvalidString = new DateTimeException("Timestamp: Invalid String"); + private static immutable ExpectedDuration = new DateTimeException("Timestamp: Expected Duration"); +} + +/++ +Timestamp + +Note: The component values in the binary encoding are always in UTC or local time with unknown offset, +while components in the text encoding are in a some timezone with known offset. +This means that transcoding requires a conversion between UTC and a timezone. + +`Timestamp` precision is up to picosecond (second/10^12). ++/ +@serdeRegister +struct Timestamp +{ + import std.traits: isSomeChar; + + /// + enum Precision : ubyte + { + /// + year, + /// + month, + /// + day, + /// + minute, + /// + second, + /// + fraction, + } + +@serdeIgnore: + + /// + this(scope const(char)[] str) @safe pure @nogc + { + this = fromString(str); + } + + /// + version (mir_test) + @safe pure @nogc unittest + { + assert(Timestamp("2010-07-04") == Timestamp(2010, 7, 4)); + assert(Timestamp("20100704") == Timestamp(2010, 7, 4)); + assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730")); + static assert(Timestamp(2021, 01, 29, 4, 42, 44).withOffset(- (7 * 60 + 30)) == Timestamp.fromISOExtString("2021-01-28T21:12:44-07:30")); + + assert(Timestamp("T0740") == Timestamp.onlyTime(7, 40)); + assert(Timestamp("T074030Z") == Timestamp.onlyTime(7, 40, 30).withOffset(0)); + assert(Timestamp("T074030.056") == Timestamp.onlyTime(7, 40, 30, -3, 56)); + + assert(Timestamp("07:40") == Timestamp.onlyTime(7, 40)); + assert(Timestamp("07:40:30") == Timestamp.onlyTime(7, 40, 30)); + assert(Timestamp("T07:40:30.056Z") == Timestamp.onlyTime(7, 40, 30, -3, 56).withOffset(0)); + } + + private short _offset = short.min; + + /++ + If the time in UTC is known, but the offset to local time is unknown, this can be represented with an offset of “-00:00”. + This differs semantically from an offset of “Z” or “+00:00”, which imply that UTC is the preferred reference point for the specified time. + RFC2822 describes a similar convention for email. + +/ + /++ + Timezone offset in minutes + +/ + short offset()() const @safe pure nothrow @nogc @property + { + return isLocalTime ? 0 : _offset; + } + + /// + void offset()(ushort offset) @safe pure nothrow @nogc @property + { + _offset = offset; + } + + /++ + Returns: true if it is a local time + +/ + bool isLocalTime()() const @safe pure nothrow @nogc @property + { + return _offset == _offset.min; + } + + /++ + +/ + void setLocalTimezone()() @safe pure nothrow @nogc @property + { + _offset = _offset.min; + } + + + /++ + Year + +/ + short year; + /++ + +/ + Precision precision; + + /++ + Month + + If the value equals to thero then this and all the following members are undefined. + +/ + ubyte month; + /++ + Day + + If the value equals to thero then this and all the following members are undefined. + +/ + ubyte day; + /++ + Hour + +/ + ubyte hour; + + version(D_Ddoc) + { + + /++ + Minute + + Note: the field is implemented as property. + +/ + ubyte minute; + /++ + Second + + Note: the field is implemented as property. + +/ + ubyte second; + /++ + Fraction + + The `fractionExponent` and `fractionCoefficient` denote the fractional seconds of the timestamp as a decimal value + The fractional seconds’ value is `coefficient * 10 ^ exponent`. + It must be greater than or equal to zero and less than 1. + A missing coefficient defaults to zero. + Fractions whose coefficient is zero and exponent is greater than -1 are ignored. + + 'fractionCoefficient' allowed values are [0 ... 10^12-1]. + 'fractionExponent' allowed values are [-12 ... 0]. + + Note: the fields are implemented as property. + +/ + byte fractionExponent; + /// ditto + ulong fractionCoefficient; + } + else + { + import mir.bitmanip: bitfields; + version (LittleEndian) + { + + mixin(bitfields!( + ubyte, "minute", 8, + ubyte, "second", 8, + byte, "fractionExponent", 8, + ulong, "fractionCoefficient", 40, + )); + } + else + { + mixin(bitfields!( + ulong, "fractionCoefficient", 40, + byte, "fractionExponent", 8, + ubyte, "second", 8, + ubyte, "minute", 8, + )); + } + } + + /// + @safe pure nothrow @nogc + this(short year) + { + this.year = year; + this.precision = Precision.year; + } + + /// + @safe pure nothrow @nogc + this(short year, ubyte month) + { + this.year = year; + this.month = month; + this.precision = Precision.month; + } + + /// + @safe pure nothrow @nogc + this(short year, ubyte month, ubyte day) + { + this.year = year; + this.month = month; + this.day = day; + this.precision = Precision.day; + } + + /// + @safe pure nothrow @nogc + this(short year, ubyte month, ubyte day, ubyte hour, ubyte minute) + { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.minute = minute; + this.precision = Precision.minute; + } + + /// + @safe pure nothrow @nogc + this(short year, ubyte month, ubyte day, ubyte hour, ubyte minute, ubyte second) + { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.day = day; + this.minute = minute; + this.second = second; + this.precision = Precision.second; + } + + /// + @safe pure nothrow @nogc + this(short year, ubyte month, ubyte day, ubyte hour, ubyte minute, ubyte second, byte fractionExponent, ulong fractionCoefficient) + { + this.year = year; + this.month = month; + this.day = day; + this.hour = hour; + this.day = day; + this.minute = minute; + this.second = second; + assert(fractionExponent < 0); + this.fractionExponent = fractionExponent; + this.fractionCoefficient = fractionCoefficient; + this.precision = Precision.fraction; + } + + /// + @safe pure nothrow @nogc + static Timestamp onlyTime(ubyte hour, ubyte minute) + { + return Timestamp(0, 0, 0, hour, minute); + } + + /// + @safe pure nothrow @nogc + static Timestamp onlyTime(ubyte hour, ubyte minute, ubyte second) + { + return Timestamp(0, 0, 0, hour, minute, second); + } + + /// + @safe pure nothrow @nogc + static Timestamp onlyTime(ubyte hour, ubyte minute, ubyte second, byte fractionExponent, ulong fractionCoefficient) + { + return Timestamp(0, 0, 0, hour, minute, second, fractionExponent, fractionCoefficient); + } + + /// + this(Date)(const Date datetime) + if (Date.stringof == "Date" || Date.stringof == "date") + { + static if (__traits(hasMember, Date, "yearMonthDay")) + with(datetime.yearMonthDay) this(year, cast(ubyte)month, day); + else + with(datetime) this(year, month, day); + } + + /// + version (mir_test) + @safe unittest { + import mir.date : Date; + auto dt = Date(1982, 4, 1); + Timestamp ts = dt; + assert(ts.opCmp(ts) == 0); + assert(dt.toISOExtString == ts.toString); + assert(dt == cast(Date) ts); + } + + /// + version (mir_test) + @safe unittest { + import std.datetime.date : Date; + auto dt = Date(1982, 4, 1); + Timestamp ts = dt; + assert(dt.toISOExtString == ts.toString); + assert(dt == cast(Date) ts); + } + + /// + this(TimeOfDay)(const TimeOfDay timeOfDay) + if (TimeOfDay.stringof == "TimeOfDay") + { + with(timeOfDay) this = onlyTime(hour, minute, second); + } + + /// + version (mir_test) + @safe unittest { + import mir.test: should; + import std.datetime.date : TimeOfDay; + auto dt = TimeOfDay(7, 14, 30); + Timestamp ts = dt; + (dt.toISOExtString ~ "-00:00").should == ts.toString; + assert(dt == cast(TimeOfDay) ts); + } + + /// + this(DateTime)(const DateTime datetime) + if (DateTime.stringof == "DateTime") + { + with(datetime) this(year, cast(ubyte)month, day, hour, minute, second); + } + + /// + version (mir_test) + @safe unittest { + import std.datetime.date : DateTime; + auto dt = DateTime(1982, 4, 1, 20, 59, 22); + Timestamp ts = dt; + assert(dt.toISOExtString ~ "-00:00" == ts.toString); + assert(dt == cast(DateTime) ts); + } + + /// + this(SysTime)(const SysTime systime) pure + if (SysTime.stringof == "SysTime") + { + import std.datetime.timezone : LocalTime; + auto offset = assumePureSafe(()=>systime.utcOffset)(); + auto isLocal = systime.timezone is LocalTime(); + auto thisTimes = isLocal ? systime + offset : systime.toUTC; + this = fromUnixTime(thisTimes.toUnixTime); + this.fractionExponent = -7; + this.fractionCoefficient = assumePureSafe(()=>thisTimes.fracSecs)().total!"hnsecs"; + this.precision = Precision.fraction; + if (!isLocal) + this.offset = cast(short) offset.total!"minutes"; + } + + /// + version (mir_test) + @safe unittest { + import core.time : hnsecs, minutes; + import std.datetime.date : DateTime; + import std.datetime.timezone : SimpleTimeZone; + import std.datetime.systime : SysTime; + + auto dt = DateTime(1982, 4, 1, 20, 59, 22); + auto tz = new immutable SimpleTimeZone(-330.minutes); + auto st = SysTime(dt, 1234567.hnsecs, tz); + Timestamp ts = st; + + assert(st.toISOExtString == ts.toString); + assert(st == cast(SysTime) ts); + } + + /++ + Creates a fake timestamp from a Duration using `total!"hnsecs"` method. + For positive and zero timestamps the format is + `wwww-dd-88Thh:mm:ss.nnnnnnn` + and for negative timestamps + `wwww-dd-99Thh:mm:ss.nnnnnnn`. + +/ + this(Duration)(const Duration duration) + if (Duration.stringof == "Duration") + { + auto hnsecs = duration.total!"hnsecs"; + ulong abs = hnsecs < 0 ? -hnsecs : hnsecs; + precision = Precision.fraction; + day = hnsecs >= 0 ? 88 : 99; + fractionExponent = -7; + fractionCoefficient = abs % 10_000_000U; + abs /= 10_000_000U; + second = abs % 60; + abs /= 60; + minute = abs % 60; + abs /= 60; + hour = abs % 24; + abs /= 24; + month = abs % 7; + abs /= 7; + year = cast(typeof(year)) abs; + } + + /// + version (mir_test) + @safe unittest { + import core.time : Duration, weeks, days, hours, minutes, seconds, hnsecs; + + auto duration = 5.weeks + 2.days + 7.hours + 40.minutes + 4.seconds + 9876543.hnsecs; + Timestamp ts = duration; + + assert(ts.toISOExtString == `0005-02-88T07:40:04.9876543-00:00`); + assert(duration == cast(Duration) ts); + + duration = -duration; + ts = Timestamp(duration); + assert(ts.toISOExtString == `0005-02-99T07:40:04.9876543-00:00`); + assert(duration == cast(Duration) ts); + + assert(Timestamp(Duration.zero).toISOExtString == `0000-00-88T00:00:00.0000000-00:00`); + } + + /++ + Decomposes Timestamp to an algebraic type. + Supported types up to T.stringof equivalence: + + $(UL + $(LI `Year`) + $(LI `YearMonth`) + $(LI `YearMonthDay`) + $(LI `Date`) + $(LI `date`) + $(LI `TimeOfDay`) + $(LI `DateTime`) + $(LI `SysTime`) + $(LI `Timestamp` as fallback type) + ) + + + Throws: an exception if timestamp cannot be converted to an algebraic type and there is no `Timestamp` type in the Algebraic set. + +/ + T opCast(T)() const + if (__traits(hasMember, T, "AllowedTypes")) + { + foreach (AT; T.AllowedTypes) + static if (AT.stringof == "Year") + if (precision == precision.year) + return T(opCast!AT); + + foreach (AT; T.AllowedTypes) + static if (AT.stringof == "YearMonth") + if (precision == precision.month) + return T(opCast!AT); + + foreach (AT; T.AllowedTypes) + static if (AT.stringof == "Duration") + if (isDuration) + return T(opCast!AT); + + foreach (AT; T.AllowedTypes) + static if (AT.stringof == "YearMonthDay" || AT.stringof == "Date" || AT.stringof == "date") + if (precision == precision.day) + return T(opCast!AT); + + foreach (AT; T.AllowedTypes) + static if (AT.stringof == "TimeOfDay") + if (isOnlyTime) + return T(opCast!AT); + + if (!isOnlyTime && precision >= precision.day) + { + foreach (AT; T.AllowedTypes) + static if (AT.stringof == "DateTime") + if (offset == 0 && precision <= precision.second) + return T(opCast!AT); + + foreach (AT; T.AllowedTypes) + static if (AT.stringof == "SysTime") + return T(opCast!AT); + } + + import std.meta: staticIndexOf; + static if (staticIndexOf!(Timestamp, T.AllowedTypes) < 0) + { + static immutable exc = new Exception("Cannot cast Timestamp to " ~ T.stringof); + { import mir.exception : toMutable; throw exc.toMutable; } + } + else + { + return T(this); + } + } + + /// + version (mir_test) + @safe unittest + { + import core.time : hnsecs, minutes, Duration; + import mir.algebraic; + import mir.date: Date; // Can be other Date type as well + import std.datetime.date : TimeOfDay, DateTime; + import std.datetime.systime : SysTime; + import std.datetime.timezone: UTC, SimpleTimeZone; + + alias A = Variant!(Date, TimeOfDay, DateTime, Duration, SysTime, Timestamp, string); // non-date-time types is OK + assert(cast(A) Timestamp(1023) == Timestamp(1023)); // Year isn't represented in the algebraic, use fallback type + assert(cast(A) Timestamp.onlyTime(7, 40, 30) == TimeOfDay(7, 40, 30)); + assert(cast(A) Timestamp(1982, 4, 1, 20, 59, 22) == DateTime(1982, 4, 1, 20, 59, 22)); + + auto dt = DateTime(1982, 4, 1, 20, 59, 22); + auto tz = new immutable SimpleTimeZone(-330.minutes); + auto st = SysTime(dt, 1234567.hnsecs, tz); + assert(cast(A) Timestamp(st) == st); + } + + /++ + Casts timestamp to a date-time type. + + Supported types up to T.stringof equivalence: + + $(UL + $(LI `Year`) + $(LI `YearMonth`) + $(LI `YearMonthDay`) + $(LI `Date`) + $(LI `date`) + $(LI `TimeOfDay`) + $(LI `DateTime`) + $(LI `SysTime`) + ) + +/ + T opCast(T)() const + if ( + T.stringof == "Year" + || T.stringof == "YearMonth" + || T.stringof == "YearMonthDay" + || T.stringof == "Date" + || T.stringof == "date" + || T.stringof == "TimeOfDay" + || T.stringof == "Duration" + || T.stringof == "DateTime" + || T.stringof == "SysTime") + { + static if (T.stringof == "YearMonth") + { + return T(year, month); + } + else + static if (T.stringof == "Date" || T.stringof == "date" || T.stringof == "YearMonthDay") + { + return T(year, month, day); + } + else + static if (T.stringof == "DateTime") + { + return T(year, month, day, hour, minute, second); + } + else + static if (T.stringof == "TimeOfDay") + { + return T(hour, minute, second); + } + else + static if (T.stringof == "SysTime") + { + import core.time : hnsecs, minutes; + import std.datetime.date: DateTime; + import std.datetime.systime: SysTime; + import std.datetime.timezone: UTC, LocalTime, SimpleTimeZone; + auto ret = SysTime.fromUnixTime(toUnixTime, UTC()) + getFraction!7.hnsecs; + if (isLocalTime) + { + ret = ret.toLocalTime; + ret -= assumePureSafe(()=>ret.utcOffset)(); + } + else + if (offset) + { + ret.timezone = new immutable SimpleTimeZone(offset.minutes); + } + return ret; + } + else + static if (T.stringof == "Duration") + { + if (!isDuration) + { import mir.exception : toMutable; throw ExpectedDuration.toMutable; } + auto coeff = ((((long(year) * 7 + month) * 24 + hour) * 60 + minute) * 60 + second) * 10_000_000 + getFraction!7; + if (isNegativeDuration) + coeff = -coeff; + + import mir.conv: to; + import core.time: hnsecs; + return coeff.hnsecs.to!T; + } + } + + /++ + +/ + long getFraction(int digits)() @property const @safe pure nothrow @nogc + if (digits >= 1 && digits <= 12) + { + long coeff; + if (fractionCoefficient) + { + coeff = fractionCoefficient; + int exp = fractionExponent; + while (exp > -digits) + { + exp--; + coeff *= 10; + } + while (exp < -digits) + { + exp++; + coeff /= 10; + } + } + return coeff; + } + + /++ + Returns: true if timestamp represent a time only value. + +/ + bool isOnlyTime() @property const @safe pure nothrow @nogc + { + return precision > Precision.day && day == 0; + } + + /// + int opCmp(Timestamp rhs) const @safe pure nothrow @nogc + { + import std.meta: AliasSeq; + static foreach (member; [ + "year", + "month", + "day", + "hour", + "minute", + "second", + ]) + if (auto d = int(__traits(getMember, this, member)) - int(__traits(getMember, rhs, member))) + return d; + int frel = this.fractionExponent; + int frer = rhs.fractionExponent; + ulong frcl = this.fractionCoefficient; + ulong frcr = rhs.fractionCoefficient; + while(frel > frer) + { + frel--; + frcl *= 10; + } + while(frer > frel) + { + frer--; + frcr *= 10; + } + if (frcl < frcr) return -1; + if (frcl > frcr) return +1; + if (auto d = int(this.fractionExponent) - int(rhs.fractionExponent)) + return d; + return int(this.offset) - int(rhs.offset); + } + + /++ + Attaches local offset, doesn't adjust other fields. + Local-time offsets may be represented as either `hour*60+minute` offsets from UTC, + or as the zero to denote a local time of UTC. They are required on timestamps with time and are not allowed on date values. + +/ + @safe pure nothrow @nogc const + Timestamp withOffset(short minutes) + { + assert(-24 * 60 <= minutes && minutes <= 24 * 60, "Offset absolute value should be less or equal to 24 * 60"); + assert(precision >= Precision.minute, "Offsets are not allowed on date values."); + Timestamp ret = this; + ret.offset = minutes; + return ret; + } + + version(D_BetterC){} else + private string toStringImpl(alias fun)() const @safe pure nothrow + { + import mir.appender: UnsafeArrayBuffer; + char[64] buffer = void; + auto w = UnsafeArrayBuffer!char(buffer); + fun(w); + return w.data.idup; + } + + /++ + Converts this $(LREF Timestamp) to a string with the format `yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm`. + + If `w` writer is set, the resulting string will be written directly + to it. + + Returns: + A `string` when not using an output range; `void` otherwise. + +/ + alias toString = toISOExtString; + + /// + version (mir_test) + @safe pure nothrow unittest + { + import mir.test; + Timestamp.init.toString.should == "0000T"; + Timestamp(2010, 7, 4).toString.should == "2010-07-04"; + Timestamp(1998, 12, 25).toString.should == "1998-12-25"; + Timestamp(0, 1, 5).toString.should == "0000-01-05"; + Timestamp(-4, 1, 5).toString.should == "-0004-01-05"; + + // yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm + Timestamp(2021).toString.should == "2021T"; + Timestamp(2021, 01).toString.should == "2021-01T"; + Timestamp(2021, 01, 29).toString.should == "2021-01-29"; + Timestamp(2021, 01, 29, 19, 42).withOffset(0).toString.should == "2021-01-29T19:42Z"; + Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toString.should == "2021-01-29T19:42:44+07"; + Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toString.should == "2021-01-29T20:12:44+07:30"; + + Timestamp.onlyTime(7, 40).toString.should == "07:40-00:00"; + Timestamp.onlyTime(7, 40, 30).toString.should == "07:40:30-00:00"; + Timestamp.onlyTime(7, 40, 30, -3, 56).withOffset(0).toString.should == "07:40:30.056Z"; + } + + /// + version (mir_test) + @safe unittest + { + // Test A.D. + assert(Timestamp(9, 12, 4).toISOExtString == "0009-12-04"); + assert(Timestamp(99, 12, 4).toISOExtString == "0099-12-04"); + assert(Timestamp(999, 12, 4).toISOExtString == "0999-12-04"); + assert(Timestamp(9999, 7, 4).toISOExtString == "9999-07-04"); + assert(Timestamp(10000, 10, 20).toISOExtString == "+10000-10-20"); + + // Test B.C. + assert(Timestamp(0, 12, 4).toISOExtString == "0000-12-04"); + assert(Timestamp(-9, 12, 4).toISOExtString == "-0009-12-04"); + assert(Timestamp(-99, 12, 4).toISOExtString == "-0099-12-04"); + assert(Timestamp(-999, 12, 4).toISOExtString == "-0999-12-04"); + assert(Timestamp(-9999, 7, 4).toISOExtString == "-9999-07-04"); + assert(Timestamp(-10000, 10, 20).toISOExtString == "-10000-10-20"); + + assert(Timestamp.onlyTime(7, 40).toISOExtString == "07:40-00:00"); + assert(Timestamp.onlyTime(7, 40, 30).toISOExtString == "07:40:30-00:00"); + assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toISOExtString == "07:40:30.056-00:00"); + + const cdate = Timestamp(1999, 7, 6); + immutable idate = Timestamp(1999, 7, 6); + assert(cdate.toISOExtString == "1999-07-06"); + assert(idate.toISOExtString == "1999-07-06"); + } + + /// ditto + alias toISOExtString = toISOStringImp!true; + + /++ + Converts this $(LREF Timestamp) to a string with the format `YYYYMMDDThhmmss±hhmm`. + + If `w` writer is set, the resulting string will be written directly + to it. + + Returns: + A `string` when not using an output range; `void` otherwise. + +/ + alias toISOString = toISOStringImp!false; + + /// + version (mir_test) + @safe pure nothrow unittest + { + import mir.test; + Timestamp.init.toISOString.should == "0000T"; + Timestamp(2010, 7, 4).toISOString.should == "20100704"; + Timestamp(1998, 12, 25).toISOString.should == "19981225"; + Timestamp(0, 1, 5).toISOString.should == "00000105"; + Timestamp(-4, 1, 5).toISOString.should == "-00040105"; + + // YYYYMMDDThhmmss±hhmm + Timestamp(2021).toISOString.should == "2021T"; + Timestamp(2021, 01).toISOString.should == "2021-01T"; // always extended + Timestamp(2021, 01, 29).toISOString.should == "20210129"; + Timestamp(2021, 01, 29, 19, 42).toISOString.should == "20210129T1942"; + Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toISOString.should == "20210129T194244+07"; + Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toISOString.should == "20210129T201244+0730"; + static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toISOString == "20210129T201244+0730"); + + assert(Timestamp.onlyTime(7, 40).toISOString == "T0740"); + assert(Timestamp.onlyTime(7, 40, 30).toISOString == "T074030"); + assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toISOString == "T074030.056"); + } + + /// + long toUnixTime() const @safe pure nothrow @nogc + { + import mir.date: Date; + long result; + if (!isDuration && !isOnlyTime) + { + enum fistDay = Date.trustedCreate(1970, 1, 1); + result = Date.trustedCreate(year, month ? month : 1, day ? day : 1) - fistDay; + result *= 24; + } + result += hour; + result *= 60; + result += minute; + result *= 60; + result += second; + return result; + } + + /// + version(mir_test) + @safe pure nothrow @nogc unittest + { + assert(Timestamp(1970, 1, 1).toUnixTime == 0); + assert(Timestamp(2007, 12, 22, 8, 14, 45).toUnixTime == 1_198_311_285); + assert(Timestamp(2007, 12, 22, 8, 14, 45).withOffset(90).toUnixTime == 1_198_311_285); + assert(Timestamp(1969, 12, 31, 23, 59, 59).toUnixTime == -1); + } + + /// + static Timestamp fromUnixTime(long time) @safe pure nothrow @nogc + { + import mir.date: Date; + auto days = time / (24 * 60 * 60); + time -= days * (24 * 60 * 60); + if (time < 0) + { + days--; + time += 24 * 60 * 60; + } + assert(time >= 0); + auto second = time % 60; + time /= 60; + auto minute = time % 60; + time /= 60; + auto hour = cast(ubyte) time; + enum fistDay = Date.trustedCreate(1970, 1, 1); + with((fistDay + cast(int)days).yearMonthDay) + return Timestamp(year, cast(ubyte)month, day, hour, cast(ubyte)minute, cast(ubyte)second); + } + + /// + version(mir_test) + @safe pure nothrow @nogc unittest + { + import mir.format; + assert(Timestamp.fromUnixTime(0) == Timestamp(1970, 1, 1, 0, 0, 0)); + assert(Timestamp.fromUnixTime(1_198_311_285) == Timestamp(2007, 12, 22, 8, 14, 45)); + assert(Timestamp.fromUnixTime(-1) == Timestamp(1969, 12, 31, 23, 59, 59)); + } + + /// Helpfer for time zone offsets + void addMinutes(short minutes) @safe pure nothrow @nogc + { + int totalMinutes = minutes + (this.minute + this.hour * 60u); + auto h = totalMinutes / 60; + + int dayShift; + + while (totalMinutes < 0) + { + totalMinutes += 24 * 60; + dayShift--; + } + + while (totalMinutes >= 24 * 60) + { + totalMinutes -= 24 * 60; + dayShift++; + } + + if (dayShift) + { + import mir.date: Date; + auto ymd = (Date.trustedCreate(year, month, day) + dayShift).yearMonthDay; + year = ymd.year; + month = cast(ubyte)ymd.month; + day = ymd.day; + } + + hour = cast(ubyte) (totalMinutes / 60); + minute = cast(ubyte) (totalMinutes % 60); + } + + template toISOStringImp(bool ext) + { + version(D_BetterC){} else + string toISOStringImp() const @safe pure nothrow + { + return toStringImpl!toISOStringImp; + } + + /// ditto + void toISOStringImp(W)(ref scope W w) const scope + // if (isOutputRange!(W, char)) + { + import mir.format: printZeroPad; + // yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm + Timestamp t = this; + + if (t.offset) + { + assert(-24 * 60 <= t.offset && t.offset <= 24 * 60, "Offset absolute value should be less or equal to 24 * 60"); + assert(precision >= Precision.minute, "Offsets are not allowed on date values."); + t.addMinutes(t.offset); + } + + if (!t.isOnlyTime) + { + if (t.year >= 10_000) + w.put('+'); + printZeroPad(w, t.year, t.year >= 0 ? t.year < 10_000 ? 4 : 5 : t.year > -10_000 ? 5 : 6); + if (precision == Precision.year) + { + w.put('T'); + return; + } + if (ext || precision == Precision.month) w.put('-'); + + printZeroPad(w, cast(uint)t.month, 2); + if (precision == Precision.month) + { + w.put('T'); + return; + } + static if (ext) w.put('-'); + + printZeroPad(w, t.day, 2); + if (precision == Precision.day) + return; + } + + if (!ext || !t.isOnlyTime) + w.put('T'); + + printZeroPad(w, t.hour, 2); + static if (ext) w.put(':'); + printZeroPad(w, t.minute, 2); + + if (precision >= Precision.second) + { + static if (ext) w.put(':'); + printZeroPad(w, t.second, 2); + + if (precision > Precision.second && (t.fractionExponent < 0 || t.fractionCoefficient)) + { + w.put('.'); + printZeroPad(w, t.fractionCoefficient, -int(t.fractionExponent)); + } + } + + if (t.isLocalTime) + { + static if (ext) + w.put("-00:00"); + return; + } + + if (t.offset == 0) + { + w.put('Z'); + return; + } + + bool sign = t.offset < 0; + uint absoluteOffset = !sign ? t.offset : -int(t.offset); + uint offsetHour = absoluteOffset / 60u; + uint offsetMinute = absoluteOffset % 60u; + + w.put(sign ? '-' : '+'); + printZeroPad(w, offsetHour, 2); + if (offsetMinute) + { + static if (ext) w.put(':'); + printZeroPad(w, offsetMinute, 2); + } + } + } + + /++ + Creates a $(LREF Timestamp) from a string with the format `YYYYMMDDThhmmss±hhmm + or its leading part allowed by the standard. + + or its leading part allowed by the standard. + + Params: + str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) formats dates. + value = (optional) result value. + + Throws: + $(LREF DateTimeException) if the given string is + not in the correct format. Two arguments overload is `nothrow`. + Returns: + `bool` on success for two arguments overload, and the resulting timestamp for single argument overdload. + +/ + alias fromISOString = fromISOStringImpl!false; + + /// + version (mir_test) + @safe unittest + { + assert(Timestamp.fromISOString("20100704") == Timestamp(2010, 7, 4)); + assert(Timestamp.fromISOString("19981225") == Timestamp(1998, 12, 25)); + assert(Timestamp.fromISOString("00000105") == Timestamp(0, 1, 5)); + // assert(Timestamp.fromISOString("-00040105") == Timestamp(-4, 1, 5)); + + assert(Timestamp(2021) == Timestamp.fromISOString("2021")); + assert(Timestamp(2021) == Timestamp.fromISOString("2021T")); + // assert(Timestamp(2021, 01) == Timestamp.fromISOString("2021-01")); + // assert(Timestamp(2021, 01) == Timestamp.fromISOString("2021-01T")); + assert(Timestamp(2021, 01, 29) == Timestamp.fromISOString("20210129")); + assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOString("20210129T1942")); + assert(Timestamp(2021, 01, 29, 19, 42).withOffset(0) == Timestamp.fromISOString("20210129T1942Z")); + assert(Timestamp(2021, 01, 29, 19, 42, 12) == Timestamp.fromISOString("20210129T194212")); + assert(Timestamp(2021, 01, 29, 19, 42, 12, -3, 67).withOffset(0) == Timestamp.fromISOString("20210129T194212.067Z")); + assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60) == Timestamp.fromISOString("20210129T194244+07")); + assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730")); + static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730")); + static assert(Timestamp(2021, 01, 29, 4, 42, 44).withOffset(- (7 * 60 + 30)) == Timestamp.fromISOString("20210128T211244-0730")); + } + + version (mir_test) + @safe unittest + { + import std.exception: assertThrown; + assertThrown!DateTimeException(Timestamp.fromISOString("")); + assertThrown!DateTimeException(Timestamp.fromISOString("990704")); + assertThrown!DateTimeException(Timestamp.fromISOString("0100704")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010070")); + assertThrown!DateTimeException(Timestamp.fromISOString("120100704")); + assertThrown!DateTimeException(Timestamp.fromISOString("-0100704")); + assertThrown!DateTimeException(Timestamp.fromISOString("+0100704")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010070a")); + assertThrown!DateTimeException(Timestamp.fromISOString("20100a04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010a704")); + + assertThrown!DateTimeException(Timestamp.fromISOString("99-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-07-0")); + assertThrown!DateTimeException(Timestamp.fromISOString("12010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("-010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("+010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-07-0a")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-0a-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-a7-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010/07/04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010/7/04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010/7/4")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010/07/4")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-7-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-7-4")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-07-4")); + + assertThrown!DateTimeException(Timestamp.fromISOString("99Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOString("010Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010Jul0")); + assertThrown!DateTimeException(Timestamp.fromISOString("12010Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOString("-010Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOString("+010Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010Jul0a")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010Jua04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010aul04")); + + assertThrown!DateTimeException(Timestamp.fromISOString("99-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("010-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jul-0")); + assertThrown!DateTimeException(Timestamp.fromISOString("12010-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("-010-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("+010-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jul-0a")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jua-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jal-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-aul-04")); + + // assertThrown!DateTimeException(Timestamp.fromISOString("2010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOString("2010-Jul-04")); + + assert(Timestamp.fromISOString("19990706") == Timestamp(1999, 7, 6)); + // assert(Timestamp.fromISOString("-19990706") == Timestamp(-1999, 7, 6)); + // assert(Timestamp.fromISOString("+019990706") == Timestamp(1999, 7, 6)); + assert(Timestamp.fromISOString("19990706") == Timestamp(1999, 7, 6)); + } + + // bug# 17801 + version (mir_test) + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + static foreach (C; AliasSeq!(char, wchar, dchar)) + { + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(Timestamp.fromISOString(to!S("20121221")) == Timestamp(2012, 12, 21)); + } + } + + /++ + Creates a $(LREF Timestamp) from a string with the format `yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm` + or its leading part allowed by the standard. + + Params: + str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) formats dates. + value = (optional) result value. + + Throws: + $(LREF DateTimeException) if the given string is + not in the correct format. Two arguments overload is `nothrow`. + Returns: + `bool` on success for two arguments overload, and the resulting timestamp for single argument overdload. + +/ + alias fromISOExtString = fromISOStringImpl!true; + + + /// + version (mir_test) + @safe unittest + { + assert(Timestamp.fromISOExtString("2010-07-04") == Timestamp(2010, 7, 4)); + assert(Timestamp.fromISOExtString("1998-12-25") == Timestamp(1998, 12, 25)); + assert(Timestamp.fromISOExtString("0000-01-05") == Timestamp(0, 1, 5)); + assert(Timestamp.fromISOExtString("-0004-01-05") == Timestamp(-4, 1, 5)); + + assert(Timestamp(2021) == Timestamp.fromISOExtString("2021")); + assert(Timestamp(2021) == Timestamp.fromISOExtString("2021T")); + assert(Timestamp(2021, 01) == Timestamp.fromISOExtString("2021-01")); + assert(Timestamp(2021, 01) == Timestamp.fromISOExtString("2021-01T")); + assert(Timestamp(2021, 01, 29) == Timestamp.fromISOExtString("2021-01-29")); + assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOExtString("2021-01-29T19:42")); + assert(Timestamp(2021, 01, 29, 19, 42).withOffset(0) == Timestamp.fromISOExtString("2021-01-29T19:42Z")); + assert(Timestamp(2021, 01, 29, 19, 42, 12) == Timestamp.fromISOExtString("2021-01-29T19:42:12")); + assert(Timestamp(2021, 01, 29, 19, 42, 12, -3, 67).withOffset(0) == Timestamp.fromISOExtString("2021-01-29T19:42:12.067Z")); + assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60) == Timestamp.fromISOExtString("2021-01-29T19:42:44+07")); + assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOExtString("2021-01-29T20:12:44+07:30")); + static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOExtString("2021-01-29T20:12:44+07:30")); + static assert(Timestamp(2021, 01, 29, 4, 42, 44).withOffset(- (7 * 60 + 30)) == Timestamp.fromISOExtString("2021-01-28T21:12:44-07:30")); + } + + /++ + Creates a $(LREF Timestamp) from a YAML string format + or its leading part allowed by the standard. + + Params: + str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) formats dates. + value = (optional) result value. + + Throws: + $(LREF DateTimeException) if the given string is + not in the correct format. Two arguments overload is `nothrow`. + Returns: + `bool` on success for two arguments overload, and the resulting timestamp for single argument overdload. + +/ + alias fromYamlString = fromISOStringImpl!(true, true); + + /// + version (mir_test) + @safe unittest + { + // canonical + assert(Timestamp.fromYamlString("2001-12-15T02:59:43.1Z") == Timestamp("2001-12-15T02:59:43.1Z")); + // with lower 't' separator + assert(Timestamp.fromYamlString("2001-12-14t21:59:43.1-05:30") == Timestamp("2001-12-14T21:59:43.1-05:30")); + // yaml space separated + assert(Timestamp.fromYamlString("2001-12-14 21:59:43.1 -5") == Timestamp("2001-12-14T21:59:43.1-05")); + // no time zone (Z) + assert(Timestamp.fromYamlString("2001-12-15 2:59:43.10") == Timestamp("2001-12-15T02:59:43.10")); + // date 00:00:00Z + assert(Timestamp.fromYamlString("2002-12-14") == Timestamp("2002-12-14")); + } + + version (mir_test) + @safe unittest + { + import std.exception: assertThrown; + + assertThrown!DateTimeException(Timestamp.fromISOExtString("")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("990704")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("0100704")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("120100704")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("-0100704")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("+0100704")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010070a")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("20100a04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010a704")); + + assertThrown!DateTimeException(Timestamp.fromISOExtString("99-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-07-0")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("12010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("-010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("+010-07-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-07-0a")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-0a-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-a7-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010/07/04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010/7/04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010/7/4")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010/07/4")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-7-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-7-4")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-07-4")); + + assertThrown!DateTimeException(Timestamp.fromISOExtString("99Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("010Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010Jul0")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("12010Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("-010Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("+010Jul04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010Jul0a")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010Jua04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010aul04")); + + assertThrown!DateTimeException(Timestamp.fromISOExtString("99-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("010-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jul-0")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("12010-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("-010-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("+010-Jul-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jul-0a")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jua-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jal-04")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-aul-04")); + + assertThrown!DateTimeException(Timestamp.fromISOExtString("20100704")); + assertThrown!DateTimeException(Timestamp.fromISOExtString("2010-Jul-04")); + + assert(Timestamp.fromISOExtString("1999-07-06") == Timestamp(1999, 7, 6)); + assert(Timestamp.fromISOExtString("-1999-07-06") == Timestamp(-1999, 7, 6)); + assert(Timestamp.fromISOExtString("+01999-07-06") == Timestamp(1999, 7, 6)); + } + + // bug# 17801 + version (mir_test) + @safe unittest + { + import std.conv : to; + import std.meta : AliasSeq; + static foreach (C; AliasSeq!(char, wchar, dchar)) + { + static foreach (S; AliasSeq!(C[], const(C)[], immutable(C)[])) + assert(Timestamp.fromISOExtString(to!S("2012-12-21")) == Timestamp(2012, 12, 21)); + } + } + + /++ + Creates a $(LREF Timestamp) from a string. + + Params: + str = A string formatted in the way that $(LREF .Timestamp.toISOExtString) and $(LREF .Timestamp.toISOString) format dates, also YAML like spaces seprated strings are accepted. The function is case sensetive. + value = (optional) result value. + + Throws: + $(LREF DateTimeException) if the given string is + not in the correct format. Two arguments overload is `nothrow`. + Returns: + `bool` on success for two arguments overload, and the resulting timestamp for single argument overdload. + +/ + static bool fromString(C)(scope const(C)[] str, out Timestamp value) @safe pure nothrow @nogc + { + return fromYamlString(str, value) + || fromISOString(str, value); + } + + /// + version (mir_test) + @safe pure @nogc unittest + { + assert(Timestamp.fromString("2010-07-04") == Timestamp(2010, 7, 4)); + assert(Timestamp.fromString("20100704") == Timestamp(2010, 7, 4)); + } + + /// ditto + static Timestamp fromString(C)(scope const(C)[] str) @safe pure + if (isSomeChar!C) + { + Timestamp ret; + if (fromString(str, ret)) + return ret; + { import mir.exception : toMutable; throw InvalidString.toMutable; } + } + + template fromISOStringImpl(bool ext, bool yaml = false) + { + static Timestamp fromISOStringImpl(C)(scope const(C)[] str) @safe pure + if (isSomeChar!C) + { + Timestamp ret; + if (fromISOStringImpl(str, ret)) + return ret; + static if (yaml) + { import mir.exception : toMutable; throw InvalidYamlString.toMutable; } + else + static if (ext) + { import mir.exception : toMutable; throw InvalidISOExtendedString.toMutable; } + else + { import mir.exception : toMutable; throw InvalidISOString.toMutable; } + } + + static bool fromISOStringImpl(C)(scope const(C)[] str, out Timestamp value) @safe pure nothrow @nogc + if (isSomeChar!C) + { + import mir.parse: fromString, parse; + + if (str.length < 4) + return false; + + static if (ext) + auto isOnlyTime = (str[0] == 'T' || yaml && (str[0] == 't')) || str[2] == ':'; + else + auto isOnlyTime = str[0] == 'T' || yaml && (str[0] == 't'); + + if (!isOnlyTime) + { + // YYYY + static if (ext) + {{ + auto startIsDigit = str.length && str[0].isDigit; + auto strOldLength = str.length; + if (!parse(str, value.year)) + return false; + auto l = strOldLength - str.length; + if ((l == 4) != startIsDigit) + return false; + }} + else + { + if (str.length < 4 || !str[0].isDigit || !fromString(str[0 .. 4], value.year)) + return false; + str = str[4 .. $]; + } + + value.precision = Precision.year; + if (str.length == 0 || str == "T") + return true; + + static if (ext) + { + if (str[0] != '-') + return false; + str = str[1 .. $]; + } + + // MM + if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.month)) + return false; + str = str[2 .. $]; + value.precision = Precision.month; + if (str.length == 0 || str.length == 1 && (str[0] == 'T' || (yaml && (str[0] == 't' || str[0] == ' ' || str[0] == '\t')))) + return ext; + + static if (ext) + { + if (str[0] != '-') + return false; + str = str[1 .. $]; + } + + // DD + if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.day)) + return false; + str = str[2 .. $]; + value.precision = Precision.day; + if (str.length == 0) + return true; + } + + // str isn't empty here + // T + if ((str[0] == 'T' || (yaml && (str[0] == 't' || str[0] == ' ' || str[0] == '\t')))) + { + str = str[1 .. $]; + // OK, onlyTime requires length >= 3 + if (str.length == 0) + return true; + } + else + { + if (!(ext && isOnlyTime)) + return false; + } + + value.precision = Precision.minute; // we don't have hour precision + + // hh + if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], value.hour)) + { + static if (yaml) + { + if (str.length < 1 || !str[0].isDigit || !fromString(str[0 .. 1], value.hour)) + return false; + else + str = str[1 .. $]; + } + else + return false; + } + else + str = str[2 .. $]; + + if (str.length == 0) + return true; + + static if (ext) + { + if (str[0] != ':') + return false; + str = str[1 .. $]; + } + + // mm + { + uint minute; + if (str.length < 2 || !str[0].isDigit || !fromString(str[0 .. 2], minute)) + return false; + value.minute = cast(ubyte) minute; + str = str[2 .. $]; + if (str.length == 0) + return true; + } + + static if (ext) + { + if (str[0] != ':') + goto TZ; + str = str[1 .. $]; + } + + // ss + { + uint second; + if (str.length < 2 || !str[0].isDigit) + goto TZ; + if (!fromString(str[0 .. 2], second)) + return false; + value.second = cast(ubyte) second; + str = str[2 .. $]; + value.precision = Precision.second; + if (str.length == 0) + return true; + } + + // . + if (str[0] != '.') + goto TZ; + str = str[1 .. $]; + value.precision = Precision.fraction; + + // fraction + { + const strOldLength = str.length; + ulong fractionCoefficient; + if (str.length < 1 || !str[0].isDigit || !parse!ulong(str, fractionCoefficient)) + return false; + sizediff_t fractionExponent = str.length - strOldLength; + if (fractionExponent < -12) + return false; + value.fractionExponent = cast(byte)fractionExponent; + value.fractionCoefficient = fractionCoefficient; + if (str.length == 0) + return true; + } + + TZ: + + static if (yaml) + { + if (str.length && (str[0] == ' ' || str[0] == '\t')) + str = str[1 .. $]; + } + + if (str == "Z") + { + value.offset = 0; + return true; + } + + bool neg = str[0] == '-'; + + int hour; + int minute; + if (str.length < 3 || str[0].isDigit || !fromString(str[0 .. 3], hour)) + { + static if (yaml) + { + if (str.length < 2 || str[0].isDigit || !fromString(str[0 .. 2], hour)) + return false; + str = str[2 .. $]; + } + else + return false; + } + else + { + str = str[3 .. $]; + } + + if (str.length) + { + static if (ext) + { + if (str[0] != ':') + return false; + str = str[1 .. $]; + } + if (str.length != 2 || !str[0].isDigit || !fromString(str[0 .. 2], minute)) + return false; + } + + if (neg && hour == 0 && minute == 0) + value.setLocalTimezone; + else + value.offset = cast(short)(hour * 60 + (hour < 0 ? -minute : minute)); + value.addMinutes(cast(short)-int(value.offset)); + return true; + } + } + + /// + bool isDuration() const @safe pure nothrow @nogc @property + { + return day == 88 || day == 99; + } + + /// + bool isNegativeDuration() const @safe pure nothrow @nogc @property + { + return day == 99; + } +} + +version(mir_test) +unittest +{ + long sec = -2208988800; + uint nanosec = 0; + auto ts = Timestamp.fromUnixTime(sec); + if (nanosec >= 0) + { + ts.precision = Timestamp.Precision.fraction; + ts.fractionCoefficient = nanosec; + ts.fractionExponent = -9; + } + auto ts2 = "1900-01-01T00:00:00.000000000".Timestamp; + assert(ts == ts2); +} + +version(mir_test) +@safe pure unittest +{ + import std.datetime.systime : SysTime; + import mir.test; + auto ts = "2001-12-15T2:59:43.1234567".Timestamp; + // ts.toString.should == "2001-12-15T02:59:43.1234567-00:00"; + auto st = cast(SysTime)ts; + // st.toISOExtString.should == "2001-12-15T02:59:43.1234567"; + st.Timestamp.should == ts; +} + +private auto assumePureSafe(T)(T t) @trusted + // if (isFunctionPointer!T || isDelegate!T) +{ + import std.traits; + enum attrs = (functionAttributes!T | FunctionAttribute.pure_ | FunctionAttribute.safe) & ~FunctionAttribute.system; + return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; +} diff --git a/source/mir/type_info.d b/source/mir/type_info.d index 2e28317c..7983d0bc 100644 --- a/source/mir/type_info.d +++ b/source/mir/type_info.d @@ -3,8 +3,8 @@ $(H1 Type Information) Type Information implementation compatible with BetterC mode. -Copyright: Copyright © 2019, Kaleidic Associates Advisory Limited -Authors: Ilya Yaroshenko +Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments +Authors: Ilia Ki Macros: NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) @@ -12,8 +12,6 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) +/ module mir.type_info; -import std.traits: Unqual; - /++ +/ struct mir_type_info @@ -27,20 +25,12 @@ struct mir_type_info int size; } -/++ -Convinience function for integration with .NET. -+/ -export extern(C) void mir_type_info_init(ref mir_type_info ti, typeof(mir_type_info.init.destructor) destructor, int size) - @safe pure nothrow @nogc -{ - ti.destructor = destructor; - ti.size = size; -} - /++ +/ ref immutable(mir_type_info) mir_get_type_info(T)() @trusted { + import std.traits: Unqual, hasElaborateDestructor; + static if (is(T == class)) enum sizeof = __traits(classInstanceSize, T); else @@ -51,7 +41,7 @@ ref immutable(mir_type_info) mir_get_type_info(T)() @trusted return mir_get_type_info!(Unqual!T); } else - static if (hasDestructor!T) + static if (hasElaborateDestructor!T) { import std.traits: SetFunctionAttributes, functionAttributes; alias fun = void function(void*) @safe pure nothrow @nogc; @@ -93,6 +83,8 @@ ref immutable(mir_type_info) mir_get_type_info(uint sizeof)() package template hasDestructor(T) { + import std.traits: Unqual; + static if (is(T == struct)) { static if (__traits(hasMember, Unqual!T, "__xdtor")) @@ -113,6 +105,8 @@ package template hasDestructor(T) package const(void)* mir_get_payload_ptr(T)() { + import std.traits: Unqual; + static if (!is(T == Unqual!T)) { return mir_get_payload_ptr!(Unqual!T); diff --git a/test_examples.sh b/test_examples.sh deleted file mode 100755 index 4f0cb3df..00000000 --- a/test_examples.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -dub fetch dtools - -# extract examples -dub run dtools:tests_extractor -- -i source -o out - -git clone https://github.com/libmir/mir-core - -# compile the examples -for file in $(find out -name "*.d") ; do - echo "Testing: $file" - $DMD -de -unittest -Isource -Imir-core/source -c -o- "$file" -done