├── .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 | [![npm version](https://badge.fury.io/js/eigen.svg)](https://badge.fury.io/js/eigen) 6 | [![Website shields.io](https://img.shields.io/website-up-down-green-red/http/shields.io.svg)](https://bertrandbev.github.io/eigen-js/#/) 7 | [![Made with emscripten](https://img.shields.io/badge/Made%20width-emscripten-blue.svg)](https://github.com/emscripten-core/emscripten) 8 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 | 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 | 2 | 3 | 8 | 9 | logo 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /demo/src/assets/logo_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | logo_white 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /demo/src/components/TexDisplay.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 52 | 53 | -------------------------------------------------------------------------------- /demo/src/components/benchmark/Benchmark.vue: -------------------------------------------------------------------------------- 1 | 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 | 8 | 9 | -------------------------------------------------------------------------------- /demo/src/components/nav/Drawer.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 74 | 75 | -------------------------------------------------------------------------------- /demo/src/components/nav/Toolbar.vue: -------------------------------------------------------------------------------- 1 | 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 | 14 | 15 | 39 | -------------------------------------------------------------------------------- /demo/src/views/Documentation.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 47 | 48 | -------------------------------------------------------------------------------- /demo/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 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_("Solvers") 176 | .class_function("eigenSolve", &Solvers::eigenSolve) 177 | .class_function("careSolve", &Solvers::careSolve) 178 | .class_function("createSimplicialCholeskySolver", &Solvers::createSimplicialCholeskySolver) 179 | #ifndef NO_OSQP 180 | .class_function("quadProgSolve", &Solvers::quadProgSolve) 181 | #endif 182 | ; 183 | 184 | // Decompositions 185 | value_object("CholeskyResult") 186 | .field("L", &Decompositions::CholeskyResult::L); 187 | 188 | value_object("LUResult") 189 | .field("L", &Decompositions::LUResult::L) 190 | .field("U", &Decompositions::LUResult::U) 191 | .field("P", &Decompositions::LUResult::P) 192 | .field("Q", &Decompositions::LUResult::Q); 193 | 194 | value_object("QRResult") 195 | .field("Q", &Decompositions::QRResult::Q) 196 | .field("R", &Decompositions::QRResult::R); 197 | 198 | value_object("SVDResult") 199 | .field("sv", &Decompositions::SVDResult::sv) 200 | .field("U", &Decompositions::SVDResult::U) 201 | .field("V", &Decompositions::SVDResult::V); 202 | 203 | class_("Decompositions") 204 | .class_function("cholesky", &Decompositions::cholesky) 205 | .class_function("lu", &Decompositions::lu) 206 | .class_function("qr", &Decompositions::qr) 207 | .class_function("svd", &Decompositions::svd); 208 | 209 | // Quad prog solver 210 | #ifndef NO_OSQP 211 | class_("QuadProgSolver") 212 | .class_function("solve", &QuadProgSolver::solve) 213 | .class_function("solveSparse", &QuadProgSolver::solveSparse) 214 | .class_function("solveBasic", &QuadProgSolver::solveBasic); 215 | #endif 216 | 217 | // Random 218 | class_("Random") 219 | .class_function("normal", &Random::normal); 220 | } -------------------------------------------------------------------------------- /src/eigen.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-class-members/sort-class-members */ 2 | /* eslint-disable lines-between-class-members */ 3 | /* eslint-disable semi */ 4 | /* eslint-disable import/no-unresolved */ 5 | /* eslint-disable prettier/prettier */ 6 | import HashMap from "hashmap"; 7 | 8 | declare namespace eig { 9 | class GC { 10 | static add(...addList: unknown[]): void; 11 | static pushException(...exceptionList: unknown[]): void; 12 | static popException(...exceptionList: unknown[]): void; 13 | static flush(): number; 14 | static set(ref: unknown, name: string, newObj: unknown): void; 15 | static initClass(classes: Set, Class: unknown): unknown; 16 | static objects: Set; 17 | static whitelist: HashMap; 18 | } 19 | 20 | const ready: Promise; 21 | 22 | class Vector { 23 | constructor(); 24 | push_back(value: number): void; 25 | resize(count: number, value: number): void; 26 | size(): number; 27 | get(index: number): number | undefined; 28 | set(index: number, value: number): true; 29 | } 30 | 31 | class Vector2d { 32 | constructor(); 33 | push_back(value: Vector): void; 34 | resize(count: number, value: Vector): void; 35 | size(): number; 36 | get(index: number): Vector | undefined; 37 | set(index: number, value: Vector): true; 38 | } 39 | 40 | class Complex { 41 | constructor(re: number, im: number); 42 | real(): number; 43 | imag(): number; 44 | } 45 | 46 | class Matrix { 47 | constructor(arg0: number, arg1?: number); 48 | constructor(arg0: number[] | number[][] | Matrix); 49 | static identity(m: number, n: number): Matrix; 50 | static ones(m: number, n: number): Matrix; 51 | static constant(m: number, n: number, x: number): Matrix; 52 | static random(m: number, n: number): Matrix; 53 | static diagonal(vector: Matrix): Matrix; 54 | static fromVector(v: Vector2d): Matrix; 55 | transpose(): Matrix; 56 | transposeSelf(): Matrix; 57 | inverse(): Matrix; 58 | rows(): number; 59 | cols(): number; 60 | norm(): number; 61 | normSqr(): number; 62 | l1Norm(): number; 63 | lInfNorm(): number; 64 | rank(): number; 65 | det(): number; 66 | sum(): number; 67 | block(i: number, j: number, di: number, dj: number): Matrix; 68 | setBlock(i: number, j: number, block: Matrix): void; 69 | mul(s: number): Matrix; 70 | mulSelf(s: number): Matrix; 71 | div(s: number): Matrix; 72 | divSelf(s: number): Matrix; 73 | matAdd(B: Matrix): Matrix; 74 | matAddSelf(B: Matrix): Matrix; 75 | matSub(B: Matrix): Matrix; 76 | matSubSelf(B: Matrix): Matrix; 77 | matMul(B: Matrix): Matrix; 78 | matMulSelf(B: Matrix): Matrix; 79 | get(i: number, j?: number): number; 80 | set(i: number, j: number, s: number): void; 81 | set(i: number, s: number): void; 82 | hcat(B: Matrix): Matrix; 83 | vcat(B: Matrix): Matrix; 84 | print(title: string): void; 85 | clamp(lo: number, hi: number): Matrix; 86 | clampSelf(lo: number, hi: number): Matrix; 87 | length(): number; 88 | vGet(i: number): number; 89 | vSet(i: number, s: number): void; 90 | dot(B: Matrix): number; 91 | } 92 | 93 | class ComplexDenseMatrix { 94 | constructor(m: number, n: number); 95 | constructor(B: ComplexDenseMatrix); 96 | static identity(m: number, n: number): ComplexDenseMatrix; 97 | static ones(m: number, n: number): ComplexDenseMatrix; 98 | static constant(m: number, n: number, x: number): ComplexDenseMatrix; 99 | static random(m: number, n: number): ComplexDenseMatrix; 100 | transpose(): ComplexDenseMatrix; 101 | inverse(): ComplexDenseMatrix; 102 | conjugate(): ComplexDenseMatrix; 103 | rows(): number; 104 | cols(): number; 105 | norm(): number; 106 | rank(): number; 107 | sum(): Complex; 108 | block(i: number, j: number, di: number, dj: number): ComplexDenseMatrix; 109 | mul(s: Complex): ComplexDenseMatrix; 110 | div(s: Complex): ComplexDenseMatrix; 111 | matAdd(B: ComplexDenseMatrix): ComplexDenseMatrix; 112 | matSub(B: ComplexDenseMatrix): ComplexDenseMatrix; 113 | matMul(B: ComplexDenseMatrix): ComplexDenseMatrix; 114 | get(i: number, j: number): Complex; 115 | set(i: number, j: number, s: Complex): void; 116 | hcat(B: ComplexDenseMatrix): ComplexDenseMatrix; 117 | vcat(B: ComplexDenseMatrix): ComplexDenseMatrix; 118 | print(title: string): void; 119 | } 120 | 121 | class TripletVector { 122 | constructor(capacity: number); 123 | add(i: number, j: number, x: number): void; 124 | addBlock(i: number, j: number, mat: Matrix): void; 125 | addDiag(i: number, j: number, diag: Matrix): void; 126 | } 127 | 128 | class SparseMatrix { 129 | constructor(m: number, n: number); 130 | constructor(m: number, n: number, tripletVector: TripletVector); 131 | constructor(B: SparseMatrix); 132 | static identity(m: number, n: number): SparseMatrix; 133 | static diag(d: Matrix): SparseMatrix; 134 | static fromTriplets(m: number, n: number, array: [number, number, number][]): SparseMatrix; 135 | transpose(): SparseMatrix; 136 | rows(): number; 137 | cols(): number; 138 | nonZeros(): number; 139 | frobeniusNorm(): number; 140 | block(r0: number, r1: number, c0: number, c1: number): SparseMatrix; 141 | toDense(): Matrix; 142 | mul(s: number): SparseMatrix; 143 | mulSelf(s: number): void; 144 | vecMul(v: Matrix): Matrix 145 | div(s: number): SparseMatrix; 146 | divSelf(s: number): void; 147 | matAdd(B: SparseMatrix): SparseMatrix; 148 | matAddSelf(B: SparseMatrix): void; 149 | matSub(B: SparseMatrix): SparseMatrix; 150 | matSubSelf(B: SparseMatrix): void; 151 | matMul(B: SparseMatrix): SparseMatrix; 152 | get(i: number, j: number): number; 153 | set(i: number, j: number, s: number): void; 154 | print(title: string): void; 155 | } 156 | 157 | type ComputationInfo = unknown; 158 | 159 | interface EigenSolverResult { 160 | info: ComputationInfo; 161 | eigenvalues: ComplexDenseMatrix; 162 | eigenvectors: ComplexDenseMatrix; 163 | } 164 | 165 | interface CareSolverResult { 166 | info: ComputationInfo; 167 | K: Matrix; 168 | S: Matrix; 169 | } 170 | 171 | class SimplicialCholesky { 172 | constructor(m: SparseMatrix); 173 | 174 | solve(vec: Matrix): Matrix 175 | } 176 | 177 | class Solvers { 178 | static createSimplicialCholeskySolver(matrix: SparseMatrix): SimplicialCholesky 179 | static eigenSolve(matrix: Matrix, computeEigenvectors: boolean): EigenSolverResult; 180 | static careSolve(A: Matrix, B: Matrix, Q: Matrix, R: Matrix): CareSolverResult; 181 | static solve(P: SparseMatrix, q: Matrix, A: SparseMatrix, l: Matrix, u: Matrix): Matrix; 182 | } 183 | 184 | interface CholeskyResult { 185 | L: Matrix; 186 | } 187 | 188 | interface LUResult { 189 | L: Matrix; 190 | U: Matrix; 191 | P: Matrix; 192 | Q: Matrix; 193 | } 194 | 195 | interface QRResult { 196 | Q: Matrix; 197 | R: Matrix; 198 | } 199 | 200 | interface SVDResult { 201 | sv: Matrix; 202 | U: Matrix; 203 | V: Matrix; 204 | } 205 | 206 | class Decompositions { 207 | static cholesky(M: Matrix): CholeskyResult; 208 | static lu(M: Matrix): LUResult; 209 | static qr(M: Matrix): QRResult; 210 | static svd(M: Matrix, thin: boolean): SVDResult; 211 | } 212 | 213 | class QuadProgSolver { 214 | static solve(P: SparseMatrix, q: Matrix, A: SparseMatrix, l: Matrix, u: Matrix): Matrix; 215 | static solveSparse(): void; 216 | static solveBasic(): void; 217 | } 218 | 219 | class Random { 220 | static normal(mean: Matrix, cov: Matrix, samples: number): Matrix; 221 | } 222 | } 223 | 224 | export default eig; 225 | -------------------------------------------------------------------------------- /src/eigen.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import eigen_gen from '../build/eigen_gen.js' 3 | import wasm from '../build/eigen_gen.wasm' // Comment out for local testing 4 | import GC, { getStaticMethods } from './GC.mjs' 5 | 6 | 7 | const Module = eigen_gen({ 8 | wasmBinary: wasm // Comment out for local testing 9 | }); 10 | 11 | /** 12 | * Add helper functions TODO: extract in file 13 | */ 14 | function addHelpers(eig) { 15 | /** 16 | * Add fromTriplets factory to triplets 17 | */ 18 | eig.SparseMatrix.fromTriplets = function (m, n, array) { 19 | let triplets = new eig.TripletVector(array.length) 20 | array.forEach(sub => triplets.add(...sub)) 21 | return new eig.SparseMatrix(m, n, triplets); 22 | } 23 | 24 | /** 25 | * Re-define matrix constructor 26 | */ 27 | const clazz = eig.Matrix; 28 | eig.Matrix = function (arg0, arg1 = 1) { 29 | // Copy constructor 30 | if (arg0 instanceof eig.Matrix) 31 | return new clazz(arg0); 32 | // Create matrices or arrays 33 | if (typeof arg0 === "number") 34 | return new clazz(arg0, arg1); 35 | if (!Array.isArray(arg0)) 36 | throw new Error(`Invalid constructor argument ${arg0}`); 37 | // Generate vector if needed 38 | if (!arg0.length || !Array.isArray(arg0[0])) { 39 | arg0 = arg0.map(val => [val]) 40 | } 41 | // Create initializer 42 | var v2d = new eig.Vector2d(); 43 | arg0.forEach(arr => { 44 | var v = new eig.Vector(); 45 | arr.forEach(val => v.push_back(val)); 46 | v2d.push_back(v) 47 | }) 48 | // Pass matrix 49 | return clazz.fromVector(v2d); 50 | } 51 | eig.Matrix.prototype = clazz.prototype; 52 | const staticMethods = getStaticMethods(clazz); 53 | staticMethods.forEach(method => { 54 | eig.Matrix[method] = clazz[method]; 55 | }); 56 | 57 | /** 58 | * Update get & set methods 59 | */ 60 | const getFun = eig.Matrix.prototype.get; 61 | eig.Matrix.prototype.get = function (arg0, arg1) { 62 | if (arg1 == undefined) 63 | return this.vGet(arg0); 64 | else 65 | return getFun.call(this, arg0, arg1); 66 | } 67 | 68 | const setFun = eig.Matrix.prototype.set; 69 | eig.Matrix.prototype.set = function (arg0, arg1, arg2) { 70 | if (arg2 == undefined) 71 | return this.vSet(arg0, arg1); 72 | else 73 | return setFun.call(this, arg0, arg1, arg2); 74 | } 75 | 76 | /** 77 | * Return pointer on self methods 78 | */ 79 | const methods = [ 80 | "mulSelf", 81 | "divSelf", 82 | "matAddSelf", 83 | "matSubSelf", 84 | "matMulSelf", 85 | "negatedSelf", 86 | "clampSelf" 87 | ] 88 | methods.forEach(method => { 89 | const fun = eig.Matrix.prototype[method] 90 | eig.Matrix.prototype[method] = function (...args) { 91 | fun.call(this, ...args) 92 | return this 93 | } 94 | }) 95 | } 96 | 97 | const eig = { 98 | GC: GC 99 | } 100 | 101 | eig.ready = Module.then(module => { 102 | const classes = new Set([ 103 | "Vector", 104 | "Vector2d", 105 | "Complex", 106 | "Matrix", 107 | "SparseMatrix", 108 | "TripletVector", 109 | "ComplexDenseMatrix", 110 | "Solvers", 111 | "Decompositions", 112 | "QuadProgSolver", // should be commented if libeigen built without OSQP 113 | "Random", 114 | "SimplicialCholesky", 115 | ]); 116 | classes.forEach(className => { 117 | try { 118 | eig[className] = GC.initClass(classes, module[className]) 119 | } 120 | catch (e) { 121 | console.log('Error in eig.ready: no class ' + className) 122 | } 123 | }) 124 | addHelpers(eig); 125 | }) 126 | 127 | export default eig 128 | -------------------------------------------------------------------------------- /src/eigenTest.mjs: -------------------------------------------------------------------------------- 1 | // import eig from '../dist/index.js' 2 | import eig from './eigen.mjs' 3 | 4 | 5 | function refTest() { 6 | const A = new eig.Matrix([ 7 | [1, 2, 3], 8 | [4, 5, 6], 9 | [7, 8, 9] 10 | ]) 11 | const B = new eig.Matrix([ 12 | [1, 2, 3], 13 | [4, 5, 6], 14 | [7, 8, 9] 15 | ]) 16 | let tStart = Date.now(); 17 | console.log('Ref time', Date.now() - tStart) 18 | tStart = Date.now(); 19 | for (let k = 0; k < 100000; k++) { 20 | A.matMulSelf(B) 21 | } 22 | console.log('Pointer time', Date.now() - tStart) 23 | 24 | const start = Date.now(); 25 | for (let k = 0; k < 100000; k++) { 26 | A.matMul(B); 27 | } 28 | console.log("Eig time", Date.now() - start); 29 | } 30 | 31 | function quadProgBenchmark() { 32 | let tStart = Date.now(); 33 | const n = 10000; 34 | for (let k = 0; k < n; k++) 35 | eig.QuadProgSolver.solveBasic() 36 | console.log('Compiled time', Date.now() - tStart) 37 | tStart = Date.now(); 38 | for (let k = 0; k < n; k++) 39 | eig.QuadProgSolver.solveSparse() 40 | console.log('Sparse time', Date.now() - tStart) 41 | tStart = Date.now(); 42 | for (let k = 0; k < n; k++) 43 | solveTest() 44 | console.log('JS time', Date.now() - tStart) 45 | } 46 | 47 | function solveTest() { 48 | let triplets = new eig.TripletVector(3) 49 | triplets.add(0, 0, 4); 50 | triplets.add(0, 1, 1); 51 | triplets.add(1, 1, 2); 52 | const P = new eig.SparseMatrix(2, 2, triplets); 53 | 54 | triplets = new eig.TripletVector(4) 55 | triplets.add(0, 0, 1); 56 | triplets.add(0, 1, 1); 57 | triplets.add(1, 0, 1); 58 | triplets.add(2, 1, 1); 59 | const A = new eig.SparseMatrix(3, 2, triplets); 60 | 61 | const q = new eig.Matrix([1, 1]) 62 | const l = new eig.Matrix([1, 0, 0]) 63 | const u = new eig.Matrix([1, 0.7, 0.7]) 64 | 65 | return eig.QuadProgSolver.solve(P, q, A, l, u); 66 | // console.log('solved') 67 | // eig.GC.flush() 68 | } 69 | 70 | // function quadProgTest() { 71 | // const G = new eig.Matrix([[4, -2], [-2, 4]]) 72 | // const g0 = new eig.Matrix([6, 0]) 73 | // const CE = new eig.Matrix([1, 1]) 74 | // const ce0 = new eig.Matrix([-3]) 75 | // const CI = new eig.Matrix([[1, 0, 1], [0, 1, 1]]) 76 | // const ci0 = new eig.Matrix([0, 0, 2]) 77 | // const x = new eig.Matrix([0, 0]) 78 | // const res = eig.QuadProgSolver.solve(G, g0, CE, ce0, CI, ci0, x) 79 | // console.log('result', res) 80 | // x.print('x') 81 | // } 82 | 83 | function inPlaceBenchmark() { 84 | const M1 = eig.Matrix.random(8, 8); 85 | const M2 = eig.Matrix.random(8, 8); 86 | const M3 = eig.Matrix.random(8, 8); 87 | const M4 = eig.Matrix.random(8, 8); 88 | eig.GC.pushException(M1, M2, M3, M4) 89 | console.log('M1', M1) 90 | const nIter = 1; 91 | let tStart = Date.now(); 92 | for (let k = 0; k < nIter; k++) { 93 | const [m1, m2, m3, m4] = [M1, M2, M3, M4].map(m => new eig.Matrix(m)); 94 | let res = m1; 95 | for (let j = 0; j < 7; j++) { 96 | res = res.matAdd(m2).matMul(m3).matSub(m4).mul(123).div(321) 97 | } 98 | if (k % 50 === 0) { 99 | eig.GC.flush() 100 | } 101 | } 102 | console.log('Copy time', Date.now() - tStart) 103 | tStart = Date.now(); 104 | for (let k = 0; k < nIter; k++) { 105 | const [m1, m2, m3, m4] = [M1, M2, M3, M4].map(m => new eig.Matrix(m)); 106 | let res = m1; 107 | for (let j = 0; j < 7; j++) { 108 | res = res.matAddSelf(m2).matMulSelf(m3).matSubSelf(m4).mulSelf(123).divSelf(321) 109 | } 110 | if (k % 50 === 0) { 111 | eig.GC.flush() 112 | } 113 | } 114 | console.log('In place time', Date.now() - tStart) 115 | } 116 | 117 | function gcTest() { 118 | const obj1 = {} 119 | const obj2 = {} 120 | const v1 = new eig.Matrix(1, 1) 121 | eig.GC.set(obj1, 'v', v1) 122 | eig.GC.set(obj2, 'v', v1) 123 | let n = eig.GC.flush() 124 | console.assert(n == 0, 'No object should be flushed') 125 | // v1.print("v1") 126 | const v2 = eig.Matrix.identity(1, 1) 127 | eig.GC.set(obj1, 'v', v2) 128 | n = eig.GC.flush() 129 | console.assert(n == 0, 'No object should be flushed') 130 | // v1.print("v1") 131 | // v2.print("v2") 132 | eig.GC.set(obj2, 'v', v2) 133 | n = eig.GC.flush() 134 | console.assert(n == 1, 'One object should be flushed') 135 | // v2.print("v2") 136 | eig.GC.popException(v2) 137 | n = eig.GC.flush() 138 | console.assert(n == 0, 'No object should be flushed') 139 | // v2.print("v2") 140 | eig.GC.popException(v2) 141 | n = eig.GC.flush() 142 | console.assert(n == 1, 'One object should be flushed') 143 | console.log('GC test successful'); 144 | } 145 | 146 | function basicTest() { 147 | const mat1 = new eig.Matrix(2, 3); 148 | mat1.print("mat1"); 149 | const mat2 = new eig.Matrix([1, 2]) 150 | mat2.print("mat2"); 151 | const mat3 = new eig.Matrix([[1, 2]]); 152 | mat3.print("mat3"); 153 | const mat4 = new eig.Matrix([[1, 2], [3, 4]]); 154 | mat4.print("mat4"); 155 | console.log('get1:', mat2.get(1)); 156 | console.log('get2:', mat3.get(1)); 157 | console.log('get3:', mat4.get(1, 0)); 158 | mat2.set(1, 5); 159 | mat3.set(1, 5); 160 | mat4.set(1, 0, 5); 161 | console.log('get1:', mat2.get(1)); 162 | console.log('get2:', mat3.get(1)); 163 | console.log('get3:', mat4.get(1, 0)); 164 | const mat5 = new eig.Matrix(mat1); 165 | mat5.print("mat5"); 166 | } 167 | 168 | (async () => { 169 | await eig.ready 170 | basicTest(); 171 | refTest(); 172 | quadProgBenchmark(); 173 | solveTest(); 174 | inPlaceBenchmark(); 175 | gcTest(); 176 | })(); -------------------------------------------------------------------------------- /src/tests/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.1) 2 | include(CMakePrintHelpers) 3 | project (eigen_test) 4 | 5 | set(EIGEN_DIR ${PROJECT_SOURCE_DIR}/../../../lib/eigen) 6 | cmake_print_variables(EIGEN_DIR) 7 | 8 | list(APPEND CMAKE_MODULE_PATH ${EIGEN_DIR}/build) 9 | find_package(Eigen3 REQUIRED) 10 | 11 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 12 | 13 | set (CMAKE_CXX_STANDARD 11) 14 | 15 | include_directories( 16 | ${EIGEN_DIR} 17 | ${PROJECT_SOURCE_DIR} 18 | ${PROJECT_SOURCE_DIR}/simplicalcholesky/ 19 | ${PROJECT_SOURCE_DIR}/../../cpp/ 20 | ) 21 | 22 | link_directories(${EIGEN_DIR}) 23 | 24 | add_executable(${PROJECT_NAME} benchmark.cpp 25 | simplicialcholesky/Tutorial_sparse_example.cpp 26 | ) 27 | 28 | target_link_libraries(${PROJECT_NAME} 29 | ${EIGEN_DIR}/build 30 | ) 31 | -------------------------------------------------------------------------------- /src/tests/cpp/benchmark.cpp: -------------------------------------------------------------------------------- 1 | void testSimplicialCholesky(); 2 | 3 | int main() { 4 | testSimplicialCholesky(); 5 | 6 | return 0; 7 | } -------------------------------------------------------------------------------- /src/tests/cpp/simplicialcholesky/Tutorial_sparse_example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "Tutorial_sparse_example_details.hpp" 7 | 8 | typedef Eigen::SparseMatrix SpMat; // declares a column-major sparse matrix type of double 9 | typedef Eigen::Triplet T; 10 | 11 | void buildProblem(std::vector& coefficients, Eigen::VectorXd& b, int n); 12 | void prinMatrixVector(const char *title, Eigen::VectorXd &arr); 13 | 14 | void testSimplicialCholesky() { 15 | auto start = std::chrono::steady_clock::now(); 16 | 17 | int n = 10; // size of the image 18 | int m = n*n; // number of unknows (=number of pixels) 19 | 20 | // Assembly: 21 | std::vector coefficients; // list of non-zeros coefficients 22 | Eigen::VectorXd b(m); // the right hand side-vector resulting from the constraints 23 | buildProblem(coefficients, b, n); 24 | 25 | SpMat A(m,m); 26 | A.setFromTriplets(coefficients.begin(), coefficients.end()); 27 | 28 | // Solving: 29 | Eigen::SimplicialCholesky chol(A); // performs a Cholesky factorization of A 30 | 31 | auto solverStart = std::chrono::steady_clock::now(); 32 | 33 | Eigen::VectorXd x = chol.solve(b); // use the factorization to solve for the given right hand side 34 | 35 | auto end = std::chrono::steady_clock::now(); 36 | 37 | prinMatrixVector("X = ", x); 38 | 39 | std::cout << "SimplicalCholesky test elapsed time " 40 | << std::chrono::duration_cast(end - start).count() 41 | << " ns" << std::endl; 42 | std::cout << "Solver time " 43 | << std::chrono::duration_cast(end - solverStart).count() 44 | << " ns" << std::endl; 45 | } 46 | -------------------------------------------------------------------------------- /src/tests/cpp/simplicialcholesky/Tutorial_sparse_example_details.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef Eigen::SparseMatrix SpMat; // declares a column-major sparse matrix type of double 5 | typedef Eigen::Triplet T; 6 | 7 | void insertCoefficient(int id, int i, int j, double w, std::vector& coeffs, 8 | Eigen::VectorXd& b, const Eigen::VectorXd& boundary) 9 | { 10 | int n = int(boundary.size()); 11 | int id1 = i+j*n; 12 | 13 | if(i==-1 || i==n) b(id) -= w * boundary(j); // constrained coefficient 14 | else if(j==-1 || j==n) b(id) -= w * boundary(i); // constrained coefficient 15 | else coeffs.push_back(T(id,id1,w)); // unknown coefficient 16 | } 17 | 18 | void printArrayXd(const char *title, Eigen::ArrayXd &arr) { 19 | std::cout << title << std::endl; 20 | 21 | for (int i=0; i& coefficients, Eigen::VectorXd& b, int n) 39 | { 40 | b.setZero(); 41 | Eigen::ArrayXd boundary = Eigen::ArrayXd::LinSpaced(n, 0,M_PI).sin().pow(2); 42 | 43 | for(int j=0; j { 8 | await eig.ready 9 | }) 10 | 11 | test('matrix type basic test', () => { 12 | let M = new eig.Matrix([ 13 | [1, 2], 14 | [3, 4], 15 | ]) as Matrix 16 | M.print('M') 17 | M = M.inverse() 18 | M.print('Minv') 19 | eig.GC.flush() 20 | }) 21 | 22 | test('SimplicialCholesky test', () => { 23 | console.info('SimplicialCholesky test') 24 | testSimplicialCholesky() 25 | eig.GC.flush() 26 | console.info('SimplicialCholesky done') 27 | }) 28 | -------------------------------------------------------------------------------- /src/tests/ts/readme.md: -------------------------------------------------------------------------------- 1 | # Benchmark and comparison tests 2 | 3 | The purpose of this directory is to have some benchmark tests with comparison against natively executed cpp binary code. At the moment only SmiplicialCholesky solver is being tested here, it solves large linear systems with constraints. 4 | 5 | benchmark.test.ts loads built eigen-js package from dist/index.js and performs tests over it using jest. 6 | 7 | My initial intent was to write test in Typescript with ts-jest in Visual Studio Code, where we have type information at hand, thanks to its intellisense. 8 | 9 | I have issues with these tests: 10 | - import { eig } from '../../../dist/index' generates an error, because the import is outside the module 11 | Workaround: const eig = require('../../../dist/index') 12 | 13 | - But then, the type declaration from eigen.d.ts is not applied to eig. 14 | Workaround: manually extract needed class declarations from eigen.d.ts and put to src/tests/types.ts, this will allow for cast via 'as' in typescript, at least something. 15 | 16 | 17 | # about Tutorial_sparse_example.ts test 18 | 19 | This test was taken from libeigen and executed vie eigen-js. Solution of large linear system using SimplicialCholesky solver: 20 | 21 | Javascript version (nodejs): 22 | 23 | X = 0.1359, 0.2717, 0.4530, 0.6366, 0.7529, 0.7529, 0.6366, 0.4530, 0.2717, 0.1359, 0.2717, 0.3810, 0.4904, 0.5906, 0.6521, 0.6521, 0.5906, 0.4904, 0.3810, 0.2717, 0.4530, 0.4904, 0.5369, 0.5834, 0.6128, 0.6128, 0.5834, 0.5369, 0.4904, 0.4530, 0.6366, 0.5906, 0.5834, 0.5932, 0.6030, 0.6030, 0.5932, 0.5834, 0.5906, 0.6366, 0.7529, 0.6521, 0.6128, 0.6030, 0.6030, 0.6030, 0.6030, 0.6128, 0.6521, 0.7529, 0.7529, 0.6521, 0.6128, 0.6030, 0.6030, 0.6030, 0.6030, 0.6128, 0.6521, 0.7529, 0.6366, 0.5906, 0.5834, 0.5932, 0.6030, 0.6030, 0.5932, 0.5834, 0.5906, 0.6366, 0.4530, 0.4904, 0.5369, 0.5834, 0.6128, 0.6128, 0.5834, 0.5369, 0.4904, 0.4530, 0.2717, 0.3810, 0.4904, 0.5906, 0.6521, 0.6521, 0.5906, 0.4904, 0.3810, 0.2717, 0.1359, 0.2717, 0.4530, 0.6366, 0.7529, 0.7529, 0.6366, 0.4530, 0.2717, 0.1359 24 | 25 | SimplicalCholesky test elapsed time 1 917 766 n 26 | Solver time 85 345 ns 27 | 28 | Cpp native version: 29 | 30 | X = 0.1359, 0.2717, 0.4530, 0.6366, 0.7529, 0.7529, 0.6366, 0.4530, 0.2717, 0.1359, 0.2717, 0.3810, 0.4904, 0.5906, 0.6521, 0.6521, 0.5906, 0.4904, 0.3810, 0.2717, 0.4530, 0.4904, 0.5369, 0.5834, 0.6128, 0.6128, 0.5834, 0.5369, 0.4904, 0.4530, 0.6366, 0.5906, 0.5834, 0.5932, 0.6030, 0.6030, 0.5932, 0.5834, 0.5906, 0.6366, 0.7529, 0.6521, 0.6128, 0.6030, 0.6030, 0.6030, 0.6030, 0.6128, 0.6521, 0.7529, 0.7529, 0.6521, 0.6128, 0.6030, 0.6030, 0.6030, 0.6030, 0.6128, 0.6521, 0.7529, 0.6366, 0.5906, 0.5834, 0.5932, 0.6030, 0.6030, 0.5932, 0.5834, 0.5906, 0.6366, 0.4530, 0.4904, 0.5369, 0.5834, 0.6128, 0.6128, 0.5834, 0.5369, 0.4904, 0.4530, 0.2717, 0.3810, 0.4904, 0.5906, 0.6521, 0.6521, 0.5906, 0.4904, 0.3810, 0.2717, 0.1359, 0.2717, 0.4530, 0.6366, 0.7529, 0.7529, 0.6366, 0.4530, 0.2717, 0.1359 31 | 32 | SimplicalCholesky test elapsed time 1 436 149 ns 33 | Solver time 156 210 ns 34 | 35 | 36 | Conclusion: javascript version of the test is not stable in terms of elapsed time, sometimes it becomes even faster than the native version, but that's possible due to caches at all levels of the V8. -------------------------------------------------------------------------------- /src/tests/ts/simplicialcholesky/Tutorial_sparse_example.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TripletVector, 3 | Matrix, 4 | SparseMatrix, 5 | SimplicialCholesky, 6 | } from '../types' 7 | import { linSpacedColVector, printVector, vectorSetZero } from '../utils' 8 | // eslint-disable-next-line @typescript-eslint/no-var-requires 9 | const eig = require('../../../../dist/index.js') // TODO if import is used, it gives error because importing outside module 10 | 11 | type Vector = Matrix 12 | 13 | const insertCoefficient = ( 14 | id: number, 15 | i: number, 16 | j: number, 17 | w: number, 18 | coefficients: TripletVector, 19 | b: Vector, 20 | boundary: Vector 21 | ) => { 22 | const n = boundary.length() 23 | const id1 = i + j * n 24 | 25 | if (i === -1 || i === n) 26 | b.set(id, b.get(id) - w * boundary.get(j)) // constrained coefficient 27 | else if (j === -1 || j === n) 28 | b.set(id, b.get(id) - w * boundary.get(i)) // constrained coefficient 29 | else coefficients.add(id, id1, w) // unknown coefficient 30 | } 31 | 32 | const buildProblem = (coefficients: TripletVector, b: Vector, n: number) => { 33 | vectorSetZero(b) 34 | 35 | // Eigen::ArrayXd boundary = Eigen::ArrayXd::LinSpaced(n, 0,M_PI).sin().pow(2); 36 | const boundary = linSpacedColVector(n, 0, Math.PI) 37 | 38 | // printVector('buildProblem.boundary', boundary) 39 | 40 | for (let i = 0; i < n; ++i) { 41 | boundary.set(i, Math.pow(Math.sin(boundary.get(i)), 2)) 42 | } 43 | 44 | for (let j = 0; j < n; ++j) { 45 | for (let i = 0; i < n; ++i) { 46 | const id = i + j * n 47 | insertCoefficient(id, i - 1, j, -1, coefficients, b, boundary) 48 | insertCoefficient(id, i + 1, j, -1, coefficients, b, boundary) 49 | insertCoefficient(id, i, j - 1, -1, coefficients, b, boundary) 50 | insertCoefficient(id, i, j + 1, -1, coefficients, b, boundary) 51 | insertCoefficient(id, i, j, 4, coefficients, b, boundary) 52 | } 53 | } 54 | } 55 | 56 | export const testSimplicialCholesky = () => { 57 | const timeStart = process.hrtime.bigint() 58 | 59 | const n = 10 // size of the image 60 | const m = n * n // number of unknows (=number of pixels) 61 | 62 | // Assembly: 63 | const coefficients = new eig.TripletVector(m) as TripletVector // list of non-zeros coefficients 64 | const b = new eig.Matrix(m) as Vector // the right hand side-vector resulting from the constraints 65 | buildProblem(coefficients, b, n) 66 | 67 | const A = eig.SparseMatrix(m, m, coefficients) as SparseMatrix 68 | 69 | // Solving: 70 | const chol = new eig.SimplicialCholesky(A) as SimplicialCholesky // performs a Cholesky factorization of A 71 | 72 | const timeSolveStart = process.hrtime.bigint() 73 | 74 | const x = chol.solve(b) // use the factorization to solve for the given right hand side 75 | 76 | const timeEnd = process.hrtime.bigint() 77 | 78 | printVector('X', x) 79 | 80 | console.log('SimplicalCholesky test elapsed time', timeEnd - timeStart) 81 | console.log('Solver time', timeEnd - timeSolveStart) 82 | } 83 | -------------------------------------------------------------------------------- /src/tests/ts/types.ts: -------------------------------------------------------------------------------- 1 | // TODO I wish it could just pick eig namespace from eigen.d.ts file, but it does not work yet 2 | 3 | export interface Matrix { 4 | // constructor(arg0: number, arg1?: number); 5 | // constructor(arg0: number[] | number[][] | Matrix); 6 | // static identity(m: number, n: number): Matrix; 7 | // static ones(m: number, n: number): Matrix; 8 | // static constant(m: number, n: number, x: number): Matrix; 9 | // static random(m: number, n: number): Matrix; 10 | // static diagonal(vector: Matrix): Matrix; 11 | // static fromVector(v: Vector2d): Matrix; 12 | transpose(): Matrix 13 | transposeSelf(): Matrix 14 | inverse(): Matrix 15 | rows(): number 16 | cols(): number 17 | norm(): number 18 | normSqr(): number 19 | l1Norm(): number 20 | lInfNorm(): number 21 | rank(): number 22 | det(): number 23 | sum(): number 24 | block(i: number, j: number, di: number, dj: number): Matrix 25 | setBlock(i: number, j: number, block: Matrix): void 26 | mul(s: number): Matrix 27 | mulSelf(s: number): Matrix 28 | div(s: number): Matrix 29 | divSelf(s: number): Matrix 30 | matAdd(B: Matrix): Matrix 31 | matAddSelf(B: Matrix): Matrix 32 | matSub(B: Matrix): Matrix 33 | matSubSelf(B: Matrix): Matrix 34 | matMul(B: Matrix): Matrix 35 | matMulSelf(B: Matrix): Matrix 36 | get(i: number, j?: number): number 37 | set(i: number, j: number, s: number): void 38 | set(i: number, s: number): void 39 | hcat(B: Matrix): Matrix 40 | vcat(B: Matrix): Matrix 41 | print(title: string): void 42 | clamp(lo: number, hi: number): Matrix 43 | clampSelf(lo: number, hi: number): Matrix 44 | length(): number 45 | vGet(i: number): number 46 | vSet(i: number, s: number): void 47 | dot(B: Matrix): number 48 | } 49 | 50 | export interface TripletVector { 51 | // constructor(capacity: number) 52 | add(i: number, j: number, x: number): void 53 | addBlock(i: number, j: number, mat: Matrix): void 54 | addDiag(i: number, j: number, diag: Matrix): void 55 | } 56 | 57 | export interface SparseMatrix { 58 | // constructor(m: number, n: number) 59 | // constructor(m: number, n: number, tripletVector: TripletVector) 60 | // constructor(B: SparseMatrix) 61 | // static identity(m: number, n: number): SparseMatrix 62 | // static diag(d: Matrix): SparseMatrix 63 | // static fromTriplets( 64 | // m: number, 65 | // n: number, 66 | // array: [number, number, number][] 67 | // ): SparseMatrix 68 | transpose(): SparseMatrix 69 | rows(): number 70 | cols(): number 71 | nonZeros(): number 72 | frobeniusNorm(): number 73 | block(r0: number, r1: number, c0: number, c1: number): SparseMatrix 74 | toDense(): Matrix 75 | mul(s: number): SparseMatrix 76 | mulSelf(s: number): void 77 | vecMul(v: Matrix): Matrix 78 | div(s: number): SparseMatrix 79 | divSelf(s: number): void 80 | matAdd(B: SparseMatrix): SparseMatrix 81 | matAddSelf(B: SparseMatrix): void 82 | matSub(B: SparseMatrix): SparseMatrix 83 | matSubSelf(B: SparseMatrix): void 84 | matMul(B: SparseMatrix): SparseMatrix 85 | get(i: number, j: number): number 86 | set(i: number, j: number, s: number): void 87 | print(title: string): void 88 | } 89 | 90 | export interface SimplicialCholesky { 91 | solve(vec: Matrix): Matrix 92 | } 93 | 94 | export type MatrixVector = Matrix 95 | -------------------------------------------------------------------------------- /src/tests/ts/utils.ts: -------------------------------------------------------------------------------- 1 | import { MatrixVector } from './types' 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const eig = require('../../../dist/index.js') // TODO if import is used, it gives error because importing outside module 4 | 5 | export const vectorSetZero = (v: MatrixVector) => { 6 | for (let i = 0, len = v.length(); i < len; ++i) { 7 | v.vSet(i, 0) 8 | } 9 | } 10 | 11 | export const printVector = (name: string, v: MatrixVector) => { 12 | const arr: number[] = [] 13 | for (let i = 0, len = v.length(); i < len; ++i) { 14 | arr.push(v.vGet(i)) 15 | } 16 | console.log(name + ' = ' + arr.map(_ => _.toFixed(4)).join(', ')) 17 | } 18 | 19 | // export const printMatrix = (name: string, m: Matrix) => { 20 | // // get maximum element string length 21 | // for (let i = 0, cols = m.cols(); i < cols; ++i) { 22 | // for (let j = 0, rows = m.rows(); j < rows; ++j) { 23 | // } 24 | // } 25 | // const maxLen = m.flat().reduce((a,c) => Math.max(c.length,a), 0); 26 | // const res = grid.map(arr => arr.map(s => s.padEnd(mLen,' ')).join(' ')).join('\n') 27 | // console.log(res) 28 | // } 29 | 30 | export const linSpacedColVector = ( 31 | numSteps: number, 32 | start: number, 33 | end: number 34 | ): MatrixVector => { 35 | const result = new eig.Matrix(1, numSteps) as MatrixVector 36 | const dt = (end - start) / (numSteps - 1) 37 | for (let i = 0; i < numSteps; ++i) { 38 | result.vSet(i, dt * i) 39 | } 40 | return result 41 | } 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "module": "ESNext", 3 | "moduleResolution": "node", 4 | 5 | "compilerOptions": { 6 | "allowJs": true, 7 | "esModuleInterop": true, 8 | "declaration": false 9 | }, 10 | 11 | "types": ["react", "jest", "src/eigen.d.ts"], 12 | "typeRoots": ["./src"], 13 | "exclude": ["node_modules"], 14 | "include": ["src/**/*", "../dist/index.js"] 15 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path'); 3 | 4 | const config = { 5 | mode: 'production', 6 | entry: './src/eigen.mjs', 7 | context: path.resolve(__dirname, "."), 8 | module: { 9 | rules: [ 10 | // { 11 | // test: /\.js?$/, 12 | // exclude: /(node_modules)/, 13 | // use: 'babel-loader', 14 | // }, 15 | // { 16 | // test: /\.wasm$/, 17 | // type: "javascript/auto", // ← !! 18 | // loader: "file-loader", 19 | // options: { 20 | // publicPath: "dist/" 21 | // } 22 | // }, 23 | { 24 | test: /\.wasm$/, 25 | type: "javascript/auto", 26 | loader: "arraybuffer-loader" 27 | } 28 | ], 29 | }, 30 | resolve: { 31 | extensions: ['.js'], 32 | }, 33 | // browser: { 34 | // fs: false 35 | // } 36 | }; 37 | 38 | const nodeConfig = { 39 | target: 'node', 40 | ...config, 41 | output: { 42 | path: path.resolve(__dirname, "dist"), 43 | filename: 'index.js', 44 | libraryExport: 'default', 45 | library: 'eig', 46 | libraryTarget: 'umd' 47 | } 48 | } 49 | 50 | // const webConfig = { 51 | // target: 'web', 52 | // ...config, 53 | // output: { 54 | // path: path.resolve(__dirname, "dist"), 55 | // filename: 'index.js', 56 | // libraryExport: 'default', 57 | // library: 'eig', 58 | // libraryTarget: 'umd' 59 | // }, 60 | // node: { 61 | // fs: 'empty' 62 | // }, 63 | // } 64 | 65 | module.exports = [nodeConfig]; 66 | --------------------------------------------------------------------------------