├── .circleci └── config.yml ├── .github └── workflows │ ├── ci.yml │ └── compilers.json ├── .gitignore ├── .travis.yml ├── README.md ├── dub.sdl ├── index.d ├── meson.build ├── source └── mir │ └── optim │ ├── boxcqp.d │ ├── fit_splie.d │ └── least_squares.d └── subprojects ├── cblas-d.wrap ├── lapack-d.wrap ├── mir-algorithm.wrap ├── mir-blas.wrap ├── mir-core.wrap ├── mir-lapack.wrap ├── mir-linux-kernel.wrap └── mir-random.wrap /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | mirci: libmir/upload_docs@0.1.4 5 | 6 | workflows: 7 | version: 2 8 | build-deploy: 9 | jobs: 10 | - mirci/test_and_build_docs: 11 | filters: 12 | tags: 13 | only: /^v(\d)+(\.(\d)+)+$/ 14 | - mirci/upload_docs: 15 | to: mir-optim.libmir.org 16 | requires: 17 | - mirci/test_and_build_docs 18 | filters: 19 | branches: 20 | ignore: /.*/ 21 | tags: 22 | only: /^v(\d)+(\.(\d)+)+$/ 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | # allow this workflow to be triggered manually 12 | 13 | jobs: 14 | setup: 15 | name: 'Load job configuration' 16 | runs-on: ubuntu-22.04 17 | outputs: 18 | compilers: ${{ steps.load-config.outputs.compilers }} 19 | steps: 20 | - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 21 | # This step checks if we want to only run tests on a specific platform or 22 | # if we want to skip CI entirely, then outputs the compilers to be used for 23 | # each job. 24 | - id: load-config 25 | uses: actions/github-script@9ac08808f993958e9de277fe43a64532a609130e 26 | with: 27 | script: | 28 | const base_compiler_config = require("./.github/workflows/compilers.json"); 29 | const compilers = {"windows": [], "macos": [], "ubuntu": []}; 30 | const {owner, repo} = context.repo; 31 | let commit_sha = context.sha; 32 | if (context.eventName == "pull_request") 33 | { 34 | commit_sha = context.payload.pull_request.head.sha; 35 | } 36 | 37 | const commit = await github.rest.git.getCommit({ 38 | owner, 39 | repo, 40 | commit_sha 41 | }); 42 | const head_commit_message = commit.data.message; 43 | 44 | if (head_commit_message.startsWith("[windows-only]")) 45 | { 46 | compilers.windows = base_compiler_config; 47 | } 48 | else if (head_commit_message.startsWith("[macos-only]")) 49 | { 50 | compilers.macos = base_compiler_config; 51 | } 52 | else if (head_commit_message.startsWith("[ubuntu-only]")) 53 | { 54 | compilers.ubuntu = base_compiler_config; 55 | } 56 | else if (!head_commit_message.startsWith("[skip-ci]")) 57 | { 58 | compilers.windows = base_compiler_config; 59 | compilers.macos = base_compiler_config; 60 | compilers.ubuntu = base_compiler_config; 61 | } 62 | core.setOutput("compilers", JSON.stringify(compilers)); 63 | 64 | macos: 65 | name: '[macos] x86_64/${{ matrix.dc }}' 66 | runs-on: macos-latest 67 | needs: setup 68 | # Only run if the setup phase explicitly defined compilers to be used 69 | if: ${{ fromJSON(needs.setup.outputs.compilers).macos != '' && fromJSON(needs.setup.outputs.compilers).macos != '[]' }} 70 | continue-on-error: ${{ contains(matrix.dc, 'beta') || contains(matrix.dc, 'master') }} 71 | env: 72 | ARCH: x86_64 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | dc: ${{ fromJSON(needs.setup.outputs.compilers).macos }} 77 | steps: 78 | - name: Checkout repo 79 | uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 80 | with: 81 | fetch-depth: 0 82 | - name: Setup D compiler 83 | uses: dlang-community/setup-dlang@763d869b4d67e50c3ccd142108c8bca2da9df166 84 | with: 85 | compiler: ${{ matrix.dc }} 86 | - name: Install dependencies 87 | run: | 88 | brew install openblas 89 | brew link openblas 90 | - name: Cache dub dependencies 91 | uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed 92 | with: 93 | path: ~/.dub/packages 94 | key: macos-latest-build-${{ hashFiles('**/dub.sdl', '**/dub.json') }} 95 | restore-keys: | 96 | macos-latest-build- 97 | - name: Build / test 98 | run: | 99 | dub test --arch=$ARCH --build=unittest-cov -c unittest-openblas 100 | dub test --arch=$ARCH --build=unittest-cov -c unittest-blas 101 | shell: bash 102 | - name: Upload coverage data 103 | uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b 104 | 105 | ubuntu: 106 | name: '[ubuntu] x86_64/${{ matrix.dc }}' 107 | runs-on: ubuntu-latest 108 | needs: setup 109 | # Only run if the setup phase explicitly defined compilers to be used 110 | if: ${{ fromJSON(needs.setup.outputs.compilers).ubuntu != '' && fromJSON(needs.setup.outputs.compilers).ubuntu != '[]' }} 111 | continue-on-error: ${{ contains(matrix.dc, 'beta') || contains(matrix.dc, 'master') }} 112 | env: 113 | # Ubuntu no longer has a 32-bit version of LAPACK available, so 114 | # we have to drop support. :( 115 | ARCH: x86_64 116 | strategy: 117 | fail-fast: false 118 | matrix: 119 | dc: ${{ fromJSON(needs.setup.outputs.compilers).ubuntu }} 120 | steps: 121 | - name: Checkout repo 122 | uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 123 | with: 124 | fetch-depth: 0 125 | - name: Setup D compiler 126 | uses: dlang-community/setup-dlang@763d869b4d67e50c3ccd142108c8bca2da9df166 127 | with: 128 | compiler: ${{ matrix.dc }} 129 | - name: Download dependencies 130 | run: | 131 | sudo apt-get update 132 | sudo apt-get install -y liblapack-dev 133 | - name: Cache dub dependencies 134 | uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed 135 | with: 136 | path: ~/.dub/packages 137 | key: ubuntu-latest-build-${{ hashFiles('**/dub.sdl', '**/dub.json') }} 138 | restore-keys: | 139 | ubuntu-latest-build- 140 | - name: Build / test 141 | run: | 142 | dub test --arch=$ARCH --build=unittest-cov -c unittest-blas 143 | shell: bash 144 | - name: Upload coverage data 145 | uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b 146 | 147 | windows: 148 | name: '[windows] x86_64/${{ matrix.dc }}' 149 | runs-on: windows-latest 150 | needs: setup 151 | # Only run if the setup phase explicitly defined compilers to be used 152 | if: ${{ fromJSON(needs.setup.outputs.compilers).windows != '' && fromJSON(needs.setup.outputs.compilers).windows != '[]' }} 153 | continue-on-error: ${{ contains(matrix.dc, 'beta') || contains(matrix.dc, 'master') }} 154 | env: 155 | ARCH: x86_64 156 | strategy: 157 | fail-fast: false 158 | matrix: 159 | dc: ${{ fromJSON(needs.setup.outputs.compilers).windows }} 160 | steps: 161 | - name: Checkout repo 162 | uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 163 | with: 164 | fetch-depth: 0 165 | - name: Setup D compiler 166 | uses: dlang-community/setup-dlang@763d869b4d67e50c3ccd142108c8bca2da9df166 167 | with: 168 | compiler: ${{ matrix.dc }} 169 | - uses: actions/setup-python@9c644ca2ab8e57ea0a487b5ec2f8290740378bfd 170 | with: 171 | python-version: '3.10' 172 | architecture: 'x64' 173 | # Why oh god does Windows not have a good package manager 174 | - name: Install dependencies 175 | run: | 176 | $CONDA/condabin/conda.bat install -y -c intel -c conda-forge mkl-static lapack 177 | shell: bash 178 | - name: Cache dub dependencies 179 | uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed 180 | with: 181 | path: ~\AppData\Local\dub 182 | key: windows-latest-build-${{ hashFiles('**/dub.sdl', '**/dub.json') }} 183 | restore-keys: | 184 | windows-latest-build- 185 | - name: Build / test 186 | run: | 187 | echo 'lflags "/LIBPATH:$CONDA/Library/lib"' >> dub.sdl 188 | dub test --arch=$ARCH --build=unittest-cov -c unittest-mkl-sequential 189 | shell: bash 190 | - name: Upload coverage data 191 | uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b 192 | 193 | 194 | -------------------------------------------------------------------------------- /.github/workflows/compilers.json: -------------------------------------------------------------------------------- 1 | [ 2 | "dmd-master", 3 | "dmd-latest", 4 | "dmd-beta", 5 | "dmd-2.098.1", 6 | "ldc-master", 7 | "ldc-latest", 8 | "ldc-beta", 9 | "ldc-1.28.1" 10 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | mir-optim.so 6 | mir-optim.dylib 7 | mir-optim.dll 8 | mir-optim.a 9 | mir-optim.lib 10 | mir-optim-test-* 11 | *.exe 12 | *.o 13 | *.obj 14 | *.lst 15 | .DS_Store 16 | *.out 17 | dub.selections.json 18 | *.a 19 | build/ 20 | build/mir-algorithm/ 21 | build/mir-blas/ 22 | build/mir-lapack/ 23 | build/cblas/ 24 | build/lapack/ 25 | build/mir-random/ 26 | *.dll 27 | x.txt 28 | subprojects/*/ 29 | web 30 | docgen 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | os: 4 | - linux 5 | 6 | language: d 7 | 8 | d: 9 | - dmd 10 | - ldc 11 | - ldc-beta 12 | 13 | matrix: 14 | allow_failures: 15 | - {d: ldc-beta} 16 | 17 | branches: 18 | only: 19 | - master 20 | 21 | script: 22 | - dub test -c unittest-blas 23 | # - if [ "$DC" = "ldc2" ]; then rm -rf .dub && dub build -c blas --compiler=ldmd2 --build-mode=singleFile --build=better-c-release --force && g++ -std=c++1y -pthread -I./include examples/least_squares.cpp -L./ -lmir-optim -llapack -lblas && ./a.out; fi 24 | 25 | addons: 26 | apt: 27 | update: true 28 | packages: 29 | - liblapack-dev 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/libmir/mir-optim.svg?branch=master)](https://travis-ci.org/libmir/mir-optim) 2 | [![Dub downloads](https://img.shields.io/dub/dt/mir-optim.svg)](http://code.dlang.org/packages/mir-optim) 3 | [![Dub downloads](https://img.shields.io/dub/dm/mir-optim.svg)](http://code.dlang.org/packages/mir-optim) 4 | [![License](https://img.shields.io/dub/l/mir-optim.svg)](http://code.dlang.org/packages/mir-optim) 5 | [![Latest version](https://img.shields.io/dub/v/mir-optim.svg)](http://code.dlang.org/packages/mir-optim) 6 | 7 | # mir-optim 8 | 9 | Dlang BetterC Nonlinear Optimizers. 10 | 11 | ### Algorithms 12 | 13 | - Modified Levenberg-Marquardt Algorithm (Nonlinear Least Squares). 14 | - Boxed Constrained Quadratic Problem Solver 15 | 16 | See also [online documentation](http://mir-optim.libmir.org). 17 | 18 | ### Features 19 | 20 | - Tiny BetterC library size 21 | - Based on LAPACK 22 | - Fast compilaiton speed. There are two (for `float` and `double`) precompiled algorithm instatiations. Generic API is a thin wrappers around them. 23 | - Four APIs for any purpose: 24 | * Extern C API 25 | * Powerfull high level generic D API 26 | * Nothrow middle level generic D API 27 | * Low level nongeneric D API 28 | 29 | ## Required system libraries 30 | 31 | See [wiki: Link with CBLAS & LAPACK](https://github.com/libmir/mir-lapack/wiki/Link-with-CBLAS-&-LAPACK). 32 | 33 | ### Our sponsors 34 | 35 | [](http://symmetryinvestments.com/)         36 | [](https://github.com/kaleidicassociates) 37 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "mir-optim" 2 | authors "Ilya Yaroshenko" 3 | description "Optimisation Framework" 4 | copyright "Copyright © 2018, Kaleidic Associates and Symmetry Investments" 5 | targetType "library" 6 | license "BSL-1.0" 7 | dependency "mir-lapack" version=">=1.2.3" 8 | dependency "mir-algorithm" version=">=3.7.19" 9 | 10 | buildType "better-c-release" { 11 | buildOptions "releaseMode" "inline" "noBoundsCheck" 12 | dflags "-betterC" "-Os" "-nogc" "-linkonce-templates" "-enable-cross-module-inlining" platform="ldc" 13 | dflags "-betterC" "-O" platform="dmd" # no proper betterC support in DMD 14 | dflags "-betterC" "-O" platform="gdc" # no proper betterC support in GDC 15 | } 16 | 17 | configuration "library" { 18 | targetType "library" 19 | # default or user specified mir-lapack sub-configuration is used 20 | } 21 | 22 | configuration "unittest" { 23 | dependency "mir-random" version=">=1.0.0-beta" 24 | } 25 | 26 | configuration "openblas" { 27 | subConfiguration "mir-lapack" "openblas" 28 | } 29 | 30 | configuration "threelib" { 31 | subConfiguration "mir-lapack" "threelib" 32 | } 33 | 34 | configuration "cblas" { 35 | subConfiguration "mir-lapack" "cblas" 36 | } 37 | 38 | configuration "blas" { 39 | subConfiguration "mir-lapack" "blas" 40 | } 41 | 42 | configuration "lapack" { 43 | subConfiguration "mir-lapack" "lapack" 44 | } 45 | 46 | configuration "mkl-sequential" { 47 | subConfiguration "mir-lapack" "mkl-sequential" 48 | } 49 | 50 | configuration "mkl-sequential-ilp" { 51 | subConfiguration "mir-lapack" "mkl-sequential-ilp" 52 | } 53 | 54 | configuration "mkl-tbb-thread" { 55 | subConfiguration "mir-lapack" "mkl-tbb-thread" 56 | } 57 | 58 | configuration "mkl-tbb-thread-ilp" { 59 | subConfiguration "mir-lapack" "mkl-tbb-thread-ilp" 60 | } 61 | 62 | configuration "mkl-sequential-dll" { 63 | subConfiguration "mir-lapack" "mkl-sequential-dll" 64 | } 65 | 66 | configuration "mkl-sequential-ilp-dll" { 67 | subConfiguration "mir-lapack" "mkl-sequential-ilp-dll" 68 | } 69 | 70 | configuration "mkl-tbb-thread-dll" { 71 | subConfiguration "mir-lapack" "mkl-tbb-thread-dll" 72 | } 73 | 74 | configuration "mkl-tbb-thread-ilp-dll" { 75 | subConfiguration "mir-lapack" "mkl-tbb-thread-ilp-dll" 76 | } 77 | 78 | configuration "zerolib" { 79 | subConfiguration "mir-lapack" "zerolib" 80 | } 81 | 82 | configuration "unittest-openblas" { 83 | versions "mir_optim_test" 84 | dependency "mir-random" version=">=1.0.0-beta" 85 | subConfiguration "mir-lapack" "openblas" 86 | } 87 | 88 | configuration "unittest-threelib" { 89 | versions "mir_optim_test" 90 | dependency "mir-random" version=">=1.0.0-beta" 91 | subConfiguration "mir-lapack" "threelib" 92 | } 93 | 94 | configuration "unittest-cblas" { 95 | versions "mir_optim_test" 96 | dependency "mir-random" version=">=1.0.0-beta" 97 | subConfiguration "mir-lapack" "cblas" 98 | } 99 | 100 | configuration "unittest-blas" { 101 | versions "mir_optim_test" 102 | dependency "mir-random" version=">=1.0.0-beta" 103 | subConfiguration "mir-lapack" "blas" 104 | } 105 | 106 | configuration "unittest-lapack" { 107 | versions "mir_optim_test" 108 | dependency "mir-random" version=">=1.0.0-beta" 109 | subConfiguration "mir-lapack" "lapack" 110 | } 111 | 112 | configuration "unittest-mkl-sequential" { 113 | versions "mir_optim_test" 114 | dependency "mir-random" version=">=1.0.0-beta" 115 | subConfiguration "mir-lapack" "mkl-sequential" 116 | } 117 | -------------------------------------------------------------------------------- /index.d: -------------------------------------------------------------------------------- 1 | Ddoc 2 | 3 | $(H1 Dlang BetterC Nonlinear Optimizers) 4 | 5 | $(P The following table is a quick reference guide for which Mir Optim modules to 6 | use for a given category of functionality.) 7 | 8 | $(BOOKTABLE , 9 | $(TR 10 | $(TH Modules) 11 | $(TH Description) 12 | ) 13 | $(TR 14 | $(TDNW $(MREF mir,optim,boxcqp)) 15 | $(TD Bound Constrained Convex Quadratic Problem Solver) 16 | ) 17 | $(TR 18 | $(TDNW $(MREF mir,optim,least_squares)) 19 | $(TD Nonlinear Least Squares Solver) 20 | ) 21 | ) 22 | 23 | Macros: 24 | TITLE=Mir Optim 25 | WIKI=Mir Optim 26 | DDOC_BLANKLINE= 27 | _= 28 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('mir-optim', 'd', version: '2.0.0', license: 'BSL-1.0') 2 | 3 | description = 'Mir Optim - Nonlinear Optimizers' 4 | 5 | subprojects = [ 6 | 'cblas-d', 7 | 'lapack-d', 8 | 'mir-algorithm', 9 | 'mir-blas', 10 | 'mir-core', 11 | 'mir-lapack', 12 | 'mir-random', 13 | ] 14 | 15 | if target_machine.system() == 'linux' 16 | subprojects += 'mir-linux-kernel' 17 | endif 18 | 19 | has_cpp_headers = false 20 | 21 | sources_list = [ 22 | 'mir/optim/boxcqp', 23 | 'mir/optim/least_squares', 24 | ] 25 | 26 | sources = [] 27 | foreach s : sources_list 28 | sources += 'source/' + s + '.d' 29 | endforeach 30 | 31 | add_project_arguments([ 32 | '-preview=dip1008', 33 | '-lowmem', 34 | ], language: 'd') 35 | 36 | required_deps = [] 37 | 38 | foreach p : subprojects 39 | required_deps += dependency(p, fallback : [p, 'this_dep']) 40 | endforeach 41 | 42 | directories = ['source'] 43 | 44 | if has_cpp_headers 45 | directories += 'include' 46 | endif 47 | 48 | directories = include_directories(directories) 49 | 50 | this_lib = library(meson.project_name(), 51 | sources, 52 | include_directories: directories, 53 | install: true, 54 | version: meson.project_version(), 55 | dependencies: required_deps, 56 | ) 57 | 58 | this_dep = declare_dependency( 59 | link_with: [this_lib], 60 | include_directories: directories, 61 | dependencies: required_deps, 62 | ) 63 | 64 | test_versions = ['mir_optim_test'] 65 | 66 | if has_cpp_headers 67 | install_subdir('include/', 68 | strip_directory :true, 69 | install_dir: 'include/', 70 | ) 71 | endif 72 | 73 | install_subdir('source/', 74 | strip_directory : true, 75 | install_dir: 'include/d/' + meson.project_name(), 76 | ) 77 | 78 | import('pkgconfig').generate(this_lib, 79 | description: description, 80 | subdirs: 'd/' + meson.project_name(), 81 | ) 82 | 83 | mir_optim_dep = this_dep 84 | mir_optim_lib = this_lib 85 | 86 | test_subdirs = [] 87 | -------------------------------------------------------------------------------- /source/mir/optim/boxcqp.d: -------------------------------------------------------------------------------- 1 | /++ 2 | $(H1 Bound Constrained Convex Quadratic Problem Solver) 3 | 4 | Paper: $(HTTP www.cse.uoi.gr/tech_reports/publications/boxcqp.pdf, BOXCQP: AN ALGORITHM FOR BOUND CONSTRAINED CONVEX QUADRATIC PROBLEMS) 5 | 6 | Copyright: Copyright © 2020, Symmetry Investments & Kaleidic Associates Advisory Limited 7 | Authors: Ilya Yaroshenko 8 | +/ 9 | module mir.optim.boxcqp; 10 | 11 | import mir.ndslice.slice: Slice, Canonical; 12 | import lapack: lapackint; 13 | import mir.math.common: fmin, fmax, sqrt, fabs; 14 | 15 | /++ 16 | BOXCQP Exit Status 17 | +/ 18 | enum BoxQPStatus 19 | { 20 | /// 21 | solved, 22 | /// 23 | numericError, 24 | /// 25 | maxIterations, 26 | } 27 | 28 | // ? Not compatible with Intel MKL _posvx 29 | // version = boxcqp_compact; 30 | 31 | extern(C) @safe nothrow @nogc 32 | { 33 | /++ 34 | +/ 35 | @safe pure nothrow @nogc 36 | size_t mir_box_qp_work_length(size_t n) 37 | { 38 | version(boxcqp_compact) 39 | return n ^^ 2 + n * 8; 40 | else 41 | return n ^^ 2 * 2 + n * 8; 42 | } 43 | 44 | /++ 45 | +/ 46 | @safe pure nothrow @nogc 47 | size_t mir_box_qp_iwork_length(size_t n) 48 | { 49 | return n + (n / lapackint.sizeof + (n % lapackint.sizeof != 0)); 50 | } 51 | } 52 | 53 | /++ 54 | BOXCQP Algorithm Settings 55 | +/ 56 | struct BoxQPSettings(T) 57 | if (is(T == float) || is(T == double)) 58 | { 59 | /++ 60 | Relative active constraints tolerance. 61 | +/ 62 | T relTolerance = T.epsilon * 16; 63 | /++ 64 | Absolute active constraints tolerance. 65 | +/ 66 | T absTolerance = T.epsilon * 16; 67 | /++ 68 | Maximal iterations allowed. `0` is used for default value equals to `10 * N + 100`. 69 | +/ 70 | uint maxIterations = 0; 71 | } 72 | 73 | /++ 74 | Solves: 75 | `argmin_x(xPx + qx) : l <= x <= u` 76 | Params: 77 | P = Positive-definite Matrix, NxN 78 | q = Linear component, N 79 | l = Lower bounds in `[-inf, +inf$(RPAREN)`, N 80 | u = Upper bounds in `$(LPAREN)-inf, +inf]`, N 81 | x = solutoin, N 82 | settings = Iteration settings (optional) 83 | +/ 84 | @safe pure nothrow @nogc 85 | BoxQPStatus solveBoxQP(T)( 86 | Slice!(T*, 2, Canonical) P, 87 | Slice!(const(T)*) q, 88 | Slice!(const(T)*) l, 89 | Slice!(const(T)*) u, 90 | Slice!(T*) x, 91 | BoxQPSettings!T settings = BoxQPSettings!T.init, 92 | ) 93 | if (is(T == float) || is(T == double)) 94 | { 95 | import mir.ndslice.allocation: rcslice; 96 | auto n = q.length; 97 | auto work = rcslice!T(mir_box_qp_work_length(n)); 98 | auto iwork = rcslice!lapackint(mir_box_qp_iwork_length(n)); 99 | auto workS = work.lightScope; 100 | auto iworkS = iwork.lightScope; 101 | return solveBoxQP(settings, P, q, l, u, x, false, workS, iworkS, true); 102 | } 103 | 104 | /++ 105 | Solves: 106 | `argmin_x(xPx + qx) : l <= x <= u` 107 | Params: 108 | settings = Iteration settings 109 | P = Positive-definite Matrix (in lower triangular part is store), NxN. 110 | The upper triangular part (and diagonal) of the matrix is used for temporary data and then can be resotored. 111 | Matrix diagonal is always restored. 112 | q = Linear component, N 113 | l = Lower bounds in `[-inf, +inf$(RPAREN)`, N 114 | u = Upper bounds in `$(LPAREN)-inf, +inf]`, N 115 | x = solutoin, N 116 | unconstrainedSolution = 117 | work = workspace, $(LREF mir_box_qp_work_length)(N) 118 | iwork = integer workspace, $(LREF mir_box_qp_iwork_length)(N) 119 | restoreUpperP = (optional) restore upper triangular part of P 120 | +/ 121 | @safe pure nothrow @nogc 122 | BoxQPStatus solveBoxQP(T)( 123 | ref const BoxQPSettings!T settings, 124 | Slice!(T*, 2, Canonical) P, 125 | Slice!(const(T)*) q, 126 | Slice!(const(T)*) l, 127 | Slice!(const(T)*) u, 128 | Slice!(T*) x, 129 | bool unconstrainedSolution, 130 | Slice!(T*) work, 131 | Slice!(lapackint*) iwork, 132 | bool restoreUpperP = true, 133 | ) 134 | if (is(T == float) || is(T == double)) 135 | in { 136 | auto n = q.length; 137 | assert(P.length!0 == n); 138 | assert(P.length!1 == n); 139 | assert(q.length == n); 140 | assert(l.length == n); 141 | assert(u.length == n); 142 | assert(x.length == n); 143 | assert(work.length >= mir_box_qp_work_length(n)); 144 | assert(iwork.length >= mir_box_qp_iwork_length(n)); 145 | } 146 | do { 147 | import mir.blas: dot, copy; 148 | import mir.lapack: posvx; 149 | import mir.math.sum; 150 | import mir.ndslice.slice: sliced; 151 | import mir.ndslice.topology: canonical, diagonal; 152 | 153 | enum Flag : byte 154 | { 155 | l = -1, 156 | s = 0, 157 | u = 1, 158 | } 159 | 160 | auto n = q.length; 161 | 162 | if (n == 0) 163 | return BoxQPStatus.solved; 164 | 165 | auto bwork = iwork[n .. $]; 166 | iwork = iwork[0 .. n]; 167 | 168 | if (!unconstrainedSolution) 169 | { 170 | auto buffer = work; 171 | auto Pdiagonal = buffer[0 .. n]; buffer = buffer[n .. $]; 172 | auto scaling = buffer[0 .. n]; buffer = buffer[n .. $]; 173 | auto b = buffer[0 .. n]; buffer = buffer[n .. $]; 174 | auto lapackWorkSpace = buffer[0 .. n * 3]; buffer = buffer[n * 3 .. $]; 175 | auto F = buffer[0 .. n ^^ 2].sliced(n, n); buffer = buffer[n ^^ 2 .. $]; 176 | 177 | version(boxcqp_compact) 178 | { 179 | foreach(i; 1 .. n) 180 | copy(P[i, 0 .. i], P[0 .. i, i]); 181 | copy(P.diagonal, Pdiagonal); 182 | alias A = P; 183 | } 184 | else 185 | { 186 | auto A = buffer[0 .. n ^^ 2].sliced(n, n); buffer = buffer[n ^^ 2 .. $]; 187 | foreach(i; 0 .. n) 188 | copy(P[i, 0 .. i + 1], A[0 .. i + 1, i]); 189 | } 190 | 191 | b[] = -q; 192 | char equed; 193 | T rcond, ferr, berr; 194 | auto info = posvx('E', 'L', 195 | A.canonical, 196 | F.canonical, 197 | equed, 198 | scaling, 199 | b, 200 | x, 201 | rcond, 202 | ferr, 203 | berr, 204 | lapackWorkSpace, 205 | iwork); 206 | 207 | version(boxcqp_compact) 208 | { 209 | copy(Pdiagonal, P.diagonal); 210 | } 211 | 212 | if (info != 0 && info != n + 1) 213 | return BoxQPStatus.numericError; 214 | } 215 | 216 | foreach (i; 0 .. n) 217 | if (!(l[i] <= x[i] && x[i] <= u[i])) 218 | goto Start; 219 | return BoxQPStatus.solved; 220 | 221 | Start: 222 | auto flags = (()@trusted=>(cast(Flag*)bwork.ptr).sliced(n))(); 223 | 224 | auto maxIterations = cast()settings.maxIterations; 225 | if (!maxIterations) 226 | maxIterations = cast(uint)n * 10 + 100; // fix 227 | 228 | auto la = work[0 .. n]; work = work[n .. $]; 229 | auto mu = work[0 .. n]; work = work[n .. $]; 230 | 231 | la[] = 0; 232 | mu[] = 0; 233 | 234 | MainLoop: foreach (step; 0 .. maxIterations) 235 | { 236 | { 237 | size_t s; 238 | 239 | with(settings) foreach (i; 0 .. n) 240 | { 241 | auto xl = x[i] - l[i]; 242 | auto ux = u[i] - x[i]; 243 | if (xl < 0 || xl < relTolerance + absTolerance * l[i].fabs && la[i] >= 0) 244 | { 245 | flags[i] = Flag.l; 246 | x[i] = l[i]; 247 | mu[i] = 0; 248 | } 249 | else 250 | if (ux < 0 || ux < relTolerance + absTolerance * u[i].fabs && mu[i] >= 0) 251 | { 252 | flags[i] = Flag.u; 253 | x[i] = u[i]; 254 | la[i] = 0; 255 | } 256 | else 257 | { 258 | flags[i] = Flag.s; 259 | iwork[s++] = cast(lapackint)i; 260 | mu[i] = 0; 261 | la[i] = 0; 262 | } 263 | } 264 | 265 | if (s == n) 266 | break; 267 | 268 | { 269 | auto SIWorkspace = iwork[0 .. s]; 270 | auto buffer = work; 271 | auto scaling = buffer[0 .. s]; buffer = buffer[s .. $]; 272 | auto sX = buffer[0 .. s]; buffer = buffer[s .. $]; 273 | auto b = buffer[0 .. s]; buffer = buffer[s .. $]; 274 | auto lapackWorkSpace = buffer[0 .. s * 3]; buffer = buffer[s * 3 .. $]; 275 | auto F = buffer[0 .. s ^^ 2].sliced(s, s); buffer = buffer[s ^^ 2 .. $]; 276 | 277 | version(boxcqp_compact) 278 | auto A = P[0 .. $ - 1, 1 .. $][$ - s .. $, $ - s .. $]; 279 | else 280 | auto A = buffer[0 .. s ^^ 2].sliced(s, s); buffer = buffer[s ^^ 2 .. $]; 281 | 282 | foreach (ii, i; SIWorkspace.field) 283 | { 284 | Summator!(T, Summation.kbn) sum = q[i]; 285 | uint jj; 286 | { 287 | auto Aii = A[0 .. $, ii]; 288 | auto Pi = P[i, 0 .. $]; 289 | foreach (j; 0 .. i) 290 | if (flags[j]) 291 | sum += Pi[j] * (flags[j] < 0 ? l : u)[j]; 292 | else 293 | Aii[jj++] = Pi[j]; 294 | } 295 | { 296 | auto Aii = A[ii, 0 .. $]; 297 | auto Pi = P[0 .. $, i]; 298 | foreach (j; i .. n) 299 | if (flags[j]) 300 | sum += Pi[j] * (flags[j] < 0 ? l : u)[j]; 301 | else 302 | Aii[jj++] = Pi[j]; 303 | } 304 | b[ii] = -sum.sum; 305 | } 306 | 307 | { 308 | char equed; 309 | T rcond, ferr, berr; 310 | auto info = posvx('E', 'L', 311 | A.canonical, 312 | F.canonical, 313 | equed, 314 | scaling, 315 | b, 316 | sX, 317 | rcond, 318 | ferr, 319 | berr, 320 | lapackWorkSpace, 321 | SIWorkspace); 322 | 323 | if (info != 0 && info != s + 1) 324 | return BoxQPStatus.numericError; 325 | } 326 | 327 | size_t ii; 328 | foreach (i; 0 .. n) if (flags[i] == Flag.s) 329 | x[i] = sX[ii++]; 330 | } 331 | } 332 | 333 | foreach (i; 0 .. n) if (flags[i]) 334 | { 335 | auto val = dot!T(P[i, 0 .. i], x[0 .. i]) + dot!T(P[i .. $, i], x[i .. $]) + q[i]; 336 | (flags[i] < 0 ? la : mu)[i] = flags[i] < 0 ? val : -val; 337 | } 338 | 339 | foreach (i; 0 .. n) 340 | { 341 | final switch (flags[i]) 342 | { 343 | case Flag.l: if (la[i] >= 0) continue; continue MainLoop; 344 | case Flag.u: if (mu[i] >= 0) continue; continue MainLoop; 345 | case Flag.s: if (x[i] >= l[i] && x[i] <= u[i]) continue; continue MainLoop; 346 | } 347 | } 348 | 349 | applyBounds(x, l, u); 350 | 351 | version(none) 352 | { 353 | import std.traits, std.meta; 354 | static auto assumePure(T)(T t) 355 | if (isFunctionPointer!T || isDelegate!T) 356 | { 357 | enum attrs = functionAttributes!T | FunctionAttribute.pure_; 358 | return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 359 | } 360 | 361 | import core.stdc.stdio; 362 | (()@trusted => cast(void) assumePure(&printf)("#### BOXCQP iters = %d\n", step + 1))(); 363 | } 364 | 365 | if (restoreUpperP) 366 | { 367 | while(P.length > 1) 368 | { 369 | copy(P[1 .. $, 0], P[0, 1 .. $]); 370 | P.popFront!1; 371 | P.popFront!0; 372 | } 373 | } 374 | 375 | return BoxQPStatus.solved; 376 | } 377 | 378 | return BoxQPStatus.maxIterations; 379 | } 380 | 381 | /// 382 | version(mir_optim_test) 383 | unittest 384 | { 385 | import mir.ndslice; 386 | import mir.algorithm.iteration; 387 | import mir.math.common; 388 | 389 | auto P = [ 390 | [ 2.0, -1, 0], 391 | [-1.0, 2, -1], 392 | [ 0.0, -1, 2], 393 | ].fuse.canonical; 394 | 395 | auto q = [3.0, -7, 5].sliced; 396 | auto l = [-100.0, -2, 1].sliced; 397 | auto u = [100.0, 2, 1].sliced; 398 | auto x = slice!double(q.length); 399 | 400 | solveBoxQP(P, q, l, u, x); 401 | assert(x.equal!approxEqual([-0.5, 2, 1])); 402 | } 403 | 404 | package(mir) void applyBounds(T)(Slice!(T*) x, Slice!(const(T)*) l, Slice!(const(T)*) u) 405 | { 406 | pragma(inline, false); 407 | import mir.math.common: fmin, fmax; 408 | foreach (i; 0 .. x.length) 409 | x[i] = x[i].fmin(u[i]).fmax(l[i]); 410 | } 411 | -------------------------------------------------------------------------------- /source/mir/optim/fit_splie.d: -------------------------------------------------------------------------------- 1 | module mir.optim.fit_spline; 2 | 3 | import mir.optim.least_squares; 4 | import mir.interpolate.spline; 5 | 6 | /// 7 | struct FitSplineResult(T) 8 | { 9 | /// 10 | LeastSquaresResult!T leastSquaresResult; 11 | /// 12 | Spline!T spline; 13 | } 14 | 15 | /++ 16 | Params: 17 | settings = LMA settings 18 | points = points to fit 19 | x = fixed X values of the spline 20 | l = lower bounds for spline(X) values 21 | u = upper bounds for spline(X) values 22 | lambda = coefficient for the normalized integral of the square of the second derivative 23 | configuration = spline configuration (optional) 24 | Returns: $(FitSplineResult) 25 | +/ 26 | FitSplineResult!T fitSpline(alias d = "a - b", T)( 27 | scope const ref LeastSquaresSettings!T settings, 28 | scope const T[2][] points, 29 | scope const T[] x, 30 | scope const T[] l, 31 | scope const T[] u, 32 | const T lambda = 0, 33 | SplineConfiguration!T configuration = SplineConfiguration!T(), 34 | ) @nogc @trusted pure 35 | if ((is(T == float) || is(T == double))) 36 | in (lambda >= 0) 37 | { 38 | pragma(inline, false); 39 | 40 | import mir.functional: naryFun; 41 | import mir.math.common: sqrt, fabs; 42 | import mir.ndslice.slice: sliced, Slice; 43 | import mir.rc.array; 44 | 45 | if (points.length < x.length && lambda == 0) 46 | { 47 | static immutable exc = new Exception("fitSpline: points.length has to be greater or equal x.length when lambda is 0.0"); 48 | throw cast()exc; 49 | } 50 | 51 | FitSplineResult!T ret; 52 | 53 | ret.spline = x.rcarray!(immutable T).moveToSlice.Spline!T; 54 | 55 | auto y = x.length.RCArray!T; 56 | y[][] = 0; 57 | 58 | scope f = delegate(scope Slice!(const (T)*) splineY, scope Slice!(T*) y) 59 | { 60 | assert(y.length == points.length + !lambda); 61 | ret.spline._values = splineY; 62 | with(configuration) 63 | ret.spline._computeDerivatives(kind, param, leftBoundary, rightBoundary); 64 | foreach (i, ref point; points) 65 | y[i] = naryFun!d(ret.spline(point[0]), point[1]); 66 | 67 | T integral = 0; 68 | if (lambda) 69 | { 70 | T ld = ret.spline.withTwoDerivatives(x[0])[1]; 71 | foreach (i; 1 .. x.length) 72 | { 73 | T rd = ret.spline.withTwoDerivatives(x[i])[1]; 74 | integral += (rd * rd + rd * ld + ld * ld) * (x[i] - x[i - 1]); 75 | ld = rd; 76 | } 77 | assert(integral >= 0); 78 | } 79 | y[$ - 1] = sqrt(integral * lambda * points.length / (3 * x.length)); 80 | }; 81 | 82 | ret.leastSquaresResult = optimize!(f)(settings, points.length + !lambda, y[].sliced, l[].sliced, u[].sliced); 83 | 84 | return ret; 85 | } 86 | 87 | @safe pure 88 | unittest 89 | { 90 | import mir.test; 91 | 92 | LeastSquaresSettings!double settings; 93 | 94 | auto x = [-1.0, 2, 4, 5, 8, 10, 12, 15, 19, 22]; 95 | 96 | auto y = [17.0, 0, 16, 4, 10, 15, 19, 5, 18, 6]; 97 | 98 | auto l = new double[x.length]; 99 | l[] = -double.infinity; 100 | 101 | auto u = new double[x.length]; 102 | u[] = +double.infinity; 103 | import mir.stdio; 104 | 105 | double[2][] points = [ 106 | [x[0] + 0.5, -0.68361541], 107 | [x[1] + 0.5, 7.28568719], 108 | [x[2] + 0.5, 10.490694 ], 109 | [x[3] + 0.5, 0.36192032], 110 | [x[4] + 0.5, 11.91572713], 111 | [x[5] + 0.5, 16.44546433], 112 | [x[6] + 0.5, 17.66699525], 113 | [x[7] + 0.5, 4.52730869], 114 | [x[8] + 0.5, 19.22825394], 115 | [x[9] + 0.5, -2.3242592 ], 116 | ]; 117 | 118 | auto result = settings.fitSpline(points, x, l, u, 0); 119 | 120 | foreach (i; 0 .. x.length) 121 | result.spline(x[i]).shouldApprox == y[i]; 122 | 123 | result = settings.fitSpline(points, x, l, u, 1e-3); 124 | 125 | // this case sensetive for numeric noise 126 | y = [ 127 | 15.898984945597563, 128 | 0.44978154774119194, 129 | 15.579636654078188, 130 | 4.028312405287987, 131 | 9.945895290402778, 132 | 15.07778815727665, 133 | 18.877926155854535, 134 | 5.348699237978274, 135 | 16.898507797404278, 136 | 22.024920998359942, 137 | ]; 138 | 139 | foreach (i; 0 .. x.length) 140 | result.spline(x[i]).shouldApprox == y[i]; 141 | } 142 | -------------------------------------------------------------------------------- /source/mir/optim/least_squares.d: -------------------------------------------------------------------------------- 1 | /++ 2 | $(H1 Nonlinear Least Squares Solver) 3 | 4 | Copyright: Copyright © 2018, Symmetry Investments & Kaleidic Associates Advisory Limited 5 | Authors: Ilya Yaroshenko 6 | 7 | Macros: 8 | NDSLICE = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) 9 | T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 10 | +/ 11 | module mir.optim.least_squares; 12 | 13 | import mir.ndslice.slice: Slice, SliceKind, Contiguous, sliced; 14 | import std.meta; 15 | import std.traits; 16 | import lapack: lapackint; 17 | 18 | /++ 19 | +/ 20 | enum LeastSquaresStatus 21 | { 22 | /// Maximum number of iterations reached 23 | maxIterations = -1, 24 | /// The algorithm cann't improve the solution 25 | furtherImprovement, 26 | /// Stationary values 27 | xConverged, 28 | /// Stationary gradient 29 | gConverged, 30 | /// Good (small) residual 31 | fConverged, 32 | /// 33 | badBounds = -32, 34 | /// 35 | badGuess, 36 | /// 37 | badMinStepQuality, 38 | /// 39 | badGoodStepQuality, 40 | /// 41 | badStepQuality, 42 | /// 43 | badLambdaParams, 44 | /// 45 | numericError, 46 | } 47 | 48 | version(D_Exceptions) 49 | { 50 | /+ 51 | Exception for $(LREF optimize). 52 | +/ 53 | private static immutable leastSquaresException_maxIterations = new Exception("mir-optim Least Squares: " ~ LeastSquaresStatus.maxIterations.leastSquaresStatusString); 54 | private static immutable leastSquaresException_badBounds = new Exception("mir-optim Least Squares: " ~ LeastSquaresStatus.badBounds.leastSquaresStatusString); 55 | private static immutable leastSquaresException_badGuess = new Exception("mir-optim Least Squares: " ~ LeastSquaresStatus.badGuess.leastSquaresStatusString); 56 | private static immutable leastSquaresException_badMinStepQuality = new Exception("mir-optim Least Squares: " ~ LeastSquaresStatus.badMinStepQuality.leastSquaresStatusString); 57 | private static immutable leastSquaresException_badGoodStepQuality = new Exception("mir-optim Least Squares: " ~ LeastSquaresStatus.badGoodStepQuality.leastSquaresStatusString); 58 | private static immutable leastSquaresException_badStepQuality = new Exception("mir-optim Least Squares: " ~ LeastSquaresStatus.badStepQuality.leastSquaresStatusString); 59 | private static immutable leastSquaresException_badLambdaParams = new Exception("mir-optim Least Squares: " ~ LeastSquaresStatus.badLambdaParams.leastSquaresStatusString); 60 | private static immutable leastSquaresException_numericError = new Exception("mir-optim Least Squares: " ~ LeastSquaresStatus.numericError.leastSquaresStatusString); 61 | private static immutable leastSquaresExceptions = [ 62 | leastSquaresException_badBounds, 63 | leastSquaresException_badGuess, 64 | leastSquaresException_badMinStepQuality, 65 | leastSquaresException_badGoodStepQuality, 66 | leastSquaresException_badStepQuality, 67 | leastSquaresException_badLambdaParams, 68 | leastSquaresException_numericError, 69 | ]; 70 | } 71 | 72 | /// Delegates for low level D API. 73 | alias LeastSquaresFunction(T) = void delegate(Slice!(const(T)*) x, Slice!(T*) y) @safe nothrow @nogc pure; 74 | /// ditto 75 | alias LeastSquaresJacobian(T) = void delegate(Slice!(const(T)*) x, Slice!(T*, 2) J) @safe nothrow @nogc pure; 76 | 77 | /// Delegates for low level C API. 78 | alias LeastSquaresFunctionBetterC(T) = extern(C) void function(scope void* context, size_t m, size_t n, const(T)* x, T* y) @system nothrow @nogc pure; 79 | /// 80 | alias LeastSquaresJacobianBetterC(T) = extern(C) void function(scope void* context, size_t m, size_t n, const(T)* x, T* J) @system nothrow @nogc pure; 81 | 82 | /++ 83 | Least-Squares iteration settings. 84 | +/ 85 | struct LeastSquaresSettings(T) 86 | if (is(T == double) || is(T == float)) 87 | { 88 | import mir.optim.boxcqp; 89 | import mir.math.common: sqrt; 90 | import mir.math.constant: GoldenRatio; 91 | import lapack: lapackint; 92 | 93 | /// Maximum number of iterations 94 | uint maxIterations = 1000; 95 | /// Maximum jacobian model age (0 for default selection) 96 | uint maxAge; 97 | /// epsilon for finite difference Jacobian approximation 98 | T jacobianEpsilon = T(2) ^^ ((1 - T.mant_dig) / 2); 99 | /// Absolute tolerance for step size, L2 norm 100 | T absTolerance = T.epsilon; 101 | /// Relative tolerance for step size, L2 norm 102 | T relTolerance = 0; 103 | /// Absolute tolerance for gradient, L-inf norm 104 | T gradTolerance = T.epsilon; 105 | /// The algorithm stops iteration when the residual value is less or equal to `maxGoodResidual`. 106 | T maxGoodResidual = T.epsilon ^^ 2; 107 | /// maximum norm of iteration step 108 | T maxStep = T.max.sqrt / 16; 109 | /// minimum trust region radius 110 | T maxLambda = T.max / 16; 111 | /// maximum trust region radius 112 | T minLambda = T.min_normal * 16; 113 | /// for steps below this quality, the trust region is shrinked 114 | T minStepQuality = 0.1; 115 | /// for steps above this quality, the trust region is expanded 116 | T goodStepQuality = 0.5; 117 | /// `lambda` is multiplied by this factor after step below min quality 118 | T lambdaIncrease = 2; 119 | /// `lambda` is multiplied by this factor after good quality steps 120 | T lambdaDecrease = 1 / (GoldenRatio * 2); 121 | /// Bound constrained convex quadratic problem settings 122 | BoxQPSettings!T qpSettings; 123 | } 124 | 125 | /++ 126 | Least-Squares results. 127 | +/ 128 | struct LeastSquaresResult(T) 129 | if (is(T == double) || is(T == float)) 130 | { 131 | /// Computation status 132 | LeastSquaresStatus status = LeastSquaresStatus.numericError; 133 | /// Successful step count 134 | uint iterations; 135 | /// Number of the function calls 136 | uint fCalls; 137 | /// Number of the Jacobian calls 138 | uint gCalls; 139 | /// Final residual 140 | T residual = T.infinity; 141 | /// LMA variable for (inverse of) initial trust region radius 142 | T lambda = 0; 143 | } 144 | 145 | /++ 146 | High level D API for Levenberg-Marquardt Algorithm. 147 | 148 | Computes the argmin over x of `sum_i(f(x_i)^2)` using the Levenberg-Marquardt 149 | algorithm, and an estimate of the Jacobian of `f` at x. 150 | 151 | The function `f` should take an input vector of length `n`, and fill an output 152 | vector of length `m`. 153 | 154 | The function `g` is the Jacobian of `f`, and should fill a row-major `m x n` matrix. 155 | 156 | Throws: $(LREF LeastSquaresException) 157 | Params: 158 | f = `n -> m` function 159 | g = `m × n` Jacobian (optional) 160 | tm = thread manager for finite difference jacobian approximation in case of g is null (optional) 161 | settings = Levenberg-Marquardt data structure 162 | taskPool = task Pool with `.parallel` method for finite difference jacobian approximation in case of g is null (optional) 163 | See_also: $(LREF optimizeLeastSquares) 164 | +/ 165 | LeastSquaresResult!T optimize(alias f, alias g = null, alias tm = null, T)( 166 | scope const ref LeastSquaresSettings!T settings, 167 | size_t m, 168 | Slice!(T*) x, 169 | Slice!(const(T)*) l, 170 | Slice!(const(T)*) u, 171 | ) 172 | if ((is(T == float) || is(T == double))) 173 | { 174 | auto ret = optimizeLeastSquares!(f, g, tm, T)(settings, m, x, l, u); 175 | if (ret.status == -1) 176 | () @trusted { throw cast()leastSquaresException_maxIterations; }(); 177 | else 178 | if (ret.status < -1) 179 | () @trusted { throw cast()leastSquaresExceptions[ret.status + 32]; }(); 180 | return ret; 181 | } 182 | 183 | /// ditto 184 | LeastSquaresResult!T optimize(alias f, TaskPool, T)( 185 | scope const ref LeastSquaresSettings!T settings, 186 | size_t m, 187 | Slice!(T*) x, 188 | Slice!(const(T)*) l, 189 | Slice!(const(T)*) u, 190 | TaskPool taskPool) 191 | if (is(T == float) || is(T == double)) 192 | { 193 | auto tm = delegate(uint count, scope LeastSquaresTask task) 194 | { 195 | version(all) 196 | { 197 | import mir.ndslice.topology: iota; 198 | foreach(i; taskPool.parallel(count.iota!uint)) 199 | task(cast(uint)taskPool.size, cast(uint)(taskPool.size <= 1 ? 0 : taskPool.workerIndex - 1), i); 200 | } 201 | else // for debug 202 | { 203 | foreach(i; 0 .. count) 204 | task(1, 0, i); 205 | } 206 | }; 207 | 208 | auto ret = optimizeLeastSquares!(f, null, tm, T)(settings, m, x, l, u); 209 | if (ret.status == -1) 210 | () @trusted { throw cast()leastSquaresException_maxIterations; }(); 211 | else 212 | if (ret.status < -1) 213 | () @trusted { throw cast()leastSquaresExceptions[ret.status + 32]; }(); 214 | return ret; 215 | } 216 | 217 | /// With Jacobian 218 | version(mir_optim_test) 219 | @safe unittest 220 | { 221 | import mir.ndslice.allocation: slice; 222 | import mir.ndslice.slice: sliced; 223 | import mir.blas: nrm2; 224 | 225 | LeastSquaresSettings!double settings; 226 | auto x = [100.0, 100].sliced; 227 | auto l = x.shape.slice(-double.infinity); 228 | auto u = x.shape.slice(+double.infinity); 229 | optimize!( 230 | (x, y) 231 | { 232 | y[0] = x[0]; 233 | y[1] = 2 - x[1]; 234 | }, 235 | (x, J) 236 | { 237 | J[0, 0] = 1; 238 | J[0, 1] = 0; 239 | J[1, 0] = 0; 240 | J[1, 1] = -1; 241 | }, 242 | )(settings, 2, x, l, u); 243 | 244 | assert(nrm2((x - [0, 2].sliced).slice) < 1e-8); 245 | } 246 | 247 | /// Using Jacobian finite difference approximation computed using in multiple threads. 248 | version(mir_optim_test) 249 | unittest 250 | { 251 | import mir.ndslice.allocation: slice; 252 | import mir.ndslice.slice: sliced; 253 | import mir.blas: nrm2; 254 | import std.parallelism: taskPool; 255 | 256 | LeastSquaresSettings!double settings; 257 | auto x = [-1.2, 1].sliced; 258 | auto l = x.shape.slice(-double.infinity); 259 | auto u = x.shape.slice(+double.infinity); 260 | settings.optimize!( 261 | (x, y) // Rosenbrock function 262 | { 263 | y[0] = 10 * (x[1] - x[0]^^2); 264 | y[1] = 1 - x[0]; 265 | }, 266 | )(2, x, l, u, taskPool); 267 | 268 | // import std.stdio; 269 | // writeln(settings); 270 | // writeln(x); 271 | 272 | assert(nrm2((x - [1, 1].sliced).slice) < 1e-6); 273 | } 274 | 275 | /// Rosenbrock 276 | version(mir_optim_test) 277 | @safe unittest 278 | { 279 | import mir.algorithm.iteration: all; 280 | import mir.ndslice.allocation: slice; 281 | import mir.ndslice.slice: Slice, sliced; 282 | import mir.blas: nrm2; 283 | 284 | LeastSquaresSettings!double settings; 285 | auto x = [-1.2, 1].sliced; 286 | auto l = x.shape.slice(-double.infinity); 287 | auto u = x.shape.slice(+double.infinity); 288 | 289 | alias rosenbrockRes = (x, y) 290 | { 291 | y[0] = 10 * (x[1] - x[0]^^2); 292 | y[1] = 1 - x[0]; 293 | }; 294 | 295 | alias rosenbrockJac = (x, J) 296 | { 297 | J[0, 0] = -20 * x[0]; 298 | J[0, 1] = 10; 299 | J[1, 0] = -1; 300 | J[1, 1] = 0; 301 | }; 302 | 303 | static class FFF 304 | { 305 | static auto opCall(Slice!(const(double)*) x, Slice!(double*, 2) J) 306 | { 307 | rosenbrockJac(x, J); 308 | } 309 | } 310 | 311 | settings.optimize!(rosenbrockRes, FFF)(2, x, l, u); 312 | 313 | // import std.stdio; 314 | 315 | // writeln(settings.iterations, " ", settings.fCalls, " ", settings.gCalls, " x = ", x); 316 | 317 | assert(nrm2((x - [1, 1].sliced).slice) < 1e-8); 318 | 319 | ///// 320 | 321 | settings = settings.init; 322 | x[] = [150.0, 150.0]; 323 | l[] = [10.0, 10.0]; 324 | u[] = [200.0, 200.0]; 325 | 326 | settings.optimize!(rosenbrockRes, rosenbrockJac)(2, x, l, u); 327 | 328 | // writeln(settings.iterations, " ", settings.fCalls, " ", settings.gCalls, " ", x); 329 | assert(nrm2((x - [10, 100].sliced).slice) < 1e-5); 330 | assert(x.all!"a >= 10"); 331 | } 332 | 333 | /// 334 | version(mir_optim_test) 335 | @safe unittest 336 | { 337 | import mir.blas: nrm2; 338 | import mir.math.common; 339 | import mir.ndslice.allocation: slice; 340 | import mir.ndslice.topology: linspace, map; 341 | import mir.ndslice.slice: sliced; 342 | import mir.random; 343 | import mir.random.algorithm; 344 | import mir.random.variable; 345 | import std.parallelism: taskPool; 346 | 347 | alias model = (x, p) => p[0] * map!exp(-x * p[1]); 348 | 349 | auto p = [1.0, 2.0]; 350 | 351 | auto xdata = [20].linspace([0.0, 10.0]); 352 | auto rng = Random(12345); 353 | auto ydata = slice(model(xdata, p) + 0.01 * rng.randomSlice(normalVar, xdata.shape)); 354 | 355 | auto x = [0.5, 0.5].sliced; 356 | auto l = x.shape.slice(-double.infinity); 357 | auto u = x.shape.slice(+double.infinity); 358 | 359 | LeastSquaresSettings!double settings; 360 | settings.optimize!((p, y) => y[] = model(xdata, p) - ydata)(ydata.length, x, l, u); 361 | 362 | assert((x - [1.0, 2.0].sliced).slice.nrm2 < 0.05); 363 | } 364 | 365 | /// 366 | version(mir_optim_test) 367 | @safe pure unittest 368 | { 369 | import mir.algorithm.iteration: all; 370 | import mir.ndslice.allocation: slice; 371 | import mir.ndslice.topology: map, repeat, iota; 372 | import mir.ndslice.slice: sliced; 373 | import mir.random; 374 | import mir.random.variable; 375 | import mir.random.algorithm; 376 | import mir.math.common; 377 | 378 | alias model = (x, p) => p[0] * map!exp(-x / p[1]) + p[2]; 379 | 380 | auto xdata = iota([100], 1); 381 | auto rng = Random(12345); 382 | auto ydata = slice(model(xdata, [10.0, 10.0, 10.0]) + 0.1 * rng.randomSlice(normalVar, xdata.shape)); 383 | 384 | LeastSquaresSettings!double settings; 385 | 386 | auto x = [15.0, 15.0, 15.0].sliced; 387 | auto l = [5.0, 11.0, 5.0].sliced; 388 | auto u = x.shape.slice(+double.infinity); 389 | 390 | settings.optimize!((p, y) => y[] = model(xdata, p) - ydata) 391 | (ydata.length, x, l, u); 392 | 393 | assert(all!"a >= b"(x, l)); 394 | 395 | // import std.stdio; 396 | 397 | // writeln(x); 398 | // writeln(settings.iterations, " ", settings.fCalls, " ", settings.gCalls); 399 | 400 | settings = settings.init; 401 | x[] = [5.0, 5.0, 5.0]; 402 | l[] = -double.infinity; 403 | u[] = [15.0, 9.0, 15.0]; 404 | settings.optimize!((p, y) => y[] = model(xdata, p) - ydata) 405 | (ydata.length, x, l , u); 406 | 407 | assert(x.all!"a <= b"(u)); 408 | 409 | // writeln(x); 410 | // writeln(settings.iterations, " ", settings.fCalls, " ", settings.gCalls); 411 | } 412 | 413 | /// 414 | version(mir_optim_test) 415 | @safe pure unittest 416 | { 417 | import mir.blas: nrm2; 418 | import mir.math.common: sqrt; 419 | import mir.ndslice.allocation: slice; 420 | import mir.ndslice.slice: sliced; 421 | 422 | LeastSquaresSettings!double settings; 423 | auto x = [0.001, 0.0001].sliced; 424 | auto l = [-0.5, -0.5].sliced; 425 | auto u = [0.5, 0.5].sliced; 426 | settings.optimize!( 427 | (x, y) 428 | { 429 | y[0] = sqrt(1 - (x[0] ^^ 2 + x[1] ^^ 2)); 430 | }, 431 | )(1, x, l, u); 432 | 433 | assert(nrm2((x - u).slice) < 1e-8); 434 | } 435 | 436 | /++ 437 | High level nothtow D API for Levenberg-Marquardt Algorithm. 438 | 439 | Computes the argmin over x of `sum_i(f(x_i)^2)` using the Least-Squares 440 | algorithm, and an estimate of the Jacobian of `f` at x. 441 | 442 | The function `f` should take an input vector of length `n`, and fill an output 443 | vector of length `m`. 444 | 445 | The function `g` is the Jacobian of `f`, and should fill a row-major `m x n` matrix. 446 | 447 | Returns: optimization status. 448 | Params: 449 | f = `n -> m` function, the `y` vector is zero initialized before f is called 450 | g = `m × n` Jacobian (optional), the `J` matrix is zero initialized before g is called 451 | tm = thread manager for finite difference jacobian approximation in case of g is null (optional) 452 | settings = Levenberg-Marquardt data structure 453 | m = length (dimension) of `y = f(x)` 454 | x = initial (in) and final (out) X value 455 | l = lower X bound 456 | u = upper X bound 457 | See_also: $(LREF optimize) 458 | +/ 459 | LeastSquaresResult!T optimizeLeastSquares(alias f, alias g = null, alias tm = null, T)( 460 | scope const ref LeastSquaresSettings!T settings, 461 | size_t m, 462 | Slice!(T*) x, 463 | Slice!(const(T)*) l, 464 | Slice!(const(T)*) u, 465 | ) 466 | { 467 | scope fInst = delegate(Slice!(const(T)*) x, Slice!(T*) y) 468 | { 469 | y[] = 0; 470 | f(x, y); 471 | }; 472 | if (false) 473 | { 474 | fInst(x, x); 475 | } 476 | static if (is(typeof(g) == typeof(null))) 477 | enum LeastSquaresJacobian!T gInst = null; 478 | else 479 | { 480 | scope gInst = delegate(Slice!(const(T)*) x, Slice!(T*, 2) J) 481 | { 482 | J[] = 0; 483 | g(x, J); 484 | }; 485 | static if (isNullableFunction!(g)) 486 | if (!g) 487 | gInst = null; 488 | if (false) 489 | { 490 | Slice!(T*, 2) J; 491 | gInst(x, J); 492 | } 493 | } 494 | 495 | static if (is(typeof(tm) == typeof(null))) 496 | enum LeastSquaresThreadManager tmInst = null; 497 | else 498 | { 499 | scope tmInst = delegate( 500 | uint count, 501 | scope LeastSquaresTask task) 502 | { 503 | tm(count, task); 504 | }; 505 | static if (isNullableFunction!(tm)) 506 | if (!tm) 507 | tmInst = null; 508 | if (false) with(settings) 509 | tmInst(0, null); 510 | } 511 | 512 | auto n = x.length; 513 | import mir.ndslice.allocation: rcslice; 514 | auto work = rcslice!T(mir_least_squares_work_length(m, n)); 515 | auto iwork = rcslice!lapackint(mir_least_squares_iwork_length(m, n)); 516 | auto workS = work.lightScope; 517 | auto iworkS = iwork.lightScope; 518 | return optimizeLeastSquares!T(settings, m, x, l, u, workS, iworkS, fInst.trustedAllAttr, gInst.trustedAllAttr, tmInst.trustedAllAttr); 519 | } 520 | 521 | /++ 522 | Status string for low (extern) and middle (nothrow) levels D API. 523 | Params: 524 | st = optimization status 525 | Returns: description for $(LeastSquaresStatus)furtherImprovement 526 | +/ 527 | pragma(inline, false) 528 | string leastSquaresStatusString(LeastSquaresStatus st) @safe pure nothrow @nogc 529 | { 530 | final switch(st) with(LeastSquaresStatus) 531 | { 532 | case furtherImprovement: 533 | return "The algorithm cann't improve the solution"; 534 | case maxIterations: 535 | return "Maximum number of iterations reached"; 536 | case xConverged: 537 | return "X converged"; 538 | case gConverged: 539 | return "Jacobian converged"; 540 | case fConverged: 541 | return "Residual is small enough"; 542 | case badBounds: 543 | return "Initial guess must be within bounds."; 544 | case badGuess: 545 | return "Initial guess must be an array of finite numbers."; 546 | case badMinStepQuality: 547 | return "0 <= minStepQuality < 1 must hold."; 548 | case badGoodStepQuality: 549 | return "0 < goodStepQuality <= 1 must hold."; 550 | case badStepQuality: 551 | return "minStepQuality < goodStepQuality must hold."; 552 | case badLambdaParams: 553 | return "1 <= lambdaIncrease && lambdaIncrease <= T.max.sqrt and T.min_normal.sqrt <= lambdaDecrease && lambdaDecrease <= 1 must hold."; 554 | case numericError: 555 | return "Numeric Error"; 556 | } 557 | } 558 | 559 | /// 560 | alias LeastSquaresTask = void delegate( 561 | uint totalThreads, 562 | uint threadId, 563 | uint i) 564 | @safe nothrow @nogc pure; 565 | 566 | /// 567 | alias LeastSquaresTaskBetterC = extern(C) void function( 568 | scope const LeastSquaresTask, 569 | uint totalThreads, 570 | uint threadId, 571 | uint i) 572 | @safe nothrow @nogc pure; 573 | 574 | /// Thread manager delegate type for low level `extern(D)` API. 575 | alias LeastSquaresThreadManager = void delegate( 576 | uint count, 577 | scope LeastSquaresTask task) 578 | @safe nothrow @nogc pure; 579 | 580 | /++ 581 | Low level `extern(D)` instatiation. 582 | Params: 583 | settings = Levenberg-Marquardt data structure 584 | m = length (dimension) of `y = f(x)` 585 | x = initial (in) and final (out) X value 586 | l = lower X bound 587 | u = upper X bound 588 | f = `n -> m` function 589 | g = `m × n` Jacobian (optional) 590 | tm = thread manager for finite difference jacobian approximation in case of g is null (optional) 591 | work = floating point workspace length of at least $(LREF mir_least_squares_work_length) 592 | iwork = floating point workspace length of at least $(LREF mir_least_squares_iwork_length) 593 | +/ 594 | pragma(inline, false) 595 | LeastSquaresResult!double optimizeLeastSquaresD 596 | ( 597 | scope const ref LeastSquaresSettings!double settings, 598 | size_t m, 599 | Slice!(double*) x, 600 | Slice!(const(double)*) l, 601 | Slice!(const(double)*) u, 602 | Slice!(double*) work, 603 | Slice!(lapackint*) iwork, 604 | scope LeastSquaresFunction!double f, 605 | scope LeastSquaresJacobian!double g = null, 606 | scope LeastSquaresThreadManager tm = null, 607 | ) @trusted nothrow @nogc pure 608 | { 609 | return optimizeLeastSquaresImplGeneric!double(settings, m, x, l, u, work, iwork, f, g, tm); 610 | } 611 | 612 | 613 | /// ditto 614 | pragma(inline, false) 615 | LeastSquaresResult!float optimizeLeastSquaresS 616 | ( 617 | scope const ref LeastSquaresSettings!float settings, 618 | size_t m, 619 | Slice!(float*) x, 620 | Slice!(const(float)*) l, 621 | Slice!(const(float)*) u, 622 | Slice!(float*) work, 623 | Slice!(lapackint*) iwork, 624 | scope LeastSquaresFunction!float f, 625 | scope LeastSquaresJacobian!float g = null, 626 | scope LeastSquaresThreadManager tm = null, 627 | ) @trusted nothrow @nogc pure 628 | { 629 | return optimizeLeastSquaresImplGeneric!float(settings, 2, x, l, u, work, iwork, f, g, tm); 630 | } 631 | 632 | /// ditto 633 | alias optimizeLeastSquares(T : double) = optimizeLeastSquaresD; 634 | /// ditto 635 | alias optimizeLeastSquares(T : float) = optimizeLeastSquaresS; 636 | 637 | extern(C) @safe nothrow @nogc 638 | { 639 | /++ 640 | +/ 641 | @safe pure nothrow @nogc 642 | size_t mir_least_squares_work_length(size_t m, size_t n) 643 | { 644 | import mir.optim.boxcqp: mir_box_qp_work_length; 645 | return mir_box_qp_work_length(n) + n * 5 + n ^^ 2 + n * m + m * 2; 646 | } 647 | 648 | /++ 649 | +/ 650 | @safe pure nothrow @nogc 651 | size_t mir_least_squares_iwork_length(size_t m, size_t n) 652 | { 653 | import mir.utility: max; 654 | import mir.optim.boxcqp: mir_box_qp_iwork_length; 655 | return max(mir_box_qp_iwork_length(n), n); 656 | } 657 | 658 | /++ 659 | Status string for extern(C) API. 660 | Params: 661 | st = optimization status 662 | Returns: description for $(LeastSquaresStatus) 663 | +/ 664 | extern(C) 665 | pragma(inline, false) 666 | immutable(char)* mir_least_squares_status_string(LeastSquaresStatus st) @trusted pure nothrow @nogc 667 | { 668 | return st.leastSquaresStatusString.ptr; 669 | } 670 | 671 | /// Thread manager function type for low level `extern(C)` API. 672 | alias LeastSquaresThreadManagerBetterC = 673 | extern(C) void function( 674 | scope void* context, 675 | uint count, 676 | scope const LeastSquaresTask taskContext, 677 | scope LeastSquaresTaskBetterC task) 678 | @system nothrow @nogc pure; 679 | 680 | /++ 681 | Low level `extern(C)` wrapper instatiation. 682 | Params: 683 | settings = Levenberg-Marquardt data structure 684 | fContext = context for the function 685 | f = `n -> m` function 686 | gContext = context for the Jacobian (optional) 687 | g = `m × n` Jacobian (optional) 688 | tm = thread manager for finite difference jacobian approximation in case of g is null (optional) 689 | m = length (dimension) of `y = f(x)` 690 | n = length (dimension) of X 691 | x = initial (in) and final (out) X value 692 | l = lower X bound 693 | u = upper X bound 694 | f = `n -> m` function 695 | fContext = `f` context 696 | g = `m × n` Jacobian (optional) 697 | gContext = `g` context 698 | tm = thread manager for finite difference jacobian approximation in case of g is null (optional) 699 | tmContext = `tm` context 700 | work = floating point workspace length of at least $(LREF mir_least_squares_work_length) 701 | iwork = floating point workspace length of at least $(LREF mir_least_squares_iwork_length) 702 | +/ 703 | extern(C) 704 | pragma(inline, false) 705 | LeastSquaresResult!double mir_optimize_least_squares_d 706 | ( 707 | scope const ref LeastSquaresSettings!double settings, 708 | size_t m, 709 | size_t n, 710 | double* x, 711 | const(double)* l, 712 | const(double)* u, 713 | Slice!(double*) work, 714 | Slice!(lapackint*) iwork, 715 | scope void* fContext, 716 | scope LeastSquaresFunctionBetterC!double f, 717 | scope void* gContext = null, 718 | scope LeastSquaresJacobianBetterC!double g = null, 719 | scope void* tmContext = null, 720 | scope LeastSquaresThreadManagerBetterC tm = null, 721 | ) @system nothrow @nogc pure 722 | { 723 | return optimizeLeastSquaresImplGenericBetterC!double(settings, m, n, x, l, u, work, iwork, fContext, f, gContext, g, tmContext, tm); 724 | } 725 | 726 | /// ditto 727 | extern(C) 728 | pragma(inline, false) 729 | LeastSquaresResult!float mir_optimize_least_squares_s 730 | ( 731 | scope const ref LeastSquaresSettings!float settings, 732 | size_t m, 733 | size_t n, 734 | float* x, 735 | const(float)* l, 736 | const(float)* u, 737 | Slice!(float*) work, 738 | Slice!(lapackint*) iwork, 739 | scope void* fContext, 740 | scope LeastSquaresFunctionBetterC!float f, 741 | scope void* gContext = null, 742 | scope LeastSquaresJacobianBetterC!float g = null, 743 | scope void* tmContext = null, 744 | scope LeastSquaresThreadManagerBetterC tm = null, 745 | ) @system nothrow @nogc pure 746 | { 747 | return optimizeLeastSquaresImplGenericBetterC!float(settings, m, n, x, l, u, work, iwork, fContext, f, gContext, g, tmContext, tm); 748 | } 749 | 750 | /// ditto 751 | alias mir_optimize_least_squares(T : double) = mir_optimize_least_squares_d; 752 | 753 | /// ditto 754 | alias mir_optimize_least_squares(T : float) = mir_optimize_least_squares_s; 755 | 756 | /++ 757 | Initialize LM data structure with default params for iteration. 758 | Params: 759 | settings = Levenberg-Marquart data structure 760 | +/ 761 | void mir_least_squares_init_d(ref LeastSquaresSettings!double settings) pure 762 | { 763 | settings = settings.init; 764 | } 765 | 766 | /// ditto 767 | void mir_least_squares_init_s(ref LeastSquaresSettings!float settings) pure 768 | { 769 | settings = settings.init; 770 | } 771 | 772 | /// ditto 773 | alias mir_least_squares_init(T : double) = mir_least_squares_init_d; 774 | 775 | /// ditto 776 | alias mir_least_squares_init(T : float) = mir_least_squares_init_s; 777 | 778 | /++ 779 | Resets all counters and flags, fills `x`, `y`, `upper`, `lower`, vecors with default values. 780 | Params: 781 | settings = Levenberg-Marquart data structure 782 | +/ 783 | void mir_least_squares_reset_d(ref LeastSquaresSettings!double settings) pure 784 | { 785 | settings = settings.init; 786 | } 787 | 788 | /// ditto 789 | void mir_least_squares_reset_s(ref LeastSquaresSettings!float settings) pure 790 | { 791 | settings = settings.init; 792 | } 793 | 794 | /// ditto 795 | alias mir_least_squares_reset(T : double) = mir_least_squares_reset_d; 796 | 797 | /// ditto 798 | alias mir_least_squares_reset(T : float) = mir_least_squares_reset_s; 799 | } 800 | 801 | private: 802 | 803 | LeastSquaresResult!T optimizeLeastSquaresImplGenericBetterC(T) 804 | ( 805 | scope const ref LeastSquaresSettings!T settings, 806 | size_t m, 807 | size_t n, 808 | T* x, 809 | const(T)* l, 810 | const(T)* u, 811 | Slice!(T*) work, 812 | Slice!(lapackint*) iwork, 813 | scope void* fContext, 814 | scope LeastSquaresFunctionBetterC!T f, 815 | scope void* gContext, 816 | scope LeastSquaresJacobianBetterC!T g, 817 | scope void* tmContext, 818 | scope LeastSquaresThreadManagerBetterC tm, 819 | ) @system nothrow @nogc pure 820 | { 821 | version(LDC) pragma(inline, true); 822 | 823 | if (g) 824 | return optimizeLeastSquares!T( 825 | settings, 826 | m, 827 | x[0 .. n].sliced, 828 | l[0 .. n].sliced, 829 | u[0 .. n].sliced, 830 | work, 831 | iwork, 832 | (x, y) @trusted => f(fContext, y.length, x.length, x.iterator, y.iterator), 833 | (x, J) @trusted => g(gContext, J.length, x.length, x.iterator, J.iterator), 834 | null 835 | ); 836 | 837 | LeastSquaresTaskBetterC taskFunction = (scope const LeastSquaresTask context, uint totalThreads, uint threadId, uint i) @trusted 838 | { 839 | context(totalThreads, threadId, i); 840 | }; 841 | 842 | if (tm) 843 | return optimizeLeastSquares!T( 844 | settings, 845 | m, 846 | x[0 .. n].sliced, 847 | l[0 .. n].sliced, 848 | u[0 .. n].sliced, 849 | work, 850 | iwork, 851 | (x, y) @trusted => f(fContext, y.length, x.length, x.iterator, y.iterator), 852 | null, 853 | (count, scope LeastSquaresTask task) @trusted => tm(tmContext, count, task, taskFunction) 854 | ); 855 | return optimizeLeastSquares!T( 856 | settings, 857 | m, 858 | x[0 .. n].sliced, 859 | l[0 .. n].sliced, 860 | u[0 .. n].sliced, 861 | work, 862 | iwork, 863 | (x, y) @trusted => f(fContext, y.length, x.length, x.iterator, y.iterator), 864 | null, 865 | null 866 | ); 867 | } 868 | 869 | // private auto assumePure(T)(T t) 870 | // if (isFunctionPointer!T || isDelegate!T) 871 | // { 872 | // enum attrs = functionAttributes!T | FunctionAttribute.pure_; 873 | // return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 874 | // } 875 | 876 | // LM algorithm 877 | LeastSquaresResult!T optimizeLeastSquaresImplGeneric(T) 878 | ( 879 | scope const ref LeastSquaresSettings!T settings, 880 | size_t m, 881 | Slice!(T*) x, 882 | Slice!(const(T)*) lower, 883 | Slice!(const(T)*) upper, 884 | Slice!(T*) work, 885 | Slice!(lapackint*) iwork, 886 | scope LeastSquaresFunction!T f, 887 | scope LeastSquaresJacobian!T g, 888 | scope LeastSquaresThreadManager tm, 889 | ) @trusted nothrow @nogc pure 890 | { typeof(return) ret; with(ret) with(settings){ 891 | pragma(inline, false); 892 | import mir.algorithm.iteration: all; 893 | import mir.blas: axpy, gemv, scal, ger, copy, axpy, dot, swap, symv, iamax, syrk, Uplo, nrm2; 894 | import mir.math.common; 895 | import mir.math.sum: sum; 896 | import mir.ndslice.allocation: stdcUninitSlice; 897 | import mir.ndslice.dynamic: transposed; 898 | import mir.ndslice.slice: sliced; 899 | import mir.ndslice.topology: canonical, diagonal; 900 | import mir.optim.boxcqp; 901 | import mir.utility: max; 902 | import mir.algorithm.iteration; 903 | import core.stdc.stdio; 904 | 905 | debug 906 | { 907 | work[] = T.nan; 908 | iwork[] = 0; 909 | } 910 | 911 | auto n = cast(uint)x.length; 912 | 913 | auto deltaX = work[0 .. n]; work = work[n .. $]; 914 | auto Jy = work[0 .. n]; work = work[n .. $]; 915 | auto nBuffer = work[0 .. n]; work = work[n .. $]; 916 | 917 | auto JJ = work[0 .. n ^^ 2].sliced(n, n); work = work[n ^^ 2 .. $]; 918 | auto J = work[0 .. m * n].sliced(m, n); work = work[m * n .. $]; 919 | 920 | auto y = work[0 .. m]; work = work[m .. $]; 921 | auto mBuffer = work[0 .. m]; work = work[m .. $]; 922 | 923 | auto qpl = work[0 .. n]; work = work[n .. $]; 924 | auto qpu = work[0 .. n]; work = work[n .. $]; 925 | 926 | auto qpwork = work; 927 | 928 | version(LDC) pragma(inline, true); 929 | 930 | if (m == 0 || n == 0 || !x.all!"-a.infinity < a && a < a.infinity") 931 | { status = LeastSquaresStatus.badGuess; return ret; } 932 | if (!allLessOrEqual(lower, x) || !allLessOrEqual(x, upper)) 933 | { status = LeastSquaresStatus.badBounds; return ret; } 934 | if (!(0 <= minStepQuality && minStepQuality < 1)) 935 | { status = LeastSquaresStatus.badMinStepQuality; return ret; } 936 | if (!(0 <= goodStepQuality && goodStepQuality <= 1)) 937 | { status = LeastSquaresStatus.badGoodStepQuality; return ret; } 938 | if (!(minStepQuality < goodStepQuality)) 939 | { status = LeastSquaresStatus.badStepQuality; return ret; } 940 | if (!(1 <= lambdaIncrease && lambdaIncrease <= T.max.sqrt)) 941 | { status = LeastSquaresStatus.badLambdaParams; return ret; } 942 | if (!(T.min_normal.sqrt <= lambdaDecrease && lambdaDecrease <= 1)) 943 | { status = LeastSquaresStatus.badLambdaParams; return ret; } 944 | 945 | auto maxAge = settings.maxAge ? settings.maxAge : g ? 3 : 2 * n; 946 | 947 | if (!tm) tm = delegate(uint count, scope LeastSquaresTask task) pure @nogc nothrow @trusted 948 | { 949 | foreach(i; 0 .. count) 950 | task(1, 0, i); 951 | }; 952 | 953 | f(x, y); 954 | ++fCalls; 955 | residual = dot(y, y); 956 | bool fConverged = residual <= maxGoodResidual; 957 | 958 | 959 | bool needJacobian = true; 960 | uint age = maxAge; 961 | 962 | int badPredictions; 963 | 964 | import core.stdc.stdio; 965 | 966 | lambda = 0; 967 | iterations = 0; 968 | T deltaX_dot; 969 | T mu = 1; 970 | enum T suspiciousMu = 16; 971 | status = LeastSquaresStatus.maxIterations; 972 | do 973 | { 974 | if (fConverged) 975 | { 976 | status = LeastSquaresStatus.fConverged; 977 | break; 978 | } 979 | if (!(lambda <= maxLambda)) 980 | { 981 | status = LeastSquaresStatus.furtherImprovement; 982 | break; 983 | } 984 | if (mu > suspiciousMu && age) 985 | { 986 | needJacobian = true; 987 | age = maxAge; 988 | mu = 1; 989 | } 990 | if (!allLessOrEqual(x, x)) 991 | { 992 | // cast(void) assumePure(&printf)("\n@@@@\nX != X\n@@@@\n"); 993 | status = LeastSquaresStatus.numericError; 994 | break; 995 | } 996 | if (needJacobian) 997 | { 998 | needJacobian = false; 999 | if (age < maxAge) 1000 | { 1001 | age++; 1002 | auto d = 1 / deltaX_dot; 1003 | axpy(-1, y, mBuffer); // -deltaY 1004 | gemv(1, J, deltaX, 1, mBuffer); //-(f_new - f_old - J_old*h) 1005 | scal(-d, mBuffer); 1006 | ger(1, mBuffer, deltaX, J); //J_new = J_old + u*h' 1007 | } 1008 | else 1009 | { 1010 | age = 0; 1011 | if (g) 1012 | { 1013 | g(x, J); 1014 | gCalls += 1; 1015 | } 1016 | else 1017 | { 1018 | iwork[0 .. n] = 0; 1019 | tm(n, (uint totalThreads, uint threadId, uint j) 1020 | @trusted pure nothrow @nogc 1021 | { 1022 | auto idx = totalThreads >= n ? j : threadId; 1023 | auto p = JJ[idx]; 1024 | if (iwork[idx]++ == 0) 1025 | copy(x, p); 1026 | 1027 | auto save = p[j]; 1028 | auto xmh = save - jacobianEpsilon; 1029 | auto xph = save + jacobianEpsilon; 1030 | xmh = fmax(xmh, lower[j]); 1031 | xph = fmin(xph, upper[j]); 1032 | auto Jj = J[0 .. $, j]; 1033 | if (auto twh = xph - xmh) 1034 | { 1035 | p[j] = xph; 1036 | f(p, mBuffer); 1037 | copy(mBuffer, Jj); 1038 | p[j] = xmh; 1039 | f(p, mBuffer); 1040 | p[j] = save; 1041 | axpy(-1, mBuffer, Jj); 1042 | scal(1 / twh, Jj); 1043 | } 1044 | else 1045 | { 1046 | fill(T(0), Jj); 1047 | } 1048 | }); 1049 | fCalls += iwork[0 .. n].sum; 1050 | } 1051 | } 1052 | gemv(1, J.transposed, y, 0, Jy); 1053 | if (!(Jy[Jy.iamax].fabs > gradTolerance)) 1054 | { 1055 | if (age == 0) 1056 | { 1057 | status = LeastSquaresStatus.gConverged; 1058 | break; 1059 | } 1060 | age = maxAge; 1061 | continue; 1062 | } 1063 | } 1064 | 1065 | syrk(Uplo.Lower, 1, J.transposed, 0, JJ); 1066 | 1067 | if (!(lambda >= minLambda)) 1068 | { 1069 | lambda = 0.001 * JJ.diagonal[JJ.diagonal.iamax]; 1070 | if (!(lambda >= minLambda)) 1071 | lambda = 1; 1072 | } 1073 | 1074 | copy(lower, qpl); 1075 | axpy(-1, x, qpl); 1076 | copy(upper, qpu); 1077 | axpy(-1, x, qpu); 1078 | copy(JJ.diagonal, nBuffer); 1079 | JJ.diagonal[] += lambda; 1080 | if (qpSettings.solveBoxQP(JJ.canonical, Jy, qpl, qpu, deltaX, false, qpwork, iwork, false) != BoxQPStatus.solved) 1081 | { 1082 | // cast(void) assumePure(&printf)("\n@@@@\n error in solveBoxQP\n@@@@\n"); 1083 | status = LeastSquaresStatus.numericError; 1084 | break; 1085 | } 1086 | 1087 | if (!allLessOrEqual(deltaX, deltaX)) 1088 | { 1089 | // cast(void) assumePure(&printf)("\n@@@@\ndX != dX\n@@@@\n"); 1090 | status = LeastSquaresStatus.numericError; 1091 | break; 1092 | } 1093 | 1094 | copy(nBuffer, JJ.diagonal); 1095 | 1096 | axpy(1, x, deltaX); 1097 | axpy(-1, x, deltaX); 1098 | 1099 | auto newDeltaX_dot = dot(deltaX, deltaX); 1100 | 1101 | if (!(newDeltaX_dot.sqrt < maxStep)) 1102 | { 1103 | lambda *= lambdaIncrease * mu; 1104 | mu *= 2; 1105 | continue; 1106 | } 1107 | 1108 | copy(deltaX, nBuffer); 1109 | axpy(1, x, nBuffer); 1110 | applyBounds(nBuffer, lower, upper); 1111 | 1112 | ++fCalls; 1113 | f(nBuffer, mBuffer); 1114 | 1115 | auto trialResidual = dot(mBuffer, mBuffer); 1116 | 1117 | if (!(trialResidual <= T.infinity)) 1118 | { 1119 | // cast(void) assumePure(&printf)("\n@@@@\n trialResidual = %e\n@@@@\n", trialResidual); 1120 | status = LeastSquaresStatus.numericError; 1121 | break; 1122 | } 1123 | 1124 | auto improvement = residual - trialResidual; 1125 | if (!(improvement > 0)) 1126 | { 1127 | lambda *= lambdaIncrease * mu; 1128 | mu *= 2; 1129 | continue; 1130 | } 1131 | 1132 | needJacobian = true; 1133 | mu = 1; 1134 | iterations++; 1135 | copy(nBuffer, x); 1136 | swap(mBuffer, y); 1137 | residual = trialResidual; 1138 | fConverged = residual <= maxGoodResidual; 1139 | deltaX_dot = newDeltaX_dot; 1140 | 1141 | symv(Uplo.Lower, 1, JJ, deltaX, 2, Jy); // use Jy as temporal storage 1142 | auto predictedImprovement = -dot(Jy, deltaX); 1143 | 1144 | if (!(predictedImprovement > 0)) 1145 | { 1146 | status = LeastSquaresStatus.furtherImprovement; 1147 | break; 1148 | } 1149 | 1150 | auto rho = predictedImprovement / improvement; 1151 | 1152 | if (rho < minStepQuality) 1153 | { 1154 | lambda *= lambdaIncrease * mu; 1155 | mu *= 2; 1156 | } 1157 | else 1158 | if (rho >= goodStepQuality) 1159 | { 1160 | lambda = fmax(lambdaDecrease * lambda * mu, minLambda); 1161 | } 1162 | 1163 | // fmax(tolX, tolX * x.nrm2)); 1164 | if (!(deltaX_dot.sqrt > absTolerance && x.nrm2 > deltaX_dot.sqrt * relTolerance)) 1165 | { 1166 | if (age == 0) 1167 | { 1168 | status = LeastSquaresStatus.xConverged; 1169 | break; 1170 | } 1171 | age = maxAge; 1172 | continue; 1173 | } 1174 | } 1175 | while (iterations < maxIterations); 1176 | } return ret; } 1177 | 1178 | pragma(inline, false) 1179 | void fill(T, SliceKind kind)(T value, Slice!(T*, 1, kind) x) 1180 | { 1181 | x[] = value; 1182 | } 1183 | 1184 | pragma(inline, false) 1185 | bool allLessOrEqual(T)( 1186 | Slice!(const(T)*) a, 1187 | Slice!(const(T)*) b, 1188 | ) 1189 | { 1190 | import mir.algorithm.iteration: all; 1191 | return all!"a <= b"(a, b); 1192 | } 1193 | 1194 | uint normalizeSafety()(uint attrs) 1195 | { 1196 | if (attrs & FunctionAttribute.system) 1197 | attrs &= ~FunctionAttribute.safe; 1198 | return attrs; 1199 | } 1200 | 1201 | auto trustedAllAttr(T)(scope return T t) @trusted 1202 | if (isFunctionPointer!T || isDelegate!T) 1203 | { 1204 | enum attrs = (functionAttributes!T & ~FunctionAttribute.system) 1205 | | FunctionAttribute.pure_ 1206 | | FunctionAttribute.safe 1207 | | FunctionAttribute.nogc 1208 | | FunctionAttribute.nothrow_; 1209 | return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 1210 | } 1211 | 1212 | template isNullableFunction(alias f) 1213 | { 1214 | enum isNullableFunction = __traits(compiles, { alias F = Unqual!(typeof(f)); auto r = function(ref F e) {e = null;};} ); 1215 | } 1216 | -------------------------------------------------------------------------------- /subprojects/cblas-d.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=cblas-d 3 | url=https://github.com/DlangScience/cblas.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/lapack-d.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=lapack-d 3 | url=https://github.com/libmir/lapack.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-algorithm.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-algorithm 3 | url=https://github.com/libmir/mir-algorithm.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-blas.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-blas 3 | url=https://github.com/libmir/mir-blas.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-core.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-core 3 | url=https://github.com/libmir/mir-core.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-lapack.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-lapack 3 | url=https://github.com/libmir/mir-lapack.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-linux-kernel.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-linux-kernel 3 | url=https://github.com/libmir/mir-linux-kernel.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-random.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-random 3 | url=https://github.com/libmir/mir-random.git 4 | revision=head --------------------------------------------------------------------------------