├── .eslintrc
├── .gitignore
├── .gitmodules
├── README.md
├── demo
├── .gitignore
├── README.md
├── babel.config.js
├── deploy.sh
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── logo.ai
│ │ ├── logo.png
│ │ ├── logo.svg
│ │ └── logo_white.svg
│ ├── components
│ │ ├── TexDisplay.vue
│ │ ├── benchmark
│ │ │ ├── Benchmark.vue
│ │ │ └── benchmark.js
│ │ ├── home
│ │ │ └── Demo.vue
│ │ ├── nav
│ │ │ ├── Drawer.vue
│ │ │ └── Toolbar.vue
│ │ └── texUtils.js
│ ├── main.js
│ ├── plugins
│ │ └── vuetify.js
│ ├── router
│ │ └── router.js
│ └── views
│ │ ├── BenchmarksView.vue
│ │ ├── Documentation.vue
│ │ └── Home.vue
└── vue.config.js
├── dist
└── index.js
├── docs
└── doc.json
├── example
├── package.json
└── test.mjs
├── jest.config.js
├── package-lock.json
├── package.json
├── src
├── GC.mjs
├── classes
│ ├── Decompositions.js
│ ├── Matrix.js
│ └── Solvers.js
├── cpp
│ ├── CareSolver.h
│ ├── Decompositions.h
│ ├── DenseMatrix.h
│ ├── QuadProgOld.h
│ ├── QuadProgSolver.h
│ ├── Random.h
│ ├── SimplicialCholesky.h
│ ├── Solvers.h
│ ├── SparseMatrix.h
│ ├── contrib
│ │ └── multivariateNormal.h
│ └── embind.cc
├── eigen.d.ts
├── eigen.mjs
├── eigenTest.mjs
└── tests
│ ├── cpp
│ ├── CMakeLists.txt
│ ├── benchmark.cpp
│ └── simplicialcholesky
│ │ ├── Tutorial_sparse_example.cpp
│ │ └── Tutorial_sparse_example_details.hpp
│ └── ts
│ ├── benchmark.test.ts
│ ├── readme.md
│ ├── simplicialcholesky
│ └── Tutorial_sparse_example.ts
│ ├── types.ts
│ └── utils.ts
├── tsconfig.json
└── webpack.config.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "parser": "babel-eslint",
4 | "sourceType": "module",
5 | "ecmaVersion": 2018
6 | },
7 | "env": {
8 | "es6": true,
9 | "browser": true,
10 | "node": true
11 | }
12 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode/
3 | node_modules/
4 | yarn.lock
5 | yarn-error.log
6 | test
7 | build
8 | *.code-workspace
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "demo/lib"]
2 | path = demo/lib
3 | url = https://github.com/BertrandBev/vue-live-docs
4 | [submodule "lib/eigen"]
5 | path = lib/eigen
6 | url = https://gitlab.com/libeigen/eigen
7 | branch = 0fd6b4f7
8 | [submodule "lib/osqp"]
9 | path = lib/osqp
10 | url = https://github.com/osqp/osqp.git
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://badge.fury.io/js/eigen)
6 | [](https://bertrandbev.github.io/eigen-js/#/)
7 | [](https://github.com/emscripten-core/emscripten)
8 | [](https://opensource.org/licenses/MIT)
9 |
10 | # Eigen.js
11 |
12 | Eigen.js is a port of the [Eigen](https://eigen.tuxfamily.org/) C++ linear algebra library
13 |
14 | It uses a WebAssembly compiled subset of the [Eigen](https://eigen.tuxfamily.org/) library, and implements a garbage collection mechanism to manage memory
15 |
16 | # Live demo & documentation
17 |
18 | An interactive documentation is available at [eigen-js](https://bertrandbev.github.io/eigen-js/#/). Stress benchmarks can be found [here](https://bertrandbev.github.io/eigen-js/#/benchmark)
19 |
20 | ## Usage
21 |
22 | Eigen.js can be installed via [npm](https://www.npmjs.com/package/eigen) or [yarn](https://yarnpkg.com/en/package/eigen)
23 |
24 | ```bash
25 | npm install eigen
26 | ```
27 |
28 | ```bash
29 | yarn add eigen
30 | ```
31 |
32 | In a node (v14) application or in the browser (using [webpack](https://webpack.js.org/))
33 |
34 | ```js
35 | // test.mjs
36 | import eig from 'eigen';
37 |
38 | (async () => {
39 | await eig.ready;
40 | let M = new eig.Matrix([[1, 2], [3, 4]]);
41 | M.print("M");
42 | M = M.inverse();
43 | M.print("Minv");
44 | eig.GC.flush();
45 | })();
46 | ```
47 |
48 | This minimal example can be found under ``./example``
49 | To run it, execute
50 |
51 | ```bash
52 | cd example
53 | node test.mjs
54 | ```
55 |
56 | Which results in
57 |
58 | ```bash
59 | M
60 | [[1.00, 2.00]
61 | [3.00, 4.00]]
62 | Minv
63 | [[-2.00, 1.00]
64 | [1.50, -0.50]]
65 | ```
66 |
67 |
68 | ## Allocation
69 |
70 | The WebAssembly binary requires a manual memory management for objects allocated on the heap. Every time a matrix is created, it will be allocated on the heap and its memory won't be freed until its `delete()` method is invoked
71 |
72 | ```js
73 | // test.mjs
74 | import eig from 'eigen';
75 |
76 | (async () => {
77 | await eig.ready;
78 | let M = new eig.Matrix([[1, 2], [3, 4]]); // Memory is allocated for M
79 | M.print("M");
80 | M.delete(); // Memory is freed here
81 | M.print("M"); // This will trigger an error
82 | })();
83 | ```
84 |
85 | It can be cumbersome to call `delete()` on every object, especially for chained computations. Take for example `const I2 = eig.Matrix.identity(2, 2).matAdd(eig.Matrix.identity(2, 2))`. The identity matrix passed as an argument to `matAdd(...)` will be allocated but never freed, which will leak memory. To make things easier to manage, `eig.GC` keeps tracks of all the allocated objects on the heap and frees them all upon calling `eig.GC.flush()`.
86 |
87 | There could be instances where one would want to keep some matrices in memory while freeing a bunch of temporary ones used for computations. The method `eig.GC.pushException(...matrices)` whitelists its arguments to prevent `eig.GC.flush()` from flushing them. `eig.GC.popException(...matrices)` cancels any previous whitelisting.
88 |
89 | ```js
90 | // test.mjs
91 | import eig from 'eigen';
92 |
93 | (async () => {
94 | await eig.ready;
95 | const x = new eig.Matrix([[1, 2], [3, 4]]);
96 | eig.GC.pushException(x); // Whitelist x
97 | // Perform some computations
98 | const R = new eig.Matrix([[.1, 0], [.5, .1]]);
99 | x.matAddSelf(R.matMul(eig.Matrix.ones(2, 2)));
100 | // Free memory
101 | eig.GC.flush();
102 | x.print("x"); // x is still around!
103 | })();
104 | ```
105 |
106 | ## Documentation
107 |
108 | The documentation is available at [eigen.js](https://bertrandbev.github.io/eigen-js/#/)
109 |
110 | ## Build
111 |
112 | Make sure [Emscripten](https://emscripten.org/docs/getting_started/Tutorial.html) is intalled & activated in your terminal session
113 |
114 | ```bash
115 | source path/to/emsdk/emsdk_env.sh
116 | emcc -v
117 | ```
118 |
119 | Install dependencies
120 | [Eigen](https://gitlab.com/libeigen/eigen/-/releases/) 3.3.9 and [OSQP](https://github.com/oxfordcontrol/osqp/) (optional, see below)
121 |
122 | ```bash
123 | git submodule update --init --recursive
124 | ```
125 |
126 | Now compile osqp for a Webassembly target
127 |
128 | ```bash
129 | cd lib/osqp
130 | mkdir build; cd build
131 | emcmake cmake ..
132 | emmake make
133 | ```
134 |
135 | Once done, eigen.js can be compile to a wasm binary
136 |
137 | ```bash
138 | # From the root directory
139 | mkdir build
140 | emcc -I lib/eigen -I lib/osqp/include -Isrc lib/osqp/build/out/libosqp.a -s DISABLE_EXCEPTION_CATCHING=0 -s ASSERTIONS=0 -O3 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 --bind -o build/eigen_gen.js src/cpp/embind.cc
141 | ```
142 |
143 | If you are not interested in the OSQP functionality, you can build without installing it with
144 | ```bash
145 | emcc -D NO_OSQP -I lib/eigen -Isrc -s DISABLE_EXCEPTION_CATCHING=0 -s ASSERTIONS=0 -O3 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 --bind -o build/eigen_gen.js src/cpp/embind.cc
146 | ```
147 |
148 | ### Generate the documentation
149 |
150 | The documentation is generated from classes descriptions using [documentation.js](https://documentation.js.org/)
151 |
152 | ```bash
153 | documentation build src/classes/ -f json -o docs/doc.json
154 | ```
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # demo
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | yarn lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/demo/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/demo/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | # package.json "deploy": "yarn build; push-dir --dir=dist --branch=gh-pages --cleanup"
3 |
4 | # abort on errors
5 | set -e
6 |
7 | # build
8 | yarn build
9 |
10 | # navigate into the build output directory
11 | cd dist
12 |
13 | # Commit repo
14 | git init
15 | git add -A
16 | git commit -m 'deploy'
17 | git push -f git@github.com:BertrandBev/eigen-js.git master:gh-pages
18 |
19 | # Nav back
20 | cd -
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "animejs": "^3.1.0",
12 | "arraybuffer-loader": "^1.0.8",
13 | "core-js": "^3.4.3",
14 | "highlight.js": "^10.4.1",
15 | "katex": "^0.12.0",
16 | "linear-algebra": "^3.1.4",
17 | "markdown-it": "^12.0.2",
18 | "mathjs": "^8.0.1",
19 | "ml-matrix": "^6.4.1",
20 | "safe-eval": "^0.4.1",
21 | "two.js": "^0.7.0-beta.3",
22 | "vue": "^2.6.10",
23 | "vue-codemirror": "^4.0.6",
24 | "vue-katex": "^0.5.0",
25 | "vue-router": "^3.1.3",
26 | "vue-simple-svg": "^2.0.2",
27 | "vue-svg-filler": "^1.0.6",
28 | "vue-worker": "^1.2.1",
29 | "vuetify": "^2.1.0"
30 | },
31 | "devDependencies": {
32 | "@vue/cli-plugin-babel": "^4.1.0",
33 | "@vue/cli-plugin-eslint": "^4.1.0",
34 | "@vue/cli-plugin-router": "^4.1.0",
35 | "@vue/cli-service": "^4.1.0",
36 | "babel-eslint": "^10.0.3",
37 | "eslint": "^7.14.0",
38 | "eslint-plugin-vue": "^7.1.0",
39 | "sass": "^1.19.0",
40 | "sass-loader": "^10.1.0",
41 | "vue-cli-plugin-pug": "^2.0.0",
42 | "vue-cli-plugin-vuetify": "^2.0.2",
43 | "vue-template-compiler": "^2.6.10",
44 | "vuetify-loader": "^1.3.0"
45 | },
46 | "eslintConfig": {
47 | "root": true,
48 | "env": {
49 | "node": true
50 | },
51 | "extends": [
52 | "plugin:vue/essential",
53 | "eslint:recommended"
54 | ],
55 | "rules": {
56 | "no-console": "off",
57 | "no-unused-vars": "off"
58 | },
59 | "parserOptions": {
60 | "parser": "babel-eslint"
61 | }
62 | },
63 | "browserslist": [
64 | "> 1%",
65 | "last 2 versions"
66 | ]
67 | }
68 |
--------------------------------------------------------------------------------
/demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BertrandBev/eigen-js/5ef085cbe2304b1d7c85424d72be7d1f657fccff/demo/public/favicon.ico
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | eigen-js
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 | v-app(v-resize="onResize")
3 | Drawer(ref='drawer')
4 | Toolbar(@toggleDrawer='toggleDrawer')
5 | v-content
6 | //* Loading row
7 | v-row(v-if='loading'
8 | align='center'
9 | justify='center'
10 | style='height: 100%')
11 | v-progress-circular(:size='40'
12 | color='blue'
13 | indeterminate)
14 | //* Content
15 | router-view(v-else)
16 |
17 |
18 |
59 |
60 |
--------------------------------------------------------------------------------
/demo/src/assets/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BertrandBev/eigen-js/5ef085cbe2304b1d7c85424d72be7d1f657fccff/demo/src/assets/logo.ai
--------------------------------------------------------------------------------
/demo/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BertrandBev/eigen-js/5ef085cbe2304b1d7c85424d72be7d1f657fccff/demo/src/assets/logo.png
--------------------------------------------------------------------------------
/demo/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/demo/src/assets/logo_white.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/demo/src/components/TexDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 | //- v-alert(v-model='visible'
3 | //- border='left'
4 | //- outlined
5 | //- dismissible
6 | //- min-width='100%'
7 | //- icon='mdi-chevron-right'
8 | //- color='green')
9 | .grey--text.text--darken-2(v-katex="expression")
10 |
11 |
12 |
52 |
53 |
--------------------------------------------------------------------------------
/demo/src/components/benchmark/Benchmark.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | //- centered
4 | span.headline.font-weight-light {{ name }}
5 | v-tabs.elevation-2.mt-2(v-model='tab'
6 | background-color='primary'
7 | dark
8 | grow)
9 | v-tabs-slider
10 | v-tab(v-for='fun, lib in tabs'
11 | :key='`tab_${lib}`')
12 | //- :href='`#tab-${lib}`')
13 | | {{ libName(lib) }}
14 | v-tab-item(v-for='fun, lib in tabs'
15 | :key='`tab_item_${lib}`')
16 | CodeArea(:ref='`codeArea_${lib}`'
17 | :code='fun'
18 | @run='() => runBenchmark(lib)')
19 | //* Params
20 | div(style='display: flex; margin-top: 28px')
21 | v-text-field(v-model.number='params.size'
22 | label='Matrix size'
23 | hide-details
24 | outlined
25 | dense)
26 | v-text-field.ml-2(v-model.number='params.iterations'
27 | label='Iteration count'
28 | hide-details
29 | outlined
30 | dense)
31 | v-spacer
32 | v-btn(@click='runAll'
33 | outlined
34 | :loading='running'
35 | :disabled='running'
36 | color='primary') Run all
37 |
38 | //* Results
39 | v-simple-table.mt-5(style='background: transparent')
40 | thead
41 | tr
42 | th(v-for='fun, lib in tabs') {{ libName(lib) }}
43 | tbody
44 | tr
45 | td(v-for='fun, lib in tabs') {{ libResult(lib) }}
46 |
47 |
48 |
--------------------------------------------------------------------------------
/demo/src/components/benchmark/benchmark.js:
--------------------------------------------------------------------------------
1 | import eig from "@eigen";
2 | import { Matrix, inverse } from "ml-matrix";
3 | const mathjs = require('mathjs');
4 | const linalg = require('linear-algebra')();
5 | const lalolib = window.lalolib;
6 | // import lalolib from '@assets/lalolib-noglpk-module.min'
7 | import _ from 'lodash';
8 |
9 | const mlmatrix = {
10 | Matrix,
11 | inverse
12 | };
13 |
14 | const libraries = {
15 | eig: { name: 'Eigen JS', lib: eig },
16 | mlmatrix: { name: 'Ml Matrix', lib: mlmatrix },
17 | mathjs: { name: 'Math JS', lib: mathjs },
18 | linalg: { name: 'Linalg', lib: linalg },
19 | lalolib: { name: 'LaloLib', lib: lalolib },
20 | };
21 |
22 |
23 | class Random {
24 | constructor(seed) {
25 | this.seed = seed;
26 | }
27 |
28 | get() {
29 | const x = Math.sin(this.seed++) * 10000;
30 | return x - Math.floor(x);
31 | }
32 | }
33 |
34 | class Benchmark {
35 | constructor(data) {
36 | this.data = data;
37 | this.functions = {};
38 | this.libraries = libraries;
39 | _.keys(libraries).filter(lib => _.has(data, lib)).forEach(lib => {
40 | const body = data.body(lib, data[lib]);
41 | this.functions[lib] = body.replace(/^\s*[\r\n]/gm, "");
42 | });
43 | }
44 |
45 | static getRandomArray2D(size) {
46 | const r = new Random(1928473);
47 | const value = [...Array(size)].map((v, idx) => idx);
48 | return [...Array(size)].map((v, idx) => value.map(() => r.get()));
49 | }
50 |
51 | static createMatrix(type, size) {
52 | const array = Benchmark.getRandomArray2D(size);
53 | switch (type) {
54 | case 'eig':
55 | return new eig.Matrix(array);
56 | case 'mlmatrix':
57 | return new Matrix(array);
58 | case 'lalolib':
59 | return lalolib.array2mat(array);
60 | case 'mathjs':
61 | return mathjs.matrix(array);
62 | case 'linalg':
63 | return new linalg.Matrix(array);
64 | }
65 | }
66 |
67 | run(benchmarkFun) {
68 | const startTime = Date.now();
69 | const libs = _.zipObject(
70 | _.keys(libraries),
71 | _.values(libraries).map(l => l.lib)
72 | );
73 | const params = {
74 | createMatrix: Benchmark.createMatrix,
75 | ...libs,
76 | ...this.data.params,
77 | };
78 | let fun = new Function(..._.keys(params), benchmarkFun);
79 | try {
80 | const result = fun(..._.values(params));
81 | } catch (e) {
82 | console.error('Benchmark error', e);
83 | }
84 | return Date.now() - startTime;
85 | }
86 | }
87 |
88 | /**
89 | * Benchmark functions
90 | */
91 | const benchmarks = [];
92 |
93 | benchmarks.push(new Benchmark({
94 | name: 'Matrix multiplication',
95 | description: 'Matrix multiplication test',
96 | params: { size: 100, iterations: 100 },
97 | body: (type, fun) => `
98 | const A = createMatrix('${type}', size);
99 | const B = createMatrix('${type}', size);
100 | for (let k = 0; k < iterations; k++) {
101 | ${fun}
102 | }`,
103 | eig: `
104 | A.matMul(B);`,
105 | lalolib: `
106 | lalolib.mul(A, B);`,
107 | mlmatrix: `
108 | A.mmul(B)`,
109 | mathjs: `
110 | mathjs.multiply(A, B)`,
111 | linalg: `
112 | A.dot(B)`
113 | }));
114 |
115 | benchmarks.push(new Benchmark({
116 | name: 'Matrix inversion',
117 | description: 'Matrix inversion test',
118 | params: { size: 100, iterations: 20 },
119 | body: (type, fun) => `
120 | const A = createMatrix('${type}', size);
121 | for (let k = 0; k < iterations; k++) {
122 | ${fun}
123 | }`,
124 | eig: `
125 | A.inverse();`,
126 | lalolib: `
127 | lalolib.inv(A);`,
128 | mlmatrix: `
129 | mlmatrix.inverse(A)`,
130 | mathjs: `
131 | mathjs.inv(A)`
132 | }));
133 |
134 | benchmarks.push(new Benchmark({
135 | name: 'Singular value decomposition',
136 | description: 'Singular value decomposition test',
137 | params: { size: 100, iterations: 100 },
138 | body: (type, fun) => `
139 | const A = createMatrix('${type}', size);
140 | for (let k = 0; k < iterations; k++) {
141 | ${fun}
142 | }`,
143 | eig: `
144 | eig.Decompositions.svd(A, true)`,
145 | lalolib: `
146 | lalolib.svd(A, "thin");`,
147 | }));
148 |
149 | export default benchmarks;
--------------------------------------------------------------------------------
/demo/src/components/home/Demo.vue:
--------------------------------------------------------------------------------
1 |
2 | div(:style='layoutStyle')
3 | div(:style='expStyle')
4 | span.white--text.font-weight-light.headline SVD
5 | div.white--text(v-katex='expression')
6 | div(ref='canvas')
7 |
8 |
9 |
--------------------------------------------------------------------------------
/demo/src/components/nav/Drawer.vue:
--------------------------------------------------------------------------------
1 |
2 | v-navigation-drawer(v-model='drawer'
3 | app clipped)
4 | v-list(dense)
5 | //* Routes
6 | div(v-for='routes, name in groups'
7 | :key='`group_${name}`')
8 | v-list-item
9 | v-list-item-content
10 | v-list-item-subtitle {{ name }}
11 | v-list-item(v-for='item, idx in routes'
12 | :key='`item_${idx}`'
13 | @click='nav(item.name)'
14 | :class='{ active: isActive(item.name) }')
15 | v-list-item-action
16 | v-icon {{ item.icon }}
17 | v-list-item-content
18 | v-list-item-title {{ item.title }}
19 | //* Footer
20 | v-divider
21 | v-list(dense style='flex: 0 0 auto')
22 | v-list-item(@click='github')
23 | v-list-item-action
24 | v-icon mdi-github
25 | v-list-item-content
26 | v-list-item-title Github
27 |
28 |
29 |
74 |
75 |
--------------------------------------------------------------------------------
/demo/src/components/nav/Toolbar.vue:
--------------------------------------------------------------------------------
1 |
2 | v-app-bar(app clipped-left
3 | :color='color'
4 | :dark='dark'
5 | dense)
6 | v-app-bar-nav-icon(v-if='!showBack'
7 | @click.stop="$emit('toggleDrawer')")
8 | v-toolbar-items(v-else
9 | style='margin-left: 0px')
10 | v-btn.mr-2(icon @click='navback')
11 | v-icon(small) fas fa-chevron-left
12 | v-toolbar-title.headline
13 | span.font-weight-light {{ title }}
14 | v-spacer
15 | v-toolbar-items
16 | v-btn(href='https://github.com/BertrandBev/eigen-js', target='_blank', text)
17 | span.mr-2 Github
18 | v-icon mdi-open-in-new
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/src/components/texUtils.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import eig from "@eigen";
3 |
4 | function format(val) {
5 | return `${val}`.length < 5 ? `${val}` : val.toFixed(3);
6 | }
7 |
8 | function getTex(val) {
9 | if (_.isPlainObject(val)) {
10 | const list = [];
11 | _.forEach(val, (sub, key) => {
12 | list.push(`\\text{${key}: } ${getTex(sub)}`);
13 | });
14 | return list.join(", ");
15 | }
16 | if (Array.isArray(val)) {
17 | return val.map(getTex).join(", ");
18 | } else if (
19 | val instanceof eig.Matrix ||
20 | val instanceof eig.ComplexDenseMatrix
21 | ) {
22 | let body = [];
23 | for (let i = 0; i < val.rows(); i++) {
24 | const row = [];
25 | for (let j = 0; j < val.cols(); j++) {
26 | const v = val.get(i, j);
27 | const str = row.push(getTex(v));
28 | }
29 | body.push(row.join(" & "));
30 | }
31 | return `
32 | \\begin{pmatrix}
33 | ${body.join("\\\\")}
34 | \\end{pmatrix}
35 | `;
36 | } else if (val instanceof eig.Complex) {
37 | let ri = [val.real(), val.imag()];
38 | ri = ri
39 | .map((v, idx) =>
40 | Math.abs(v) > 1e-8 ? format(v) + (idx === 1 ? "i" : "") : null
41 | )
42 | .filter(val => !!val);
43 | return ri.join(" + ");
44 | } else if (typeof val === "number") {
45 | return `${format(val)}`;
46 | } else if (typeof val === "string") {
47 | return `${val}`;
48 | } else if (val) {
49 | const constructor = val.constructor;
50 | return `${constructor ? constructor.name : typeof val}`;
51 | } else {
52 | return `${val}`;
53 | }
54 | }
55 |
56 | export { getTex }
--------------------------------------------------------------------------------
/demo/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router/router.js'
4 | import vuetify from './plugins/vuetify';
5 | import VueKatex from 'vue-katex'
6 | import 'katex/dist/katex.min.css';
7 | import { SimpleSVG } from 'vue-simple-svg'
8 | import SvgFiller from 'vue-svg-filler'
9 | import '@vue-live-docs/plugins/codeMirror'; // TODO: clean that up
10 | import 'highlight.js/styles/monokai-sublime.css'; // TODO: clean that up
11 |
12 | Vue.use(VueKatex)
13 | Vue.component('svg-filler', SvgFiller)
14 | Vue.component('simple-svg', SimpleSVG)
15 |
16 | const store = new Vue({
17 | data: () => ({
18 | windowSize: { x: 0, y: 0 }
19 | })
20 | })
21 | Vue.prototype.$store = store
22 |
23 | Vue.config.productionTip = false
24 | new Vue({
25 | router,
26 | vuetify,
27 | render: h => h(App)
28 | }).$mount('#app')
29 |
--------------------------------------------------------------------------------
/demo/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuetify from 'vuetify/lib';
3 | import colors from 'vuetify/lib/util/colors'
4 |
5 | Vue.use(Vuetify);
6 |
7 | export default new Vuetify({
8 | theme: {
9 | themes: {
10 | light: {
11 | primary: colors.teal.darken1,
12 | secondary: colors.teal.lighten4,
13 | accent: colors.indigo.base,
14 | },
15 | },
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/demo/src/router/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Documentation from '../views/Documentation.vue'
4 | import BenchmarksView from '../views/BenchmarksView.vue'
5 | import Home from '../views/Home.vue'
6 |
7 | Vue.use(VueRouter)
8 |
9 | const classes = [
10 | { name: 'matrix', title: 'Matrix', icon: 'mdi-matrix' },
11 | { name: 'solvers', title: 'Solvers', icon: 'mdi-cogs' },
12 | { name: 'decompositions', title: 'Decompositions', icon: 'mdi-puzzle' }
13 | ]
14 | const classRoutes = classes.map(cl => ({
15 | ...cl,
16 | group: 'Classes',
17 | path: `/${cl.name}`,
18 | component: Documentation,
19 | props: { className: cl.title }
20 | }))
21 |
22 | const routes = [
23 | {
24 | path: '/',
25 | name: 'home',
26 | title: 'Eigen JS',
27 | group: 'Pages',
28 | component: Home,
29 | icon: 'mdi-lambda'
30 |
31 | },
32 | ...classRoutes,
33 | {
34 | path: '/benchmark',
35 | name: 'benchmark',
36 | title: 'Benchmark',
37 | group: 'Benchmarks',
38 | component: BenchmarksView,
39 | icon: 'mdi-speedometer'
40 | }
41 | ]
42 |
43 | const router = new VueRouter({
44 | // mode: 'history',
45 | base: process.env.BASE_URL,
46 | routes
47 | })
48 |
49 | export { routes }
50 | export default router
51 |
--------------------------------------------------------------------------------
/demo/src/views/BenchmarksView.vue:
--------------------------------------------------------------------------------
1 |
2 | v-container
3 | v-col
4 | v-row
5 | .headline.font-weight-light Benchmarks of linear algebra javascript libraries
6 | v-row.mt-1
7 | span.pl-2.grey--text.text--darken-2
8 | | These tables compare the performances of different javascript linear algebra libraries
9 | //*
10 | Benchmark.mt-2(v-for='benchmark, idx in benchmarks'
11 | :key='`benchmark_${idx}`'
12 | :benchmark='benchmark')
13 |
14 |
15 |
39 |
--------------------------------------------------------------------------------
/demo/src/views/Documentation.vue:
--------------------------------------------------------------------------------
1 |
2 | .docs-container
3 | DocsClass.docs-class(:data='data'
4 | :env='env')
5 | template(v-slot:default='props')
6 | TexDisplay(:value='props.result')
7 |
8 |
9 |
47 |
48 |
--------------------------------------------------------------------------------
/demo/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 | div(style='display: flex; flex-direction: column; align-items: center')
3 | div(:style='divStyle')
4 | div(style='display: flex; flex-direction: column; align-items: center')
5 | v-img(src='@assets/logo_white.svg'
6 | width='150px')
7 | span.display-1.font-weight-light.white--text(
8 | style='margin-top: 16px') Eigen JS
9 | Demo(style='margin-top: 48px')
10 | div(style='width: 100%; max-width: 960px; padding: 40px'
11 | v-html='markdownHtml')
12 |
13 |
14 |
79 |
80 |
--------------------------------------------------------------------------------
/demo/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | publicPath: process.env.NODE_ENV === 'production'
5 | ? '/eigen-js/'
6 | : '/',
7 | transpileDependencies: [
8 | "vuetify"
9 | ],
10 | chainWebpack: config => {
11 | config.resolve.symlinks(false);
12 | config.module
13 | .rule('wasm')
14 | .type('javascript/auto')
15 | .test(/\.wasm$/)
16 | .use('arraybuffer-loader')
17 | .loader('arraybuffer-loader')
18 | .end()
19 |
20 | config.resolve.alias.set('@src', path.resolve(__dirname, 'src'))
21 | config.resolve.alias.set('@assets', path.resolve(__dirname, 'src/assets'))
22 | config.resolve.alias.set('@eigen', path.resolve(__dirname, '../dist/index.js'))
23 | config.resolve.alias.set('@docs', path.resolve(__dirname, '../docs'))
24 | config.resolve.alias.set('@vue-live-docs', path.resolve(__dirname, 'lib/vue-live-docs/src'))
25 | }
26 | }
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT"
6 | }
7 |
--------------------------------------------------------------------------------
/example/test.mjs:
--------------------------------------------------------------------------------
1 | import eig from '../dist/index.js'
2 | // In a browser environment, install eigen from npm, and import it with
3 | // import eig from 'eigen'
4 |
5 | (async () => {
6 | await eig.ready
7 | let M = new eig.Matrix([[1, 2], [3, 4]])
8 | M.print("M");
9 | M = M.inverse();
10 | M.print("Minv");
11 | eig.GC.flush();
12 | })();
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eigen",
3 | "version": "0.2.2-simplicalcholesky",
4 | "description": "Eigen-js is a port of the [Eigen](https://eigen.tuxfamily.org/) C++ linear algebra library",
5 | "main": "dist/index.js",
6 | "types": "src/eigen.d.ts",
7 | "files": [
8 | "dist/index.js",
9 | "src/eigen.d.ts"
10 | ],
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/BertrandBev/eigen-js.git"
14 | },
15 | "author": "Bertrand ",
16 | "license": "MIT",
17 | "keywords": [
18 | "linear",
19 | "algebra",
20 | "eigen",
21 | "matrix",
22 | "optimisation"
23 | ],
24 | "homepage": "http://TODO",
25 | "devDependencies": {
26 | "@babel/core": "^7.7.5",
27 | "@babel/plugin-syntax-dynamic-import": "^7.7.4",
28 | "@babel/preset-env": "^7.7.6",
29 | "@types/jest": "^29.5.3",
30 | "arraybuffer-loader": "^1.0.8",
31 | "babel-loader": "^8.0.6",
32 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
33 | "exports-loader": "^0.7.0",
34 | "file-loader": "^5.0.2",
35 | "jest": "^29.6.1",
36 | "ts-jest": "^29.1.1",
37 | "ts-node": "^10.9.1",
38 | "typescript": "^5.1.6",
39 | "webpack": "^4.41.3",
40 | "webpack-cli": "^3.3.10"
41 | },
42 | "scripts": {
43 | "build": "webpack --mode production",
44 | "test": "npx jest --watchAll --verbose"
45 | },
46 | "dependencies": {
47 | "@types/hashmap": "^2.3.1",
48 | "hashmap": "^2.4.0"
49 | },
50 | "bugs": {
51 | "url": "https://github.com/BertrandBev/eigen-js/issues"
52 | },
53 | "directories": {
54 | "lib": "lib"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/GC.mjs:
--------------------------------------------------------------------------------
1 | import HashMap from 'hashmap'
2 |
3 | function getStaticMethods(Class) {
4 | return Object.getOwnPropertyNames(Class).filter(prop => prop !== "constructor" && typeof Class[prop] === "function");
5 | }
6 |
7 | class GarbageCollector {
8 | static add(...addList) {
9 | addList.flat(Infinity).forEach(obj => {
10 | GarbageCollector.objects.add(obj)
11 | })
12 | }
13 |
14 | static pushException(...exceptionList) {
15 | exceptionList.flat(Infinity).forEach(obj => {
16 | const val = GarbageCollector.whitelist.get(obj) || 0
17 | GarbageCollector.whitelist.set(obj, val + 1)
18 | })
19 | }
20 |
21 | static popException(...exceptionList) {
22 | exceptionList.flat(Infinity).forEach(obj => {
23 | const val = GarbageCollector.whitelist.get(obj) || 0
24 | GarbageCollector.whitelist.set(obj, val - 1)
25 | if (GarbageCollector.whitelist.get(obj) <= 0) {
26 | GarbageCollector.whitelist.remove(obj)
27 | }
28 | })
29 | }
30 |
31 | static flush() {
32 | const flushed = [...GarbageCollector.objects].filter(
33 | obj => !GarbageCollector.whitelist.has(obj)
34 | )
35 | flushed.forEach(obj => {
36 | obj.delete()
37 | GarbageCollector.objects.delete(obj)
38 | })
39 | return flushed.length
40 | }
41 |
42 | /**
43 | * Reference bookkeeping
44 | */
45 | static set(ref, name, newObj) {
46 | if (ref[name]) {
47 | GarbageCollector.popException(ref[name])
48 | }
49 | GarbageCollector.pushException(newObj)
50 | ref[name] = newObj
51 | }
52 |
53 | /**
54 | * Equip class to add constructor feedback
55 | * @param {Set} classes Set of all the classes names
56 | * @param {object} Class class to wrap
57 | * @returns {object} wrapped class
58 | */
59 | static initClass(classes, Class) {
60 | const NewClass = function (...args) {
61 | const instance = new Class(...args)
62 | GarbageCollector.add(instance)
63 | return instance
64 | }
65 | const arr = [Class, Class.prototype] // forEach doesn't seem to work
66 | for (let idx in arr) {
67 | let obj = arr[idx]
68 | getStaticMethods(obj).forEach(method => {
69 | // console.log(`Wrapping reg method ${method} of ${Class}`)
70 | const fun = obj[method]
71 | obj[method] = function (...args) {
72 | const rtn = fun.call(this, ...args)
73 | if (rtn && classes.has(rtn.constructor.name)) {
74 | GarbageCollector.add(rtn)
75 | }
76 | return rtn
77 | }
78 | })
79 | }
80 |
81 | // Class.prototype.constructor = NewClass TODO: control
82 | getStaticMethods(Class).forEach(method => {
83 | NewClass[method] = Class[method];
84 | })
85 | NewClass.prototype = Class.prototype
86 | return NewClass
87 | }
88 | }
89 |
90 | // Add static members
91 | GarbageCollector.objects = new Set();
92 | GarbageCollector.whitelist = new HashMap(); // Reference count
93 |
94 | export default GarbageCollector
95 | export { getStaticMethods }
--------------------------------------------------------------------------------
/src/classes/Decompositions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A class containing various matrix decompositions algorithms
3 | */
4 | class Decompositions {
5 |
6 | /**
7 | * Cholesky decomposition consists in decomposing a square, hermitian (or real symmetric), positive definite matrix A as a product A = L L*
8 | * Where L is lower triangular with real positive diagonal entries
9 | * @param {Matrix} M
10 | * @returns {QRResult} - Result
11 | * @warning M must be positive definite
12 | * @example
13 | * const M = new eig.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
14 | * const cholesky = eig.Decompositions.cholesky(M);
15 | * return cholesky;
16 | */
17 | static cholesky(M) { }
18 |
19 | /**
20 | * LU decomposition consists in decomposing a square matrix A as a product A = P^-1 * (L + I) * U * Q^-1
21 | * Where U is upper triangular L lower triangular, I is the identity matrix, and P & Q are two permutation matrices
22 | * @param {Matrix} M
23 | * @returns {QRResult} - Result
24 | * @warning M must be a square matrix
25 | * @example
26 | * const M = new eig.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
27 | * const lu = eig.Decompositions.lu(M);
28 | * // Reconstruct M from its LU decomposition
29 | * const I = eig.Matrix.identity(3, 3);
30 | * const M2 = lu.P.inverse().matMul(lu.L.matAdd(I)).matMul(lu.U).matMul(lu.Q.inverse());
31 | * return { ...lu, M2 };
32 | */
33 | static lu(M) { }
34 |
35 | /**
36 | * QR decomposition consists in decomposing any n-by-p matrix A with linearly independent colums as a product A = Q R
37 | * Where Q is a m-by-m unitary matrix and R is a m-by-n upper triangular matrix
38 | * @param {Matrix} M
39 | * @returns {QRResult} - Result
40 | * @example
41 | * const M = new eig.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
42 | * const qr = eig.Decompositions.qr(M);
43 | * return qr;
44 | */
45 | static qr(M) { }
46 |
47 | /**
48 | * SVD decomposition consists in decomposing any n-by-p matrix A as a product M = U S V'
49 | * Where U is a n-by-n unitary, V is a p-by-p unitary, and S is a n-by-p real positive diagonal matrix
50 | * Singular values are always sorted in decreasing order.
51 | * The algorithm used is a two-sided jacobi SVD decomposition of rectangular matrices for small matrices (< 16x16), or bidiagonal divide and conquer SVD for large ones.
52 | * @param {Matrix} M - Matrix of interest
53 | * @param {boolean} thin - Only keep the non-zero singular values
54 | * @returns {SVDResult} - Result
55 | * @example
56 | * const M = new eig.Matrix([[1, 2, 3], [4, 5, 6]]);
57 | * const svd = eig.Decompositions.svd(M, true);
58 | * // Reconstruct M from its SVD decomposition
59 | * let sigma = new eig.Matrix(svd.U.cols(), svd.V.cols());
60 | * sigma.setBlock(0, 0, eig.Matrix.diagonal(svd.sv));
61 | * const M2 = svd.U.matMul(sigma.transpose()).matMul(svd.V.transpose());
62 | * return {...svd, M2}
63 | */
64 | static svd(M, thin) { }
65 | }
--------------------------------------------------------------------------------
/src/classes/Matrix.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * A generic dense matrix class holding real number
4 | */
5 | class Matrix {
6 | /**
7 | * - Creates a m * n matrix filled with zeros
8 | * - Create a matrix from an array
9 | * - Copy an existing matrix
10 | * @param {number} arg0 - Number of rows || 1D or 2D array to build matrix from || matrix to be copied
11 | * @param {number} arg1 - Number of columns
12 | * @example
13 | * const m1 = new eig.Matrix(2, 2);
14 | * const m2 = new eig.Matrix(2);
15 | * const m3 = new eig.Matrix([[1, 2], [3, 4]]);
16 | * const m4 = new eig.Matrix(m2);
17 | * return [m1, m2, m3, m4]
18 | */
19 | constructor(arg0, arg1) { }
20 |
21 | /**
22 | * Get the number of rows
23 | * @returns {number} - The number of rows
24 | * @example
25 | * const m = new eig.Matrix([1, 2]);
26 | * return m.rows();
27 | */
28 | rows() { }
29 |
30 | /**
31 | * Get the number of columns
32 | * @returns {number} - The number of columns
33 | * @example
34 | * const m = new eig.Matrix([1, 2]);
35 | * return m.cols();
36 | */
37 | cols() { }
38 |
39 | /**
40 | * Get the value at (i, j)
41 | * @param {number} i - Row
42 | * @param {number} j - Col
43 | * @returns {number} - M[i, j]
44 | * @example
45 | * const m = new eig.Matrix([[1, 2], [3, 4]]);
46 | * return m.get(1, 0);
47 | */
48 | get(i, j) { }
49 |
50 | /**
51 | * Get a vector value at (i)
52 | * @param {number} i - Row
53 | * @returns {number} - M[i, 0] or M[0, i]
54 | * @example
55 | * const m = new eig.Matrix([1, 2]);
56 | * return m.get(1);
57 | */
58 | get(i) { }
59 |
60 | /**
61 | * Set the value at (i, j)
62 | * @param {number} i - Row
63 | * @param {number} j - Col
64 | * @param {number} val - value
65 | * @example
66 | * const A = new eig.Matrix(2, 2);
67 | * A.set(0, 1, 3 / 2);
68 | * return A.get(0, 1);
69 | */
70 | set(i, j, val) { }
71 |
72 | /**
73 | * Set a vector value at (i)
74 | * @param {number} i - Row
75 | * @param {number} val - value
76 | * @example
77 | * const A = new eig.Matrix(2);
78 | * A.set(1, 3 / 2);
79 | * return A.get(1);
80 | */
81 | set(i, val) { }
82 |
83 | /**
84 | * Get a vector length
85 | * @warning The matrix must be a vector (either single row or column)
86 | * @returns {number} - V.length
87 | * @example
88 | * return (new eig.Matrix(3)).length();
89 | */
90 | length() { }
91 |
92 | /**
93 | * Take the dot product with another vector
94 | * @warning The matrix must be a vector (either single row or column)
95 | * @param {Matrix} B - Vector of interest
96 | * @returns {number} - V.B
97 | * @example
98 | * const V = new eig.Matrix([1, 2]);
99 | * const B = new eig.Matrix([-1, 3]);
100 | * return V.dot(B);
101 | */
102 | dot(B) { }
103 |
104 |
105 | /**
106 | * Get the l2 (Frobenius) norm
107 | * @returns {number} - The l2 norm
108 | */
109 | norm() { }
110 |
111 | /**
112 | * Get the squared l2 (Frobenius) norm
113 | * @returns {number} - The l2 norm squared
114 | */
115 | normSqr() { }
116 |
117 | /**
118 | * Get the l1 norm
119 | * @returns {number} - The l1 norm
120 | */
121 | l1Norm() { }
122 |
123 | /**
124 | * Get the l-Infinity norm
125 | * @returns {number} - The l-Infinity norm
126 | * @example
127 | * const m = new eig.Matrix([[1, 2], [3, 4]]);
128 | * return [m.norm(), m.normSqr(), m.l1Norm(), m.lInfNorm()];
129 | */
130 | lInfNorm() { }
131 |
132 | /**
133 | * Get the rank
134 | * @returns {number} - The rank
135 | * @example
136 | * const m = new eig.Matrix([[2, 1], [2, 1]]);
137 | * return m.rank();
138 | */
139 | rank() { }
140 |
141 | /**
142 | * Get the determinant
143 | * @return {number} - The determinant
144 | * @example
145 | * const m = new eig.Matrix([[2, 0], [0, 3]]);
146 | * return m.det();
147 | */
148 | det() { }
149 |
150 | /**
151 | * Sum all the elements
152 | * @returns {number} - The sum of all the elements
153 | * @example
154 | * const m = new eig.Matrix([1, 2]);
155 | * return m.sum();
156 | */
157 | sum() { }
158 |
159 | /**
160 | * Get a block
161 | * @param {number} i - The block top left row
162 | * @param {number} j - The block top left column
163 | * @param {number} di - Block rows count
164 | * @param {number} dj - Block columns count
165 | * @returns {Matrix} - The resulting block
166 | * @example
167 | * const m = new eig.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
168 | * return m.block(1, 0, 1, 2);
169 | */
170 | block(i, j, di, dj) { }
171 |
172 | /**
173 | * Set a block
174 | * @param {number} i - The block top left row
175 | * @param {number} j - The block top left column
176 | * @param {Matrix} block - Block to be set
177 | * @example
178 | * const m = new eig.Matrix(3, 3);
179 | * const b = new eig.Matrix([[1, 2, 3], [4, 5, 6]]);
180 | * m.setBlock(1, 0, b);
181 | * return m;
182 | */
183 | setBlock(i, j, block) { }
184 |
185 | /**
186 | * Multiply by a value
187 | * @param {number} val - Value to multiply by
188 | * @returns {Matrix} - M * val
189 | * @example
190 | * const m = new eig.Matrix([1, 2]);
191 | * return m.mul(2);
192 | */
193 | mul(val) { }
194 |
195 | /**
196 | * Multiply by a value in place
197 | * @param {number} val - Value to multiply by
198 | * @returns {Matrix} - this (M * val)
199 | * @example
200 | * const m = new eig.Matrix([1, 2]);
201 | * m.mulSelf(2);
202 | * return m;
203 | */
204 | mulSelf(val) { }
205 |
206 | /**
207 | * Divide by a value
208 | * @param {number} val - Value to divide by
209 | * @returns {Matrix} - M / val
210 | * @example
211 | * const m = new eig.Matrix([2, 4]);
212 | * return m.div(2);
213 | */
214 | div(val) { }
215 |
216 | /**
217 | * Divide by a value in place
218 | * @param {number} val - Value to divide by
219 | * @returns {Matrix} - this (M / val)
220 | * @example
221 | * const m = new eig.Matrix([2, 4]);
222 | * m.divSelf(2);
223 | * return m;
224 | */
225 | divSelf(val) { }
226 |
227 | /**
228 | * Add B
229 | * @param {Matrix} B - Matrix to be added
230 | * @returns {Matrix} M + B
231 | * @example
232 | * const M = new eig.Matrix([1, 2]);
233 | * const B = new eig.Matrix([3, 4]);
234 | * return M.matAdd(B);
235 | */
236 | matAdd(B) { }
237 |
238 | /**
239 | * Add B in place
240 | * @param {Matrix} B - Matrix to be added
241 | * @returns {Matrix} - this
242 | * @example
243 | * const M = new eig.Matrix([1, 2]);
244 | * const B = new eig.Matrix([3, 4]);
245 | * M.matAddSelf(B);
246 | * return M;
247 | */
248 | matAddSelf(B) { }
249 |
250 | /**
251 | * Substract B
252 | * @param {Matrix} B - Matrix to be substracted
253 | * @returns {Matrix} - M - B
254 | * @example
255 | * const M = new eig.Matrix([3, 4]);
256 | * const B = new eig.Matrix([1, 2]);
257 | * return M.matSub(B);
258 | */
259 | matSub(B) { }
260 |
261 | /**
262 | * Substract B in place
263 | * @param {Matrix} B - Matrix to be substracted
264 | * @returns {Matrix} - this (M - B)
265 | * @example
266 | * const M = new eig.Matrix([3, 4]);
267 | * const B = new eig.Matrix([1, 2]);
268 | * M.matSubSelf(B);
269 | * return M;
270 | */
271 | matSubSelf(B) { }
272 |
273 | /**
274 | * Multiply by a matrix B
275 | * @param {Matrix} B - Matrix to multiply M by
276 | * @returns {Matrix} - M * B
277 | * @example
278 | * const M = new eig.Matrix([3, 4]);
279 | * const B = new eig.Matrix([[1, 2]]);
280 | * return M.matMul(B);
281 | */
282 | matMul(B) { }
283 |
284 | /**
285 | * Multiplyby a matrix B in place
286 | * @param {Matrix} B - Matrix to multiply M by
287 | * @returns {Matrix} - this (M * B)
288 | * @example
289 | * const M = new eig.Matrix([3, 4]);
290 | * const B = new eig.Matrix([[1, 2]]);
291 | * M.matMulSelf(B);
292 | * return M;
293 | */
294 | matMulSelf(B) { }
295 |
296 | /**
297 | * Concatenate B horizontally
298 | * @param {Matrix} B - Matrix to be concatenated
299 | * @returns {Matrix} - [M, B]
300 | * @example
301 | * const M = new eig.Matrix([[1], [2]]);
302 | * const B = new eig.Matrix([[3], [4]]);
303 | * return M.hcat(B);
304 | */
305 | hcat(B) { }
306 |
307 | /**
308 | * Concatenate B vertically
309 | * @param {Matrix} B - Matrix to be concatenated
310 | * @returns {Matrix} - [M; B]
311 | * @example
312 | * const M = new eig.Matrix([[1], [2]]);
313 | * const B = new eig.Matrix([[3], [4]]);
314 | * return M.vcat(B);
315 | */
316 | vcat(B) { }
317 |
318 | /**
319 | * Get the transpose
320 | * @returns {Matrix} - M'
321 | * @example
322 | * const m = new eig.Matrix([[0, 1], [3, 0]]);
323 | * return m.transpose();
324 | */
325 | tranpose() { }
326 |
327 | /**
328 | * Transpose in place
329 | * @returns {Matrix} - this (M')
330 | * @example
331 | * const m = new eig.Matrix([[0, 1], [3, 0]]);
332 | * m.transpose();
333 | * return m;
334 | */
335 | transposeSelf() { }
336 |
337 | /**
338 | * Get the inverse of a square matrix
339 | * The inverse is the matrix Minv such that M * Minv = In
340 | * With In the n * n identity matrix
341 | * @warning The matrix must be square and invertible (full rank)
342 | * @returns {Matrix} - The inverse
343 | * @example
344 | * const m = new eig.Matrix([[4, 9], [3, 7]]);
345 | * return m.inverse();
346 | */
347 | inverse() { }
348 |
349 | /**
350 | * Create a m * n identity matrix
351 | * @param {number} m - Rows count
352 | * @param {number} n - Columns count
353 | * @returns {Matrix} - m * n identity matrix
354 | * @example
355 | * return eig.Matrix.identity(2, 2);
356 | */
357 | static identity(m, n) { }
358 |
359 | /**
360 | * Create a m * n matrix filled with ones
361 | * @param {number} m - Rows count
362 | * @param {number} n - Columns count
363 | * @returns {Matrix} - m * n matrix filled with ones
364 | * @example
365 | * return eig.Matrix.ones(2, 3);
366 | */
367 | static ones(m, n) { }
368 |
369 | /**
370 | * Create a m * n matrix filled with some value
371 | * @param {number} m - Rows count
372 | * @param {number} n - Columns count
373 | * @param {number} val - Value to fill the matrix with
374 | * @returns {Matrix} - m * n matrix filled with val
375 | * @example
376 | * return eig.Matrix.constant(2, 3, 3 / 2);
377 | */
378 | static constant(m, n, val) { }
379 |
380 | /**
381 | * Create a m * n random matrix
382 | * @param {number} m - Rows count
383 | * @param {number} n - Columns count
384 | * @returns {Matrix} - m * n random matrix
385 | * @example
386 | * return eig.Matrix.random(3, 2);
387 | */
388 | static random(m, n) { }
389 |
390 | /**
391 | * Create a diagonal matrix from a vector
392 | * @param {Matrix} vec - Vector from which to populate the diagonal
393 | * @returns {Matrix} - Diagonal matrix
394 | * @example
395 | * const vec = new eig.Matrix([1, 2, 3]);
396 | * return eig.Matrix.diagonal(vec);
397 | */
398 | static diagonal(vec) { }
399 |
400 | /**
401 | * Create a matrix from an array
402 | * A 1D array will create a column vector
403 | * A 2D array will create a matrix
404 | * @param {Array} arr - 1D or 2D array to build matrix from
405 | * @returns {Matrix} - Matrix initialized from the array
406 | * @example
407 | * return new eig.Matrix([[1, 2], [3, 4]]);
408 | */
409 | static fromArray(arr) { }
410 | }
411 |
412 | export { Matrix }
--------------------------------------------------------------------------------
/src/classes/Solvers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A class containing a few useful solver utilities
3 | */
4 | class Solvers {
5 | /**
6 | * Find the eigenvalues of a given matrix
7 | * Optionally, compute a set of normalized eigenvectors
8 | * @param {Matrix} M - Matrix of interest
9 | * @param {boolean} computeEigenvectors - Whether to compute eigenvectors
10 | * @returns {EigenSolverResult} - Result of the solver
11 | * @example
12 | * const M = new eig.Matrix([[8, 3], [2, 7]]);
13 | * const result = eig.Solvers.eigenSolve(M, true);
14 | * delete result.info
15 | * return result
16 | */
17 | static eigenSolve(M, computeEigenvectors) { }
18 |
19 | /**
20 | * Solve a continuous time algebraic Riccati equation of the form
21 | * A' P + P A - P B Rinv B' P + Q = 0
22 | * @param {Matrix} A - State matrix
23 | * @param {Matrix} B - Input matrix
24 | * @param {Matrix} Q - Quadratic state cost matrix
25 | * @param {Matrix} R - Quadratic input cost matrix
26 | * @returns {CareSolverResult} - Result of the solver
27 | * @example
28 | * // Finds the optimal linear quadratic regulator controller of an inverted pendulum
29 | * // The state-space dynamic of the pendulum linearized about the upside down state is
30 | * // xDot = A*x + B*u
31 | * // With x = [theta, thetaDot]
32 | * // And dThetaDot/dt = -(m*g*l*sin(theta) + u)/ml^2
33 | * // Here, we take m = 1, l = 1, g = 10
34 | * const A = new eig.Matrix([[0, 1], [-10, 0]])
35 | * const B = new eig.Matrix([0, 1])
36 | * const Q = eig.Matrix.identity(2, 2).mul(10)
37 | * const R = eig.Matrix.identity(1, 1)
38 | *
39 | * // Solve the CARE equation
40 | * const result = eig.Solvers.careSolve(A, B, Q, R)
41 | * delete result.info
42 | * return result
43 | */
44 | static careSolve(A, B, Q, R) { }
45 |
46 | /**
47 | * Solve quadratic program of the form
48 | * Min 0.5 x' P x + q' x
49 | * Suject to l <= Ax <= u
50 | * Using operator splitting quadratic programming https://osqp.org/docs/
51 | * @warning The problem needs to be convex
52 | * @param {SparseMatrix} P - Quadratic cost matrix
53 | * @param {Matrix} q - Linear cost vector
54 | * @param {SparseMatrix} A - Linear constraint matrix
55 | * @param {Matrix} l - Linear constraint lower bound
56 | * @param {Matrix} u - Linear constraint upper bound
57 | * @example
58 | * // Let's minimize 0.5 x' P x + q' x
59 | * // Suject to l <= Ax <= u
60 | * // With P = [[4, 1], [0, 2]]; q = [1, 1]
61 | * // And A = [[1, 1], [1, 0], [0, 1]]
62 | * // l = [1, 0, 0]; u = [1, 0.7, 0.7];
63 | * const P = eig.SparseMatrix.fromTriplets(2, 2, [
64 | * [0, 0, 4], [0, 1, 1], [1, 1, 2]
65 | * ]);
66 | * const A = eig.SparseMatrix.fromTriplets(3, 2, [
67 | * [0, 0, 1], [0, 1, 1], [1, 0, 1], [2, 1, 1]
68 | * ]);
69 | * const q = new eig.Matrix([1, 1])
70 | * const l = new eig.Matrix([1, 0, 0])
71 | * const u = new eig.Matrix([1, 0.7, 0.7])
72 | * return eig.Solvers.quadProgSolve(P, q, A, l, u);
73 | */
74 | static quadProgSolve(P, q, A, l, u) { }
75 | }
--------------------------------------------------------------------------------
/src/cpp/CareSolver.h:
--------------------------------------------------------------------------------
1 | #ifndef CARE_H
2 | #define CARE_H
3 |
4 | #include
5 | #include "DenseMatrix.h"
6 |
7 | class CareSolver
8 | {
9 | using MatrixXd = Eigen::MatrixXd;
10 | using ComputationInfo = Eigen::ComputationInfo;
11 |
12 | public:
13 | typedef struct {
14 | DenseMatrix K;
15 | DenseMatrix S;
16 | ComputationInfo info;
17 | } CareSolverResult;
18 |
19 | static CareSolverResult solve(const DenseMatrix &A,
20 | const DenseMatrix &B,
21 | const DenseMatrix &Q,
22 | const DenseMatrix &R)
23 | {
24 | assert(isEqual(R.data, R.data.transpose(), 1e-10));
25 | Eigen::LLT R_cholesky(R.data);
26 | if (R_cholesky.info() != Eigen::Success)
27 | throw std::runtime_error("R must be positive definite");
28 | return solve(A.data, B.data, Q.data, R_cholesky);
29 | }
30 |
31 | private:
32 | template
33 | static bool isEqual(const Eigen::MatrixBase &m1,
34 | const Eigen::MatrixBase &m2,
35 | double tolerance)
36 | {
37 | return (
38 | (m1.rows() == m2.rows()) &&
39 | (m1.cols() == m2.cols()) &&
40 | ((m1 - m2).template lpNorm() <= tolerance));
41 | }
42 |
43 | static CareSolverResult solve(
44 | const MatrixXd &A,
45 | const MatrixXd &B,
46 | const MatrixXd &Q,
47 | const Eigen::LLT &R_cholesky)
48 | {
49 | const Eigen::Index n = B.rows(), m = B.cols();
50 | assert(A.rows() == n && A.cols() == n);
51 | assert(Q.rows() == n && Q.cols() == n);
52 | assert(R_cholesky.matrixL().rows() == m &&
53 | R_cholesky.matrixL().cols() == m);
54 |
55 | assert(isEqual(Q, Q.transpose(), 1e-10));
56 |
57 | MatrixXd H(2 * n, 2 * n);
58 |
59 | H << A, B * R_cholesky.solve(B.transpose()), Q, -A.transpose();
60 |
61 | MatrixXd Z = H;
62 | MatrixXd Z_old;
63 |
64 | // these could be options
65 | const double tolerance = 1e-9;
66 | const double max_iterations = 100;
67 |
68 | double relative_norm;
69 | size_t iteration = 0;
70 |
71 | const double p = static_cast(Z.rows());
72 |
73 | do
74 | {
75 | Z_old = Z;
76 | // R. Byers. Solving the algebraic Riccati equation with the matrix sign
77 | // function. Linear Algebra Appl., 85:267–279, 1987
78 | // Added determinant scaling to improve convergence (converges in rough half
79 | // the iterations with this)
80 | double ck = std::pow(std::abs(Z.determinant()), -1.0 / p);
81 | Z *= ck;
82 | Z = Z - 0.5 * (Z - Z.inverse());
83 | relative_norm = (Z - Z_old).norm();
84 | iteration++;
85 | } while (iteration < max_iterations && relative_norm > tolerance);
86 |
87 | CareSolverResult result = (CareSolverResult) {};
88 | result.info = iteration == max_iterations ? ComputationInfo::NoConvergence : ComputationInfo::Success;
89 |
90 | MatrixXd W11 = Z.block(0, 0, n, n);
91 | MatrixXd W12 = Z.block(0, n, n, n);
92 | MatrixXd W21 = Z.block(n, 0, n, n);
93 | MatrixXd W22 = Z.block(n, n, n, n);
94 |
95 | MatrixXd lhs(2 * n, n);
96 | MatrixXd rhs(2 * n, n);
97 | MatrixXd eye = MatrixXd::Identity(n, n);
98 | lhs << W12, W22 + eye;
99 | rhs << W11 + eye, W21;
100 |
101 | Eigen::JacobiSVD svd(
102 | lhs, Eigen::ComputeThinU | Eigen::ComputeThinV);
103 |
104 | MatrixXd sol = svd.solve(rhs);
105 | result.S = DenseMatrix(sol);
106 | result.K = DenseMatrix(R_cholesky.solve(B.transpose() * sol));
107 | return result;
108 | }
109 | };
110 |
111 | #endif // CARE_H
--------------------------------------------------------------------------------
/src/cpp/Decompositions.h:
--------------------------------------------------------------------------------
1 | #ifndef DECOMPOSITIONS_H
2 | #define DECOMPOSITIONS_H
3 |
4 | #include
5 | #include "DenseMatrix.h"
6 | #include "CareSolver.h"
7 |
8 | class Decompositions
9 | {
10 | using Matrix = Eigen::Matrix;
11 | using DMD = DenseMatrix;
12 | using CMD = DenseMatrix>;
13 |
14 | public:
15 | /**
16 | * Decompositions
17 | */
18 | typedef struct
19 | {
20 | DMD L;
21 | } CholeskyResult;
22 |
23 | static CholeskyResult cholesky(const DMD &M)
24 | {
25 | Eigen::LLT llt(M.data);
26 | return (CholeskyResult){
27 | .L = DMD(llt.matrixL())
28 | };
29 | };
30 |
31 | typedef struct
32 | {
33 | DMD L;
34 | DMD U;
35 | DMD P;
36 | DMD Q;
37 | } LUResult;
38 |
39 | static LUResult lu(const DMD &M)
40 | {
41 | Eigen::FullPivLU lu(M.data);
42 | return (LUResult){
43 | .L = DMD(lu.matrixLU().triangularView()),
44 | .U = DMD(lu.matrixLU().triangularView()),
45 | .P = DMD(lu.permutationP()),
46 | .Q = DMD(lu.permutationQ()),
47 | };
48 | };
49 |
50 | typedef struct
51 | {
52 | DMD Q;
53 | DMD R;
54 | } QRResult;
55 |
56 | static QRResult qr(const DMD &M)
57 | {
58 | Eigen::HouseholderQR qr(M.data);
59 | return (QRResult){
60 | .Q = DMD(qr.householderQ()),
61 | .R = DMD(qr.matrixQR().triangularView()),
62 | };
63 | };
64 |
65 | typedef struct
66 | {
67 | DMD sv;
68 | DMD U;
69 | DMD V;
70 | } SVDResult;
71 |
72 | static SVDResult svd(const DMD &M, bool thin)
73 | {
74 | Eigen::BDCSVD svd(M.data, thin ? (Eigen::ComputeThinU | Eigen::ComputeThinV) : (Eigen::ComputeFullU | Eigen::ComputeFullV));
75 | return (SVDResult){
76 | .sv = DMD(svd.singularValues()),
77 | .U = DMD(svd.matrixU()),
78 | .V = DMD(svd.matrixV())};
79 | };
80 | };
81 |
82 | #endif // DECOMPOSITIONS_H
--------------------------------------------------------------------------------
/src/cpp/DenseMatrix.h:
--------------------------------------------------------------------------------
1 | #ifndef DENSE_MATRIX_H
2 | #define DENSE_MATRIX_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | template
9 | class DenseMatrix
10 | {
11 | using Mat = Eigen::Matrix;
12 | using Vector2d = std::vector>;
13 |
14 | protected:
15 | void assertVector(DenseMatrix &M) const
16 | {
17 | assert((M.rows() == 1 || M.cols() == 1) && "The matrix must be a vector");
18 | }
19 |
20 | void assertVector() const
21 | {
22 | assert((rows() == 1 || cols() == 1) && "The matrix must be a vector");
23 | // return assertVector(*this);
24 | }
25 |
26 | public:
27 | Mat data;
28 |
29 | DenseMatrix()
30 | {
31 | data = Mat();
32 | }
33 |
34 | DenseMatrix(int m, int n)
35 | {
36 | data = Mat::Zero(m, n);
37 | }
38 |
39 | DenseMatrix(const Mat &data_) : data(data_)
40 | {
41 | }
42 |
43 | DenseMatrix(const DenseMatrix &B) : data(B.copy())
44 | {
45 | }
46 |
47 | // DenseMatrix &operator=(const Mat &data_)
48 | // {
49 | // if (&data != &data_)
50 | // data = data_;
51 |
52 | // return *this;
53 | // }
54 |
55 | // DenseMatrix &operator=(const DenseMatrix &B)
56 | // {
57 | // if (this != &B)
58 | // data = B.copy();
59 |
60 | // return *this;
61 | // }
62 |
63 | int rows() const
64 | {
65 | return (int)data.rows();
66 | }
67 |
68 | int cols() const
69 | {
70 | return (int)data.cols();
71 | }
72 |
73 | T get(int i, int j) const
74 | {
75 | return data(i, j);
76 | }
77 |
78 | void set(int i, int j, const T &s)
79 | {
80 | data(i, j) = s;
81 | }
82 |
83 | int length() const
84 | {
85 | assertVector();
86 | return std::max(rows(), cols());
87 | }
88 |
89 | T vGet(int i) const
90 | {
91 | assertVector();
92 | return cols() == 1 ? data(i, 0) : data(0, i);
93 | }
94 |
95 | void vSet(int i, const T &s)
96 | {
97 | assertVector();
98 | *(cols() == 1 ? &data(i, 0) : &data(0, i)) = s;
99 | }
100 |
101 | T dot(const DenseMatrix &B) const
102 | {
103 | assert(length() == B.length() && "The two matrices must be same length vectors");
104 | double dot = 0;
105 | for (int k = 0; k < length(); k++)
106 | dot += vGet(k) * B.vGet(k);
107 | return dot;
108 | }
109 |
110 | double norm() const
111 | {
112 | return data.norm();
113 | }
114 |
115 | double normSqr() const
116 | {
117 | return data.squaredNorm();
118 | }
119 |
120 | double l1Norm() const
121 | {
122 | return data.template lpNorm<1>();
123 | }
124 |
125 | double lInfNorm() const
126 | {
127 | return data.template lpNorm();
128 | }
129 |
130 | double rank() const
131 | {
132 | Eigen::ColPivHouseholderQR qr(data);
133 | return qr.rank();
134 | }
135 |
136 | double det() const
137 | {
138 | return data.determinant();
139 | }
140 |
141 | T sum() const
142 | {
143 | return data.sum();
144 | }
145 |
146 | DenseMatrix block(int i, int j, int di, int dj) const
147 | {
148 | return DenseMatrix(data.block(i, j, di, dj));
149 | }
150 |
151 | void setBlock(int i, int j, DenseMatrix &block)
152 | {
153 | assert(rows() >= i + block.rows() && cols() >= j + block.cols() && "The matrix doens't fit");
154 | for (int ii = 0; ii < block.rows(); ii++)
155 | {
156 | for (int jj = 0; jj < block.cols(); jj++)
157 | {
158 | data(i + ii, j + jj) = block.data(ii, jj);
159 | }
160 | }
161 | }
162 |
163 | Mat copy() const
164 | {
165 | return data;
166 | }
167 |
168 | Mat &toEigen()
169 | {
170 | return data;
171 | }
172 |
173 | DenseMatrix mul(const T &s)
174 | {
175 | return DenseMatrix(data * s);
176 | }
177 |
178 | void mulSelf(const T &s)
179 | {
180 | data *= s;
181 | }
182 |
183 | DenseMatrix div(const T &s)
184 | {
185 | return DenseMatrix(data / s);
186 | }
187 |
188 | void divSelf(const T &s)
189 | {
190 | data /= s;
191 | }
192 |
193 | DenseMatrix matAdd(const DenseMatrix *B)
194 | {
195 | return DenseMatrix(data + B->data);
196 | }
197 |
198 | void matAddSelf(const DenseMatrix *B)
199 | {
200 | data += B->data;
201 | }
202 |
203 | DenseMatrix matSub(const DenseMatrix *B)
204 | {
205 | return DenseMatrix(data - B->data);
206 | }
207 |
208 | void matSubSelf(const DenseMatrix *B)
209 | {
210 | data -= B->data;
211 | }
212 |
213 | DenseMatrix matMul(const DenseMatrix *B)
214 | {
215 | return DenseMatrix(data * B->data);
216 | }
217 |
218 | void matMulSelf(const DenseMatrix *B)
219 | {
220 | data *= B->data;
221 | }
222 |
223 | DenseMatrix hcat(DenseMatrix *B)
224 | {
225 | int m = data.rows();
226 | int n1 = data.cols();
227 | int n2 = B->data.cols();
228 | DenseMatrix C(m, n1 + n2);
229 |
230 | for (int i = 0; i < m; i++)
231 | {
232 | for (int j = 0; j < n1; j++)
233 | {
234 | C.set(i, j, data(i, j));
235 | }
236 |
237 | for (int j = 0; j < n2; j++)
238 | {
239 | C.set(i, n1 + j, B->data(i, j));
240 | }
241 | }
242 |
243 | return C;
244 | }
245 |
246 | DenseMatrix vcat(DenseMatrix *B)
247 | {
248 | int m1 = data.rows();
249 | int m2 = B->data.rows();
250 | int n = data.cols();
251 | DenseMatrix C(m1 + m2, n);
252 |
253 | for (int j = 0; j < n; j++)
254 | {
255 | for (int i = 0; i < m1; i++)
256 | {
257 | C.set(i, j, data(i, j));
258 | }
259 |
260 | for (int i = 0; i < m2; i++)
261 | {
262 | C.set(m1 + i, j, B->data(i, j));
263 | }
264 | }
265 |
266 | return C;
267 | }
268 |
269 | DenseMatrix transpose() const
270 | {
271 | return DenseMatrix(data.transpose());
272 | }
273 |
274 | DenseMatrix &transposeSelf()
275 | {
276 | std::cout << "working on: " << this << " data: " << &data << std::endl;
277 | data.transposeInPlace();
278 | return *this;
279 | }
280 |
281 | DenseMatrix inverse() const
282 | {
283 | return DenseMatrix(data.inverse());
284 | }
285 |
286 | DenseMatrix conjugate() const
287 | {
288 | return DenseMatrix(data.conjugate());
289 | }
290 |
291 | DenseMatrix clamp(T lo, T hi)
292 | {
293 | DenseMatrix clamped = DenseMatrix(data);
294 | for (int i = 0; i < rows(); i++)
295 | for (int j = 0; j < cols(); j++)
296 | clamped.data(i, j) = std::max(lo, std::min(hi, clamped.data(i, j)));
297 | return clamped;
298 | }
299 |
300 | void clampSelf(T lo, T hi)
301 | {
302 | for (int i = 0; i < rows(); i++)
303 | for (int j = 0; j < cols(); j++)
304 | data(i, j) = std::max(lo, std::min(hi, data(i, j)));
305 | }
306 |
307 | void print(const std::string title = "") const
308 | {
309 | if (title.length())
310 | std::cout << title << std::endl;
311 | const int rows = data.rows();
312 | const int cols = data.cols();
313 | for (int i = 0; i < rows; i++)
314 | {
315 | printf(i == 0 ? "[[" : " [");
316 | for (int j = 0; j < cols; j++)
317 | {
318 | if (std::is_same>::value)
319 | {
320 | printf("%.2f + %.2fi", std::real(get(i, j)), std::imag(get(i, j)));
321 | }
322 | else
323 | {
324 | printf("%.2f", std::real(get(i, j)));
325 | }
326 | printf(j < cols - 1 ? ", " : "");
327 | }
328 | printf((i < rows - 1 ? "]" : "]]"));
329 | printf("\n");
330 | }
331 | }
332 |
333 | static DenseMatrix identity(int m, int n)
334 | {
335 | return DenseMatrix(Mat::Identity(m, n));
336 | }
337 |
338 | static DenseMatrix ones(int m, int n)
339 | {
340 | return DenseMatrix(Mat::Ones(m, n));
341 | }
342 |
343 | static DenseMatrix constant(int m, int n, const T &x)
344 | {
345 | return DenseMatrix(Mat::Constant(m, n, x));
346 | }
347 |
348 | static DenseMatrix random(int m, int n)
349 | {
350 | return DenseMatrix(Mat::Random(m, n));
351 | }
352 |
353 | static DenseMatrix diagonal(const DenseMatrix vector)
354 | {
355 | int n = vector.length();
356 | DenseMatrix mat(n, n);
357 | for (int k = 0; k < n; k++)
358 | {
359 | mat.set(k, k, vector.vGet(k));
360 | }
361 | return mat;
362 | }
363 |
364 | static DenseMatrix fromVector(const Vector2d &v)
365 | {
366 | const size_t m = v.size();
367 | const size_t n = m > 0 ? v[0].size() : 0;
368 | Mat mat(m, n);
369 | for (size_t i = 0; i < m; i++)
370 | {
371 | assert(v[i].size() == n && "All the rows must have the same size");
372 | for (size_t j = 0; j < n; j++)
373 | mat(i, j) = v[i][j];
374 | }
375 | return mat;
376 | }
377 | };
378 |
379 | #endif // DENSE_MATRIX
--------------------------------------------------------------------------------
/src/cpp/QuadProgOld.h:
--------------------------------------------------------------------------------
1 | #ifndef QUADPROGSOLVER_H
2 | #define QUADPROGSOLVER_H
3 |
4 | #include
5 | #include
6 | #include "DenseMatrix.h"
7 | #include "Array.hh"
8 | #undef inverse // fix array.hh inverse directive
9 |
10 | // using namespace std;
11 | // using namespace Eigen;
12 |
13 |
14 |
15 | class QuadProgSolver
16 | {
17 | using DMD = DenseMatrix;
18 | using QMD = quadprogpp::Matrix;
19 | using QVD = quadprogpp::Vector;
20 |
21 | public:
22 | // The problem is in the form:
23 | // min 0.5 * x G x + g0 x
24 | // s.t.
25 | // CE^T x + ce0 = 0
26 | // CI^T x + ci0 >= 0
27 | //
28 | // With
29 | // x: n
30 | // G: n * n; g0: n
31 | // CE: n * p; ce0: p
32 | // CI: n * m; ci0: m
33 | static double solve(DMD &G, DMD& g0, const DMD& CE, const DMD &ce0, const DMD &CI, const DMD &ci0, DMD &x) {
34 | int n = G.rows();
35 | int m = CE.cols();
36 | int p = CI.cols();
37 | assert(G.cols() == n && "G must be a square matrix");
38 | assert(g0.rows() == n && "g0 and G must have the same number of rows");
39 | assert(CE.rows() == n && "CE and G must have the same number of rows");
40 | assert(CE.cols() == std::max(ce0.rows(), ce0.cols()) && "CE and ce0 must have the same number of columns");
41 | assert(CI.rows() == n && "CI and G must have the same number of rows");
42 | assert(CI.cols() == std::max(ci0.rows(), ci0.cols()) && "CI and ci0 must have the same number of columns");
43 | QMD _G = getMatrix(G);
44 | QVD _g0 = getVector(g0);
45 | QMD _CE = getMatrix(CE);
46 | QVD _ce0 = getVector(ce0);
47 | QMD _CI = getMatrix(CI);
48 | QVD _ci0 = getVector(ci0);
49 | QVD _x = getVector(x);
50 | double res = quadprogpp::solve_quadprog(_G, _g0, _CE, _ce0, _CI, _ci0, _x);
51 | copy(_G, G);
52 | copy(_g0, g0);
53 | copy(_x, x);
54 | return res;
55 | }
56 |
57 | private:
58 | static QMD getMatrix(const DMD &M) {
59 | QMD mat(M.rows(), M.cols());
60 | for (size_t i = 0; i < M.rows(); i++)
61 | for (size_t j =0; j < M.cols(); j++)
62 | mat[i][j] = M.get(i, j);
63 | return mat;
64 | }
65 |
66 | static QVD getVector(const DMD &V) {
67 | assert((V.rows() == 1 || V.cols() == 1) && "The matrix must be a vector");
68 | QVD vec(std::max(V.rows(), V.cols()));
69 | for (size_t k = 0; k < std::max(V.rows(), V.cols()); k++) {
70 | vec[k] = V.vGet(k);
71 | }
72 | return vec;
73 | }
74 |
75 | static void copy(const QMD &M, DMD &target) {
76 | assert(M.nrows() == target.rows() && M.ncols() == target.cols() && "Matrices must be the same size");
77 | for (size_t i = 0; i < M.nrows(); i++)
78 | for (size_t j =0; j < M.ncols(); j++)
79 | target.set(i, j, M[i][j]);
80 | }
81 |
82 | static void copy(const QVD &V, DMD &target) {
83 | assert(V.size() == std::max(target.rows(), target.cols()) && "Vectors must be the same size");
84 | for (size_t k = 0; k < V.size(); k++) {
85 | target.vSet(k, V[k]);
86 | }
87 | }
88 | };
89 |
90 | #endif // QUADPROGSOLVER_H
--------------------------------------------------------------------------------
/src/cpp/QuadProgSolver.h:
--------------------------------------------------------------------------------
1 | #ifndef QUADPROGSOLVER
2 | #define QUADPROGSOLVER
3 |
4 | #include
5 | #include "SparseMatrix.h"
6 | #include "DenseMatrix.h"
7 | #include "osqp.h"
8 | #include
9 |
10 | #define PRINT_MAT(name, M, size) \
11 | printf("%s: [", name); \
12 | for (int k = 0; k < (size); k++) \
13 | printf("%.2f,", (double)(M)[k]); \
14 | printf("]\n");
15 |
16 | class QuadProgSolver
17 | {
18 | using T = Eigen::Triplet;
19 | using DMD = DenseMatrix;
20 | using SMD = SparseMatrix;
21 |
22 | public:
23 | // Minimize 0.5 xT.P.x + qT.x
24 | // Suject to l <= Ax <= u
25 | static DMD solve(SMD &P, DMD &q, SMD &A, DMD &l, DMD &u) {
26 | assert(P.rows() == P.cols() && "P must be a square matrix");
27 | assert(q.rows() == P.rows() && "q and P must have the same number of rows");
28 | assert(A.cols() == P.rows() && "A.cols must equal P.rows");
29 | assert(l.rows() == A.rows() && "l and A must have the same number of rows");
30 | assert(u.rows() == A.rows() && "u and A must have the same number of rows");
31 |
32 |
33 | int n = P.rows();
34 | int m = A.rows();
35 | csc *P_ = sparseToCSC(P);
36 | csc *A_ = sparseToCSC(A);
37 | double *q_ = denseToArray(q);
38 | double *l_ = denseToArray(l);
39 | double *u_ = denseToArray(u);
40 |
41 | // Workspace settings
42 | OSQPSettings settings;
43 | osqp_set_default_settings(&settings);
44 | // settings.max_iter = 10;
45 | settings.verbose = 0;
46 |
47 | OSQPData data = {
48 | .n = n,
49 | .m = m,
50 | .P = P_,
51 | .A = A_,
52 | .q = q_,
53 | .l = l_,
54 | .u = u_};
55 |
56 | OSQPWorkspace *work;
57 | int exitflag = osqp_setup(&work, &data, &settings);
58 | DMD x(n, 1);
59 | if (!exitflag)
60 | {
61 | osqp_solve(work);
62 | double *xArr = work->solution->x;
63 | for (int k = 0; k < n; k++)
64 | x.set(k, 0, xArr[k]);
65 | // x.print("solution");
66 | // PRINT_MAT("x", work->solution->x, n);
67 | }
68 | osqp_cleanup(work);
69 | // Free matrices
70 | free(P_); // Don't csc_free as sparse matrix pointers are being used
71 | free(A_); // Don't csc_free as sparse matrix pointers are being used
72 | free(q_);
73 | free(l_);
74 | free(u_);
75 | return x;
76 | }
77 |
78 |
79 | static void solveSparse()
80 | {
81 | TripletVector tripletsP(3);
82 | tripletsP.add(0, 0, 4);
83 | tripletsP.add(0, 1, 1);
84 | tripletsP.add(1, 1, 2);
85 | SMD P(2, 2, &tripletsP);
86 | // P.data.setFromTriplets(triplets.begin(), triplets.end());
87 |
88 | TripletVector tripletsA(4);
89 | tripletsA.add(0, 0, 1);
90 | tripletsA.add(0, 1, 1);
91 | tripletsA.add(1, 0, 1);
92 | tripletsA.add(2, 1, 1);
93 | SMD A(3, 2, &tripletsA);
94 | // A.data.setFromTriplets(triplets.begin(), triplets.end());
95 |
96 | // Get lists
97 | // const int nnz = mat.nonZeros();
98 | // PRINT_MAT("Values", mat.valuePtr(), nnz);
99 | // PRINT_MAT("Inner indices", mat.innerIndexPtr(), nnz);
100 | // const int nCols = mat.cols();
101 | // PRINT_MAT("Outer index ptr", mat.outerIndexPtr(), nCols + 1);
102 |
103 | // Build csc from matrix
104 | csc *P_csc = sparseToCSC(P);
105 | csc *A_csc = sparseToCSC(A);
106 | double q[2] = {1.0, 1.0};
107 | double l[3] = {1.0, 0.0, 0.0};
108 | double u[3] = {1.0, 0.7, 0.7};
109 | int n = 2;
110 | int m = 3;
111 |
112 | // Workspace settings
113 | OSQPSettings settings;
114 | osqp_set_default_settings(&settings);
115 | // settings.max_iter = 10;
116 | settings.verbose = 0;
117 |
118 | OSQPData data = {
119 | .n = n,
120 | .m = m,
121 | .P = P_csc,
122 | .A = A_csc,
123 | .q = q,
124 | .l = l,
125 | .u = u};
126 |
127 | OSQPWorkspace *work;
128 | int exitflag = osqp_setup(&work, &data, &settings);
129 | if (!exitflag)
130 | {
131 | osqp_solve(work);
132 | // PRINT_MAT("x", work->solution->x, n);
133 | }
134 | osqp_cleanup(work);
135 | // Free matrices
136 | c_free(P_csc);
137 | c_free(A_csc);
138 | }
139 |
140 | static double* denseToArray(DMD mat) {
141 | double *arr = (double *) malloc(mat.rows() * sizeof(double));
142 | for (int k = 0; k < mat.rows(); k++) {
143 | arr[k] = mat.get(k, 0);
144 | }
145 | return arr;
146 | }
147 |
148 | static csc *sparseToCSC(SMD &mat)
149 | {
150 | return csc_matrix(mat.rows(), mat.cols(), mat.nonZeros(), mat.valuePtr(), mat.innerIndexPtr(), mat.outerIndexPtr());
151 | }
152 |
153 | static void solveBasic()
154 | {
155 | // Load problem data
156 | // P = [[4, 1]
157 | // [0, 2]]
158 | double P_x[3] = {4.0, 1.0, 2.0};
159 | int P_nnz = 3;
160 | int P_i[3] = {0, 0, 1};
161 | int P_p[3] = {0, 1, 3};
162 | double q[2] = {1.0, 1.0};
163 | // A = [[1 1
164 | // 1 0
165 | // 0 1]]
166 | double A_x[4] = {1.0, 1.0, 1.0, 1.0};
167 | int A_nnz = 4;
168 | int A_i[4] = {0, 1, 0, 2};
169 | int A_p[3] = {0, 2, 4};
170 | double l[3] = {1.0, 0.0, 0.0};
171 | double u[3] = {1.0, 0.7, 0.7};
172 | int n = 2;
173 | int m = 3;
174 |
175 | // Exitflag
176 | int exitflag = 0;
177 |
178 | // Workspace structures
179 | OSQPSettings settings;
180 | OSQPData data;
181 |
182 | // Populate data
183 | data.n = n;
184 | data.m = m;
185 | data.P = csc_matrix(data.n, data.n, P_nnz, P_x, P_i, P_p);
186 | data.q = q;
187 | data.A = csc_matrix(data.m, data.n, A_nnz, A_x, A_i, A_p);
188 | data.l = l;
189 | data.u = u;
190 |
191 | // Define solver settings as default
192 | osqp_set_default_settings(&settings);
193 | settings.alpha = 1.0; // Change alpha parameter
194 | // settings.max_iter = 10;
195 | settings.verbose = 0;
196 |
197 | // Setup workspace
198 | OSQPWorkspace *work;
199 | exitflag = osqp_setup(&work, &data, &settings);
200 |
201 | // Solve Problem
202 | osqp_solve(work);
203 | // PRINT_MAT("x", work->solution->x, n);
204 |
205 | // Cleanup
206 | osqp_cleanup(work);
207 | free(data.P);
208 | free(data.A);
209 | // return exitflag;
210 | }
211 |
212 | // private:
213 | };
214 |
215 | #endif // QUADPROGSOLVER
--------------------------------------------------------------------------------
/src/cpp/Random.h:
--------------------------------------------------------------------------------
1 | #ifndef RANDOM_H
2 | #define RANDOM_H
3 |
4 | #include /* srand, rand */
5 | #include
6 | #include "DenseMatrix.h"
7 | #include "CareSolver.h"
8 | #include "contrib/multivariateNormal.h"
9 |
10 |
11 | class Seed {
12 | public:
13 | Seed() {
14 | std::srand(time(NULL)); // Seed the random number generator
15 | }
16 |
17 | uint64_t get() {
18 | return std::rand();
19 | }
20 | };
21 |
22 | class Random
23 | {
24 | using Matrix = Eigen::Matrix;
25 | using DMD = DenseMatrix;
26 | static Seed seed;
27 |
28 | public:
29 | /**
30 | * Eigen solver
31 | */
32 | typedef struct {
33 | Eigen::ComputationInfo info;
34 | } EigenSolverResult;
35 |
36 | static DMD normal(const DMD &mean, const DMD &cov, int samples)
37 | {
38 | Eigen::EigenMultivariateNormal emn(mean.data, cov.data, false, seed.get());
39 | Matrix res = emn.samples(samples);
40 | return DMD(res);
41 | };
42 | };
43 |
44 | #endif // RANDOM_H
--------------------------------------------------------------------------------
/src/cpp/SimplicialCholesky.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include "DenseMatrix.h"
6 |
7 | template
8 | class SimplicialCholesky
9 | {
10 | public:
11 | SimplicialCholesky(const MatrixWrapType &m): m_(&m.data) {
12 | data = new Eigen::SimplicialCholesky( *m_ );
13 | }
14 |
15 | SimplicialCholesky(const SimplicialCholesky &s) {
16 | m_ = s.m_;
17 | data = new Eigen::SimplicialCholesky(*m_);
18 | }
19 |
20 | ~SimplicialCholesky() {
21 | delete data;
22 | }
23 |
24 | SimplicialCholesky& operator=(const SimplicialCholesky& s) {
25 | if (data) {
26 | delete data;
27 | }
28 | m_ = s.m_;
29 | data = new Eigen::SimplicialCholesky(*m_);
30 | }
31 |
32 | DenseMatrix solve(DenseMatrix &y) {
33 | return DenseMatrix(data->solve(y.data));
34 | }
35 |
36 | protected:
37 |
38 | Eigen::SimplicialCholesky *data;
39 | const MatrixType *m_;
40 |
41 | };
--------------------------------------------------------------------------------
/src/cpp/Solvers.h:
--------------------------------------------------------------------------------
1 | #ifndef SOLVERS_H
2 | #define SOLVERS_H
3 |
4 | #include
5 | #include "DenseMatrix.h"
6 | #include "CareSolver.h"
7 | #include "SimplicialCholesky.h"
8 | #ifndef NO_OSQP
9 | #include "QuadProgSolver.h"
10 | #endif
11 |
12 | class Solvers
13 | {
14 | using Matrix = Eigen::Matrix;
15 | using DMD = DenseMatrix;
16 | using CMD = DenseMatrix>;
17 | using SMD = SparseMatrix;
18 |
19 | public:
20 | /**
21 | * SimplicialCholesky solver
22 | */
23 | static SimplicialCholesky> createSimplicialCholeskySolver(SMD &matrix) {
24 | return SimplicialCholesky>(matrix);
25 | }
26 |
27 | /**
28 | * Eigen solver
29 | */
30 | typedef struct {
31 | Eigen::ComputationInfo info;
32 | CMD eigenvalues;
33 | CMD eigenvectors;
34 | } EigenSolverResult;
35 |
36 | static EigenSolverResult eigenSolve(const DMD &matrix, bool computeEigenvectors = true)
37 | {
38 | // const EigenBase &matrix
39 | Eigen::EigenSolver data = Eigen::EigenSolver(matrix.data, computeEigenvectors);
40 | // return EigenSolverResult(data);
41 | return (EigenSolverResult){
42 | .info = data.info(),
43 | .eigenvalues = CMD(data.eigenvalues()),
44 | .eigenvectors = CMD(data.eigenvectors())
45 | };
46 | };
47 |
48 | /**
49 | * Care solver
50 | */
51 | static CareSolver::CareSolverResult careSolve(const DMD &A, const DMD &B, const DMD &Q, const DMD &R)
52 | {
53 | return CareSolver::solve(A, B, Q, R);
54 | };
55 |
56 | #ifndef NO_OSQP
57 | /**
58 | * Quadratic program solver
59 | */
60 | static DMD quadProgSolve(SMD &P, DMD &q, SMD &A, DMD &l, DMD &u) {
61 | return QuadProgSolver::solve(P, q, A, l, u);
62 | };
63 | #endif
64 | };
65 |
66 | #endif // SOLVERS_H
--------------------------------------------------------------------------------
/src/cpp/SparseMatrix.h:
--------------------------------------------------------------------------------
1 | #ifndef SPARSE_MATRIX_H
2 | #define SPARSE_MATRIX_H
3 |
4 | #include
5 | // #include
6 | // #include
7 | // #include
8 | // #include
9 | #include "DenseMatrix.h"
10 | #include "SimplicialCholesky.h"
11 |
12 | template
13 | class TripletVector;
14 |
15 | template
16 | class SparseMatrix
17 | {
18 | public:
19 | SparseMatrix(int m, int n) {
20 | data = Eigen::SparseMatrix(m, n);
21 | }
22 |
23 | SparseMatrix(int m, int n, TripletVector *tripletVector) : data(m, n)
24 | {
25 | std::vector> &eigenTriplet = tripletVector->data;
26 | data.setFromTriplets(eigenTriplet.begin(), eigenTriplet.end());
27 | }
28 |
29 | SparseMatrix(const Eigen::SparseMatrix &data_) : data(data_)
30 | {
31 | }
32 |
33 | SparseMatrix(const SparseMatrix &B) : data(B.copy())
34 | {
35 | }
36 |
37 | SparseMatrix &operator=(const Eigen::SparseMatrix &data_)
38 | {
39 | if (&data != &data_)
40 | {
41 | // factorization.clear();
42 | data = data_;
43 | }
44 |
45 | return *this;
46 | }
47 |
48 | SparseMatrix &operator=(const SparseMatrix &B)
49 | {
50 | if (this != &B)
51 | {
52 | // factorization.clear();
53 | data = B.copy();
54 | }
55 |
56 | return *this;
57 | }
58 |
59 | static SparseMatrix identity(int m, int n)
60 | {
61 | Eigen::SparseMatrix id(m, n);
62 | id.setIdentity();
63 |
64 | return SparseMatrix(id);
65 | }
66 |
67 | static SparseMatrix diag(const DenseMatrix &d)
68 | {
69 | TripletVector triplet(d.rows());
70 | for (int i = 0; i < d.rows(); i++)
71 | triplet.add(i, i, d.get(i, 0));
72 |
73 | return SparseMatrix(d.rows(), d.rows(), &triplet);
74 | }
75 |
76 | SparseMatrix transpose() const
77 | {
78 | return SparseMatrix(data.transpose());
79 | }
80 |
81 | SparseMatrix conjugate() const
82 | {
83 | return SparseMatrix(data.conjugate());
84 | }
85 |
86 | int rows() const
87 | {
88 | return (int)data.rows();
89 | }
90 |
91 | int cols() const
92 | {
93 | return (int)data.cols();
94 | }
95 |
96 | int nonZeros() const
97 | {
98 | return (int)data.nonZeros();
99 | }
100 |
101 | T *valuePtr()
102 | {
103 | return data.valuePtr();
104 | }
105 |
106 | int *innerIndexPtr()
107 | {
108 | return data.innerIndexPtr();
109 | }
110 |
111 | int *outerIndexPtr()
112 | {
113 | return data.outerIndexPtr();
114 | }
115 |
116 | double frobeniusNorm() const
117 | {
118 | return data.norm();
119 | }
120 |
121 | SparseMatrix block(int r0, int r1, int c0, int c1) const
122 | {
123 | return SparseMatrix(data.block(r0, c0, r1 - r0, c1 - c0));
124 | }
125 |
126 | DenseMatrix toDense() const
127 | {
128 | return DenseMatrix(Eigen::Matrix(data));
129 | }
130 |
131 | Eigen::SparseMatrix copy() const
132 | {
133 | return data;
134 | }
135 |
136 | Eigen::SparseMatrix &toEigen()
137 | {
138 | return data;
139 | }
140 |
141 | SparseMatrix mul(const T &s)
142 | {
143 | return SparseMatrix(data * s);
144 | }
145 |
146 | void mulSelf(const T &s)
147 | {
148 | data *= s;
149 | // factorization.clearNumeric();
150 | }
151 |
152 | SparseMatrix div(const T &s)
153 | {
154 | return SparseMatrix(data / s);
155 | }
156 |
157 | void divSelf(const T &s)
158 | {
159 | data /= s;
160 | // factorization.clearNumeric();
161 | }
162 |
163 | SparseMatrix matAdd(SparseMatrix *B)
164 | {
165 | return SparseMatrix(data + B->data);
166 | }
167 |
168 | void matAddSelf(SparseMatrix *B)
169 | {
170 | data += B->data;
171 | // factorization.clear();
172 | }
173 |
174 | SparseMatrix matSub(SparseMatrix *B)
175 | {
176 | return SparseMatrix(data - B->data);
177 | }
178 |
179 | void matSubSelf(SparseMatrix *B)
180 | {
181 | data -= B->data;
182 | // factorization.clear();
183 | }
184 |
185 | SparseMatrix matMul(SparseMatrix *B)
186 | {
187 | return SparseMatrix(data * B->data);
188 | }
189 |
190 | DenseMatrix vecMul(DenseMatrix *B)
191 | {
192 | return DenseMatrix(data * B->data);
193 | }
194 |
195 | T get(int i, int j)
196 | {
197 | return data.coeffRef(i, j);
198 | }
199 |
200 | void set(int i, int j, const T &s)
201 | {
202 | data.coeffRef(i, j) = s;
203 | }
204 |
205 | // void matMulSelf(SparseMatrix &B)
206 | // {
207 | // data *= B.data;
208 | // // factorization.clear();
209 | // }
210 |
211 | // Eigen::SparseCholesky *chol()
212 | // {
213 | // return &factorization.llt;
214 | // }
215 |
216 | // Eigen::SparseLU *lu()
217 | // {
218 | // return &factorization.lu;
219 | // }
220 |
221 | // Eigen::SparseQR *qr()
222 | // {
223 | // return &factorization.qr;
224 | // }
225 |
226 | void print(const std::string title = "")
227 | {
228 | if (title.length())
229 | {
230 | std::cout << title << std::endl;
231 | }
232 | const int rows = data.rows();
233 | const int cols = data.cols();
234 | for (int i = 0; i < rows; i++)
235 | {
236 | printf(i == 0 ? "[[" : " [");
237 | for (int j = 0; j < cols; j++)
238 | {
239 | printf("%.2f", get(i, j));
240 | printf(j < cols - 1 ? ", " : "");
241 | }
242 | printf((i < rows - 1 ? "]" : "]]"));
243 | printf("\n");
244 | }
245 | }
246 |
247 | protected:
248 | // members
249 | // Eigen::SparseFactorization factorization;
250 |
251 | friend class SimplicialCholesky, Eigen::SparseMatrix>;
252 |
253 | Eigen::SparseMatrix data;
254 | };
255 |
256 | template
257 | class TripletVector
258 | {
259 | public:
260 | TripletVector(int capacity)
261 | {
262 | data.reserve(capacity);
263 | }
264 |
265 | void add(int i, int j, const T &x)
266 | {
267 | if (x > 1e-8 || x < -1e-8)
268 | data.push_back(Eigen::Triplet(i, j, x));
269 | }
270 |
271 | void addBlock(int i, int j, const DenseMatrix &mat)
272 | {
273 | for (int ii = 0; ii < mat.rows(); ii++)
274 | for (int jj = 0; jj < mat.cols(); jj++)
275 | add(i + ii, j + jj, mat.get(ii, jj));
276 | }
277 |
278 | void addDiag(int i, int j, const DenseMatrix &diag)
279 | {
280 | assert(diag.cols() == 1 && "The input matrix must be a vector");
281 | for (int k = 0; k < diag.rows(); k++)
282 | add(i + k, j + k, diag.get(k, 0));
283 | }
284 |
285 | protected:
286 | std::vector> data;
287 | friend SparseMatrix;
288 | };
289 |
290 | #endif // SPARSE_MATRIX_H
291 |
--------------------------------------------------------------------------------
/src/cpp/contrib/multivariateNormal.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Multivariate Normal distribution sampling using C++11 and Eigen matrices.
3 | *
4 | * This is taken from http://stackoverflow.com/questions/16361226/error-while-creating-object-from-templated-class
5 | * (also see http://lost-found-wandering.blogspot.fr/2011/05/sampling-from-multivariate-normal-in-c.html)
6 | *
7 | * I have been unable to contact the original author, and I've performed
8 | * the following modifications to the original code:
9 | * - removal of the dependency to Boost, in favor of straight C++11;
10 | * - ability to choose from Solver or Cholesky decomposition (supposedly faster);
11 | * - fixed Cholesky by using LLT decomposition instead of LDLT that was not yielding
12 | * a correctly rotated variance
13 | * (see this http://stats.stackexchange.com/questions/48749/how-to-sample-from-a-multivariate-normal-given-the-pt-ldlt-p-decomposition-o )
14 | */
15 |
16 | /**
17 | * Copyright (c) 2014 by Emmanuel Benazera beniz@droidnik.fr, All rights reserved.
18 | *
19 | * This library is free software; you can redistribute it and/or
20 | * modify it under the terms of the GNU Lesser General Public
21 | * License as published by the Free Software Foundation; either
22 | * version 3.0 of the License, or (at your option) any later version.
23 | *
24 | * This library is distributed in the hope that it will be useful,
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 | * Lesser General Public License for more details.
28 | *
29 | * You should have received a copy of the GNU Lesser General Public
30 | * License along with this library.
31 | */
32 |
33 | #ifndef MULTIVARIATE_NORMAL_H
34 | #define MULTIVARIATE_NORMAL_H
35 |
36 | #include
37 | #include
38 |
39 | /*
40 | We need a functor that can pretend it's const,
41 | but to be a good random number generator
42 | it needs mutable state. The standard Eigen function
43 | Random() just calls rand(), which changes a global
44 | variable.
45 | */
46 | namespace Eigen {
47 | namespace internal {
48 | template
49 | struct scalar_normal_dist_op
50 | {
51 | static std::mt19937 rng; // The uniform pseudo-random algorithm
52 | mutable std::normal_distribution norm; // gaussian combinator
53 |
54 | EIGEN_EMPTY_STRUCT_CTOR(scalar_normal_dist_op)
55 |
56 | template
57 | inline const Scalar operator() (Index, Index = 0) const { return norm(rng); }
58 | inline void seed(const uint64_t &s) { rng.seed(s); }
59 | };
60 |
61 | template
62 | std::mt19937 scalar_normal_dist_op::rng;
63 |
64 | template
65 | struct functor_traits >
66 | { enum { Cost = 50 * NumTraits::MulCost, PacketAccess = false, IsRepeatable = false }; };
67 |
68 | } // end namespace internal
69 |
70 | /**
71 | Find the eigen-decomposition of the covariance matrix
72 | and then store it for sampling from a multi-variate normal
73 | */
74 | template
75 | class EigenMultivariateNormal
76 | {
77 | Matrix _covar;
78 | Matrix _transform;
79 | Matrix< Scalar, Dynamic, 1> _mean;
80 | internal::scalar_normal_dist_op randN; // Gaussian functor
81 | bool _use_cholesky;
82 | SelfAdjointEigenSolver > _eigenSolver; // drawback: this creates a useless eigenSolver when using Cholesky decomposition, but it yields access to eigenvalues and vectors
83 |
84 | public:
85 | EigenMultivariateNormal(const Matrix& mean,const Matrix& covar,
86 | const bool use_cholesky=false,const uint64_t &seed=std::mt19937::default_seed)
87 | :_use_cholesky(use_cholesky)
88 | {
89 | randN.seed(seed);
90 | setMean(mean);
91 | setCovar(covar);
92 | }
93 |
94 | void setMean(const Matrix& mean) { _mean = mean; }
95 | void setCovar(const Matrix& covar)
96 | {
97 | _covar = covar;
98 |
99 | // Assuming that we'll be using this repeatedly,
100 | // compute the transformation matrix that will
101 | // be applied to unit-variance independent normals
102 |
103 | if (_use_cholesky)
104 | {
105 | Eigen::LLT > cholSolver(_covar);
106 | // We can only use the cholesky decomposition if
107 | // the covariance matrix is symmetric, pos-definite.
108 | // But a covariance matrix might be pos-semi-definite.
109 | // In that case, we'll go to an EigenSolver
110 | if (cholSolver.info()==Eigen::Success)
111 | {
112 | // Use cholesky solver
113 | _transform = cholSolver.matrixL();
114 | }
115 | else
116 | {
117 | throw std::runtime_error("Failed computing the Cholesky decomposition. Use solver instead");
118 | }
119 | }
120 | else
121 | {
122 | _eigenSolver = SelfAdjointEigenSolver >(_covar);
123 | _transform = _eigenSolver.eigenvectors()*_eigenSolver.eigenvalues().cwiseMax(0).cwiseSqrt().asDiagonal();
124 | }
125 | }
126 |
127 | /// Draw nn samples from the gaussian and return them
128 | /// as columns in a Dynamic by nn matrix
129 | Matrix samples(int nn)
130 | {
131 | return (_transform * Matrix::NullaryExpr(_covar.rows(),nn,randN)).colwise() + _mean;
132 | }
133 | }; // end class EigenMultivariateNormal
134 | } // end namespace Eigen
135 | #endif // MULTIVARIATE_NORMAL_H
--------------------------------------------------------------------------------
/src/cpp/embind.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "DenseMatrix.h"
6 | #include "SparseMatrix.h"
7 | #include "CareSolver.h"
8 | #include "Solvers.h"
9 | #include "Decompositions.h"
10 | #include "SimplicialCholesky.h"
11 | #ifndef NO_OSQP
12 | #include "QuadProgSolver.h"
13 | #endif
14 | #include "Random.h"
15 |
16 | using namespace std;
17 | using namespace emscripten;
18 |
19 | using DDM = DenseMatrix;
20 | using CDM = DenseMatrix>;
21 | using SDM = SparseMatrix;
22 | using SDMSCholesky = SimplicialCholesky>;
23 |
24 | EMSCRIPTEN_BINDINGS(Module)
25 | {
26 | // Double vector
27 | emscripten::register_vector("Vector");
28 | emscripten::register_vector>("Vector2d");
29 |
30 | // Complex numbers
31 | class_>("Complex")
32 | .constructor()
33 | .function("real", select_overload &)>(&real))
34 | .function("imag", select_overload &)>(&imag));
35 | // emscripten::function("real", select_overload &)>(&real)); // TODO: extract in complex class
36 | // emscripten::function("imag", select_overload &)>(&imag)); // TODO: extract in complex class
37 |
38 | // Dense Matrix
39 | class_("Matrix") // TODO: rename
40 | .constructor()
41 | .constructor()
42 | .class_function("identity", &DDM::identity)
43 | .class_function("ones", &DDM::ones)
44 | .class_function("constant", &DDM::constant)
45 | .class_function("random", &DDM::random)
46 | .class_function("diagonal", &DDM::diagonal)
47 | .class_function("fromVector", &DDM::fromVector)
48 | .function("transpose", &DDM::transpose)
49 | .function("transposeSelf", &DDM::transposeSelf)
50 | .function("inverse", &DDM::inverse)
51 | .function("rows", &DDM::rows)
52 | .function("cols", &DDM::cols)
53 | .function("norm", &DDM::norm)
54 | .function("normSqr", &DDM::normSqr)
55 | .function("l1Norm", &DDM::l1Norm)
56 | .function("lInfNorm", &DDM::lInfNorm)
57 | .function("rank", &DDM::rank)
58 | .function("det", &DDM::det)
59 | .function("sum", &DDM::sum)
60 | .function("block", &DDM::block)
61 | .function("setBlock", &DDM::setBlock)
62 | .function("mul", &DDM::mul)
63 | .function("mulSelf", &DDM::mulSelf)
64 | .function("div", &DDM::div)
65 | .function("divSelf", &DDM::divSelf)
66 | .function("matAdd", &DDM::matAdd, allow_raw_pointers())
67 | .function("matAddSelf", &DDM::matAddSelf, allow_raw_pointers())
68 | .function("matSub", &DDM::matSub, allow_raw_pointers())
69 | .function("matSubSelf", &DDM::matSubSelf, allow_raw_pointers())
70 | .function("matMul", &DDM::matMul, allow_raw_pointers())
71 | .function("matMulSelf", &DDM::matMulSelf, allow_raw_pointers())
72 | .function("get", &DDM::get)
73 | .function("set", &DDM::set)
74 | .function("hcat", &DDM::hcat, allow_raw_pointers())
75 | .function("vcat", &DDM::vcat, allow_raw_pointers())
76 | .function("print", &DDM::print)
77 | .function("clamp", &DDM::clamp)
78 | .function("clampSelf", &DDM::clampSelf)
79 | // Vector ops
80 | .function("length", &DDM::length)
81 | .function("vGet", &DDM::vGet)
82 | .function("vSet", &DDM::vSet)
83 | .function("dot", &DDM::dot);
84 |
85 | // Complex Dense Matrix
86 | class_("ComplexDenseMatrix")
87 | .constructor()
88 | .constructor()
89 | .class_function("identity", &CDM::identity)
90 | .class_function("ones", &CDM::ones)
91 | .class_function("constant", &CDM::constant)
92 | .class_function("random", &CDM::random)
93 | .function("transpose", &CDM::transpose)
94 | .function("inverse", &CDM::inverse)
95 | .function("conjugate", &CDM::conjugate)
96 | .function("rows", &CDM::rows)
97 | .function("cols", &CDM::cols)
98 | .function("norm", &CDM::norm)
99 | .function("rank", &CDM::rank)
100 | .function("sum", &CDM::sum)
101 | .function("block", select_overload(&CDM::block))
102 | .function("mul", &CDM::mul)
103 | .function("div", &CDM::div)
104 | .function("matAdd", &CDM::matAdd, allow_raw_pointers())
105 | .function("matSub", &CDM::matSub, allow_raw_pointers())
106 | .function("matMul", &CDM::matMul, allow_raw_pointers())
107 | .function("get", &CDM::get)
108 | .function("set", &CDM::set)
109 | .function("hcat", &CDM::hcat, allow_raw_pointers())
110 | .function("vcat", &CDM::vcat, allow_raw_pointers())
111 | .function("print", &CDM::print);
112 |
113 | // Triplet
114 | class_>("TripletVector")
115 | .constructor()
116 | .function("add", &TripletVector::add)
117 | .function("addDiag", &TripletVector::addDiag)
118 | .function("addBlock", &TripletVector::addBlock);
119 |
120 | // Sparse Matrix
121 | class_("SparseMatrix")
122 | .constructor()
123 | .constructor *>()
124 | .constructor()
125 | .class_function("identity", &SDM::identity)
126 | .class_function("diag", &SDM::diag)
127 | .function("transpose", &SDM::transpose)
128 | .function("rows", &SDM::rows)
129 | .function("cols", &SDM::cols)
130 | .function("nonZeros", &SDM::nonZeros)
131 | .function("frobeniusNorm", &SDM::frobeniusNorm)
132 | .function("block", &SDM::block)
133 | .function("toDense", &SDM::toDense)
134 | .function("mul", &SDM::mul)
135 | .function("mulSelf", &SDM::mulSelf)
136 | .function("div", &SDM::div)
137 | .function("divSelf", &SDM::divSelf)
138 | .function("matAdd", &SDM::matAdd, allow_raw_pointers())
139 | .function("matAddSelf", &SDM::matAddSelf, allow_raw_pointers())
140 | .function("matSub", &SDM::matSub, allow_raw_pointers())
141 | .function("matSubSelf", &SDM::matSubSelf, allow_raw_pointers())
142 | .function("matMul", &SDM::matMul, allow_raw_pointers())
143 | .function("vecMul", &SDM::vecMul, allow_raw_pointers())
144 | .function("get", &SDM::get)
145 | .function("set", &SDM::set)
146 | .function("print", &SDM::print);
147 |
148 | class_("SimplicialCholesky")
149 | .constructor()
150 | .function("solve", &SDMSCholesky::solve);
151 |
152 | // .function("matMulSelf", &SDM::matMulSelf, allow_raw_pointers());
153 | // .function("chol", &SDM::chol, allow_raw_pointers())
154 | // .function("lu", &SDM::lu, allow_raw_pointers())
155 | // .function("qr", &SDM::qr, allow_raw_pointers());
156 |
157 | // Computation Info
158 | enum_("ComputationInfo")
159 | .value("Success", Eigen::ComputationInfo::Success)
160 | .value("NumericalIssue", Eigen::ComputationInfo::NumericalIssue)
161 | .value("NoConvergence", Eigen::ComputationInfo::NoConvergence)
162 | .value("InvalidInput", Eigen::ComputationInfo::InvalidInput);
163 |
164 | // Solver
165 | value_object("EigenSolverResult")
166 | .field("info", &Solvers::EigenSolverResult::info)
167 | .field("eigenvalues", &Solvers::EigenSolverResult::eigenvalues)
168 | .field("eigenvectors", &Solvers::EigenSolverResult::eigenvectors);
169 |
170 | value_object("CareSolverResult")
171 | .field("info", &CareSolver::CareSolverResult::info)
172 | .field("K", &CareSolver::CareSolverResult::K)
173 | .field("S", &CareSolver::CareSolverResult::S);
174 |
175 | class_