├── .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 | [](https://travis-ci.org/libmir/mir-optim)
2 | [](http://code.dlang.org/packages/mir-optim)
3 | [](http://code.dlang.org/packages/mir-optim)
4 | [](http://code.dlang.org/packages/mir-optim)
5 | [](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
--------------------------------------------------------------------------------