@emasoft/svg-matrix 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+
13
+ jobs:
14
+ publish:
15
+ name: Publish to npm
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - name: Checkout code
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Setup Node.js 24 (npm 11.6.2 with OIDC support)
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: '24'
26
+
27
+ - name: Install dependencies (for prepublishOnly script)
28
+ run: npm install --production=false
29
+
30
+ - name: Verify npm version (should be >= 11.5.1)
31
+ run: npm --version
32
+
33
+ - name: Publish to npm (using OIDC trusted publishing)
34
+ run: npm publish --access public
@@ -0,0 +1,37 @@
1
+ name: Release and Publish to npm
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - 'v*.*.*'
8
+
9
+ permissions:
10
+ contents: write
11
+ packages: write
12
+
13
+ jobs:
14
+ release:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Setup Node
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: 18
23
+ registry-url: 'https://registry.npmjs.org'
24
+
25
+ - name: Install
26
+ run: npm ci
27
+
28
+ - name: Run tests
29
+ run: npm test
30
+
31
+ - name: Create GitHub Release (if tag present)
32
+ if: startsWith(github.ref, 'refs/tags/')
33
+ uses: softprops/action-gh-release@v1
34
+ with:
35
+ tag_name: ${{ github.ref_name }}
36
+ env:
37
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,21 @@
1
+ name: CI Test
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Setup Node
15
+ uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 24
18
+ - name: Install
19
+ run: npm ci
20
+ - name: Run tests/examples
21
+ run: npm test
package/CLAUDE.md ADDED
@@ -0,0 +1,60 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ svg-matrix is an arbitrary-precision matrix, vector, and affine transformation library for JavaScript using decimal.js. It provides Decimal-backed Matrix and Vector classes along with 2D (3x3) and 3D (4x4) transform helpers for geometry operations requiring high precision.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ # Install dependencies
13
+ npm install
14
+
15
+ # Run tests (executes test/examples.js)
16
+ npm test
17
+
18
+ # CI test (clean install + test)
19
+ npm run ci-test
20
+ ```
21
+
22
+ ## Architecture
23
+
24
+ ### Core Classes (src/)
25
+
26
+ **Matrix** (`matrix.js`) - Decimal-backed matrix class with:
27
+ - Factory methods: `Matrix.from()`, `Matrix.zeros()`, `Matrix.identity()`
28
+ - Operations: `add()`, `sub()`, `mul()`, `transpose()`, `inverse()`, `clone()`
29
+ - Linear algebra: `lu()` (LU decomposition), `qr()` (QR via Householder), `determinant()`, `solve()`, `exp()` (matrix exponential)
30
+ - Vector application: `applyToVector()`
31
+
32
+ **Vector** (`vector.js`) - Decimal-backed vector class with:
33
+ - Basic ops: `add()`, `sub()`, `scale()`, `dot()`, `cross()`, `outer()`
34
+ - Geometry: `norm()`, `normalize()`, `angleBetween()`, `projectOnto()`, `orthogonal()`
35
+ - Conversion: `toArray()`, `toNumberArray()`, `toStringArray()`
36
+
37
+ ### Transform Helpers
38
+
39
+ **Transforms2D** (`transforms2d.js`) - 3x3 affine matrices:
40
+ - `translation(tx, ty)`, `scale(sx, sy)`, `rotate(theta)`, `rotateAroundPoint()`, `skew()`, `stretchAlongAxis()`
41
+ - `applyTransform(M, x, y)` - applies matrix to 2D point with homogeneous division
42
+
43
+ **Transforms3D** (`transforms3d.js`) - 4x4 affine matrices:
44
+ - `translation(tx, ty, tz)`, `scale(sx, sy, sz)`, `rotateAroundAxis(ux, uy, uz, theta)`
45
+
46
+ ### Key Patterns
47
+
48
+ All numeric inputs are converted to Decimal via the helper `const D = x => (x instanceof Decimal ? x : new Decimal(x))`. This allows passing numbers, strings, or Decimal instances.
49
+
50
+ Transform composition uses right-to-left multiplication: `T.mul(R).mul(S)` applies S first, then R, then T.
51
+
52
+ ## Dependencies
53
+
54
+ - **decimal.js** (^11.4.3) - Arbitrary-precision decimal arithmetic (runtime)
55
+ - **@actions/oidc-client** - GitHub Actions OIDC for npm publishing (dev)
56
+ - **node-fetch** - For npm token retrieval scripts (dev)
57
+
58
+ ## Publishing
59
+
60
+ Releases are triggered by pushing version tags (`v*`). The publish workflow uses npm OIDC trusted publishing (Node 24 / npm 11.6+).
package/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Emasoft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction...
8
+ (standard MIT text omitted here — include the full text in your repo)
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # SVG-MATRIX
2
+
3
+ Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js.
4
+
5
+ This repository contains:
6
+ - Decimal-backed Matrix and Vector classes.
7
+ - 2D (3x3) and 3D (4x4) transform helpers.
8
+
9
+ Install:
10
+ ```
11
+ npm install svg-matrix
12
+ ```
13
+
14
+ Usage example:
15
+ ```js
16
+ import { Decimal, Matrix, Vector, Transforms2D } from 'svg-matrix';
17
+ Decimal.set({ precision: 80 });
18
+
19
+ const M = Transforms2D.translation(2, 3).mul(Transforms2D.rotate(Math.PI/4)).mul(Transforms2D.scale(1.5));
20
+ const p2 = Transforms2D.applyTransform(M, 1, 0);
21
+ console.log('Transformed point:', p2.map(x => x.toString()));
22
+ ```
23
+
24
+ See test/examples.js for more use cases.
25
+
@@ -0,0 +1,22 @@
1
+ # SVG-MATRIX
2
+
3
+ Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js.
4
+
5
+ This repository contains:
6
+ - Decimal-backed Matrix and Vector classes.
7
+ - 2D (3x3) and 3D (4x4) transform helpers.
8
+ - Examples and GitHub Actions workflows:
9
+ - .github/workflows/test.yml => runs tests/examples on push/PR
10
+ - .github/workflows/publish.yml => publishes to npm via OIDC trusted publishing on tag push
11
+
12
+ Install
13
+ npm ci
14
+
15
+ Usage example:
16
+ ```js
17
+ import { Decimal, Matrix, Vector, Transforms2D } from 'svg-matrix';
18
+ Decimal.set({ precision: 80 });
19
+
20
+ const M = Transforms2D.translation(2, 3).mul(Transforms2D.rotate(Math.PI/4)).mul(Transforms2D.scale(1.5));
21
+ const p2 = Transforms2D.applyTransform(M, 1, 0);
22
+ console.log('Transformed point:', p2.map(x => x.toString()));
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "svg-matrix",
3
+ "version": "1.0.0",
4
+ "description": "Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "scripts": {
8
+ "test": "node test/examples.js",
9
+ "ci-test": "npm ci && npm test",
10
+ "prepublishOnly": "npm test"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/Emasoft/SVG-MATRIX.git"
15
+ },
16
+ "keywords": [
17
+ "matrix",
18
+ "vector",
19
+ "arbitrary-precision",
20
+ "decimal",
21
+ "linear-algebra",
22
+ "affine",
23
+ "transform",
24
+ "svg",
25
+ "geometry"
26
+ ],
27
+ "author": "Emasoft",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "decimal.js": "^11.4.3"
31
+ },
32
+ "devDependencies": {}
33
+ }
Binary file
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@emasoft/svg-matrix",
3
+ "version": "1.0.1",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "@emasoft/svg-matrix",
9
+ "version": "1.0.1",
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "decimal.js": "^10.6.0"
13
+ }
14
+ },
15
+ "node_modules/decimal.js": {
16
+ "version": "10.6.0",
17
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
18
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
19
+ "license": "MIT"
20
+ }
21
+ }
22
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@emasoft/svg-matrix",
3
+ "version": "1.0.1",
4
+ "description": "Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "scripts": {
8
+ "test": "node test/examples.js",
9
+ "ci-test": "npm ci && npm test",
10
+ "prepublishOnly": "npm test"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/Emasoft/SVG-MATRIX.git"
15
+ },
16
+ "keywords": [
17
+ "matrix",
18
+ "vector",
19
+ "arbitrary-precision",
20
+ "decimal",
21
+ "linear-algebra",
22
+ "affine",
23
+ "transform",
24
+ "svg",
25
+ "geometry"
26
+ ],
27
+ "author": "Emasoft",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "decimal.js": "^10.6.0"
31
+ }
32
+ }
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # bootstrap_repo.sh
5
+ # Creates a new repository (owner/repo), populates it with SVG-MATRIX files,
6
+ # commits and pushes initial branch, and optionally sets OIDC secrets.
7
+ #
8
+ # Requirements:
9
+ # - gh CLI authenticated (gh auth status)
10
+ # - git installed
11
+ # - node/npm not required to run script (only to run tests later)
12
+ #
13
+ # Usage:
14
+ # OWNER=Emasoft REPO=SVG-MATRIX ./bootstrap_repo.sh
15
+ # Optional env vars:
16
+ # VISIBILITY (public|private) default: public
17
+ # BRANCH default: main
18
+ # NPM_OIDC_TOKEN_URL and NPM_OIDC_AUDIENCE - optional secrets to set in the repo (if provided they'll be added)
19
+
20
+ OWNER="${OWNER:-Emasoft}"
21
+ REPO="${REPO:-SVG-MATRIX}"
22
+ VISIBILITY="${VISIBILITY:-public}"
23
+ BRANCH="${BRANCH:-main}"
24
+ DIR="${REPO}"
25
+
26
+ echo "Bootstrap repo: ${OWNER}/${REPO} (visibility=${VISIBILITY}, branch=${BRANCH})"
27
+
28
+ # check prerequisites
29
+ if ! command -v gh >/dev/null 2>&1; then
30
+ echo "gh CLI not found. Install and authenticate (gh auth login) and try again."
31
+ exit 1
32
+ fi
33
+ if ! command -v git >/dev/null 2>&1; then
34
+ echo "git not found. Please install git and try again."
35
+ exit 1
36
+ fi
37
+
38
+ # check gh auth
39
+ if ! gh auth status >/dev/null 2>&1; then
40
+ echo "gh CLI not authenticated. Run: gh auth login"
41
+ gh auth status || true
42
+ exit 1
43
+ fi
44
+
45
+ # create local dir (safe)
46
+ if [ -d "${DIR}" ]; then
47
+ echo "Directory ${DIR} already exists. Please remove or move it and re-run, or run in a different location."
48
+ exit 1
49
+ fi
50
+
51
+ mkdir "${DIR}"
52
+ cd "${DIR}"
53
+
54
+ # create files (each file is written with a here-doc)
55
+ mkdir -p src test .github/workflows scripts
56
+
57
+ cat > .gitignore <<'EOF'
58
+ node_modules
59
+ dist
60
+ .DS_Store
61
+ .env
62
+ EOF
63
+
64
+ cat > package.json <<'EOF'
65
+ {
66
+ "name": "svg-matrix",
67
+ "version": "1.0.0",
68
+ "description": "Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js",
69
+ "type": "module",
70
+ "main": "src/index.js",
71
+ "scripts": {
72
+ "test": "node test/examples.js",
73
+ "ci-test": "npm ci && npm test",
74
+ "prepublishOnly": "npm test"
75
+ },
76
+ "repository": {
77
+ "type": "git",
78
+ "url": "https://github.com/Emasoft/SVG-MATRIX.git"
79
+ },
80
+ "keywords": [
81
+ "matrix",
82
+ "vector",
83
+ "arbitrary-precision",
84
+ "decimal",
85
+ "linear-algebra",
86
+ "affine",
87
+ "transform",
88
+ "svg",
89
+ "geometry"
90
+ ],
91
+ "author": "Emasoft",
92
+ "license": "MIT",
93
+ "dependencies": {
94
+ "decimal.js": "^11.4.3"
95
+ },
96
+ "devDependencies": {}
97
+ }
98
+ EOF
99
+
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import Decimal from 'decimal.js';
2
+ import { Matrix } from './matrix.js';
3
+ import { Vector } from './vector.js';
4
+ import * as Transforms2D from './transforms2d.js';
5
+ import * as Transforms3D from './transforms3d.js';
6
+
7
+ export { Decimal, Matrix, Vector, Transforms2D, Transforms3D };
package/src/matrix.js ADDED
@@ -0,0 +1,293 @@
1
+ import Decimal from 'decimal.js';
2
+ import { Vector } from './vector.js';
3
+ const D = x => (x instanceof Decimal ? x : new Decimal(x));
4
+
5
+ /**
6
+ * Matrix - Decimal-backed matrix class
7
+ */
8
+ export class Matrix {
9
+ constructor(data) {
10
+ if (!Array.isArray(data) || data.length === 0) throw new Error('Matrix requires non-empty 2D array');
11
+ const cols = data[0].length;
12
+ for (const row of data) {
13
+ if (!Array.isArray(row) || row.length !== cols) throw new Error('All rows must have same length');
14
+ }
15
+ this.data = data.map(r => r.map(v => D(v)));
16
+ this.rows = data.length;
17
+ this.cols = cols;
18
+ }
19
+
20
+ static from(arr) {
21
+ return new Matrix(arr);
22
+ }
23
+
24
+ static zeros(r, c) {
25
+ const out = Array.from({ length: r }, () => Array.from({ length: c }, () => new Decimal(0)));
26
+ return new Matrix(out);
27
+ }
28
+
29
+ static identity(n) {
30
+ const out = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? new Decimal(1) : new Decimal(0))));
31
+ return new Matrix(out);
32
+ }
33
+
34
+ clone() {
35
+ return new Matrix(this.data.map(r => r.map(v => new Decimal(v))));
36
+ }
37
+
38
+ toArrayOfStrings() {
39
+ return this.data.map(r => r.map(v => v.toString()));
40
+ }
41
+
42
+ // apply matrix to Vector or array: returns Vector
43
+ applyToVector(vec) {
44
+ let v;
45
+ if (vec instanceof Vector) v = vec;
46
+ else if (Array.isArray(vec)) v = Vector.from(vec);
47
+ else throw new Error('applyToVector expects Vector or array');
48
+ if (this.cols !== v.length) throw new Error('shape mismatch');
49
+ const out = [];
50
+ for (let i = 0; i < this.rows; i++) {
51
+ let sum = new Decimal(0);
52
+ for (let j = 0; j < this.cols; j++) sum = sum.plus(this.data[i][j].mul(v.data[j]));
53
+ out.push(sum);
54
+ }
55
+ return new Vector(out);
56
+ }
57
+
58
+ // elementwise add/sub or scalar
59
+ add(other) {
60
+ if (other instanceof Matrix) {
61
+ if (this.rows !== other.rows || this.cols !== other.cols) throw new Error('shape mismatch');
62
+ return new Matrix(this.data.map((r, i) => r.map((v, j) => v.plus(other.data[i][j]))));
63
+ } else {
64
+ const s = D(other);
65
+ return new Matrix(this.data.map(r => r.map(v => v.plus(s))));
66
+ }
67
+ }
68
+
69
+ sub(other) {
70
+ if (other instanceof Matrix) {
71
+ if (this.rows !== other.rows || this.cols !== other.cols) throw new Error('shape mismatch');
72
+ return new Matrix(this.data.map((r, i) => r.map((v, j) => v.minus(other.data[i][j]))));
73
+ } else {
74
+ const s = D(other);
75
+ return new Matrix(this.data.map(r => r.map(v => v.minus(s))));
76
+ }
77
+ }
78
+
79
+ mul(other) {
80
+ if (other instanceof Matrix) {
81
+ if (this.cols !== other.rows) throw new Error('shape mismatch');
82
+ const out = Array.from({ length: this.rows }, () => Array.from({ length: other.cols }, () => new Decimal(0)));
83
+ for (let i = 0; i < this.rows; i++) {
84
+ for (let k = 0; k < this.cols; k++) {
85
+ const aik = this.data[i][k];
86
+ if (aik.isZero()) continue;
87
+ for (let j = 0; j < other.cols; j++) out[i][j] = out[i][j].plus(aik.mul(other.data[k][j]));
88
+ }
89
+ }
90
+ return new Matrix(out);
91
+ } else {
92
+ const s = D(other);
93
+ return new Matrix(this.data.map(r => r.map(v => v.mul(s))));
94
+ }
95
+ }
96
+
97
+ transpose() {
98
+ const out = Array.from({ length: this.cols }, (_, i) => Array.from({ length: this.rows }, (_, j) => new Decimal(this.data[j][i])));
99
+ return new Matrix(out);
100
+ }
101
+
102
+ // LU decomposition (returns {L, U, P})
103
+ lu() {
104
+ if (this.rows !== this.cols) throw new Error('LU requires square');
105
+ const n = this.rows;
106
+ const A = this.clone().data.map(r => r.map(v => new Decimal(v)));
107
+ const Pvec = Array.from({ length: n }, (_, i) => i);
108
+ const L = Array.from({ length: n }, () => Array.from({ length: n }, () => new Decimal(0)));
109
+ for (let i = 0; i < n; i++) L[i][i] = new Decimal(1);
110
+
111
+ for (let k = 0; k < n; k++) {
112
+ let pivot = k;
113
+ let maxAbs = A[k][k].abs();
114
+ for (let i = k + 1; i < n; i++) {
115
+ const aabs = A[i][k].abs();
116
+ if (aabs.greaterThan(maxAbs)) { maxAbs = aabs; pivot = i; }
117
+ }
118
+ if (A[pivot][k].isZero()) throw new Error('Singular matrix in LU');
119
+ if (pivot !== k) {
120
+ const tmp = A[k]; A[k] = A[pivot]; A[pivot] = tmp;
121
+ const tmpIdx = Pvec[k]; Pvec[k] = Pvec[pivot]; Pvec[pivot] = tmpIdx;
122
+ for (let j = 0; j < k; j++) {
123
+ const t = L[k][j]; L[k][j] = L[pivot][j]; L[pivot][j] = t;
124
+ }
125
+ }
126
+ for (let i = k + 1; i < n; i++) {
127
+ const factor = A[i][k].div(A[k][k]);
128
+ L[i][k] = factor;
129
+ for (let j = k; j < n; j++) A[i][j] = A[i][j].minus(factor.mul(A[k][j]));
130
+ }
131
+ }
132
+
133
+ const U = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (j < i ? new Decimal(0) : A[i][j])));
134
+ const P = Matrix.zeros(n, n);
135
+ for (let i = 0; i < n; i++) P.data[i][Pvec[i]] = new Decimal(1);
136
+ return { L: new Matrix(L), U: new Matrix(U), P: P };
137
+ }
138
+
139
+ determinant() {
140
+ if (this.rows !== this.cols) throw new Error('Determinant only for square');
141
+ const n = this.rows;
142
+ const { L, U, P } = this.lu();
143
+ let det = new Decimal(1);
144
+ for (let i = 0; i < n; i++) det = det.mul(U.data[i][i]);
145
+ // permutation sign
146
+ const perm = [];
147
+ for (let i = 0; i < n; i++) {
148
+ for (let j = 0; j < n; j++) if (P.data[i][j].equals(1)) perm.push(j);
149
+ }
150
+ let inv = 0;
151
+ for (let i = 0; i < perm.length; i++) for (let j = i + 1; j < perm.length; j++) if (perm[i] > perm[j]) inv++;
152
+ if (inv % 2 === 1) det = det.negated();
153
+ return det;
154
+ }
155
+
156
+ inverse() {
157
+ if (this.rows !== this.cols) throw new Error('Inverse only for square');
158
+ const n = this.rows;
159
+ const aug = Array.from({ length: n }, (_, i) => Array.from({ length: 2 * n }, (_, j) => (j < n ? new Decimal(this.data[i][j]) : (j - n === i ? new Decimal(1) : new Decimal(0)))));
160
+ for (let col = 0; col < n; col++) {
161
+ let pivot = col;
162
+ let maxAbs = aug[col][col].abs();
163
+ for (let r = col + 1; r < n; r++) {
164
+ const aabs = aug[r][col].abs();
165
+ if (aabs.greaterThan(maxAbs)) { maxAbs = aabs; pivot = r; }
166
+ }
167
+ if (aug[pivot][col].isZero()) throw new Error('Singular matrix');
168
+ if (pivot !== col) {
169
+ const tmp = aug[col]; aug[col] = aug[pivot]; aug[pivot] = tmp;
170
+ }
171
+ const pivval = aug[col][col];
172
+ for (let j = 0; j < 2 * n; j++) aug[col][j] = aug[col][j].div(pivval);
173
+ for (let r = 0; r < n; r++) {
174
+ if (r === col) continue;
175
+ const factor = aug[r][col];
176
+ if (factor.isZero()) continue;
177
+ for (let j = 0; j < 2 * n; j++) aug[r][j] = aug[r][j].minus(factor.mul(aug[col][j]));
178
+ }
179
+ }
180
+ const inv = aug.map(row => row.slice(n));
181
+ return new Matrix(inv);
182
+ }
183
+
184
+ // solve Ax = b where b is Vector or array; returns Vector
185
+ solve(b) {
186
+ let B;
187
+ if (b instanceof Vector) B = b;
188
+ else if (Array.isArray(b)) B = Vector.from(b);
189
+ else throw new Error('b must be Vector or array');
190
+ if (this.rows !== this.cols) throw new Error('Solve only implemented for square A');
191
+ const n = this.rows;
192
+ if (B.length !== n) throw new Error('dimension mismatch');
193
+ // convert to augmented array
194
+ const aug = Array.from({ length: n }, (_, i) => Array.from({ length: n + 1 }, (_, j) => new Decimal(j < n ? this.data[i][j] : B.data[i])));
195
+ // forward elimination
196
+ for (let col = 0; col < n; col++) {
197
+ let pivot = col;
198
+ let maxAbs = aug[col][col].abs();
199
+ for (let r = col + 1; r < n; r++) {
200
+ const aabs = aug[r][col].abs();
201
+ if (aabs.greaterThan(maxAbs)) { maxAbs = aabs; pivot = r; }
202
+ }
203
+ if (aug[pivot][col].isZero()) throw new Error('Singular or no unique solution');
204
+ if (pivot !== col) { const tmp = aug[col]; aug[col] = aug[pivot]; aug[pivot] = tmp; }
205
+ for (let r = col + 1; r < n; r++) {
206
+ const factor = aug[r][col].div(aug[col][col]);
207
+ if (factor.isZero()) continue;
208
+ for (let j = col; j < n + 1; j++) aug[r][j] = aug[r][j].minus(factor.mul(aug[col][j]));
209
+ }
210
+ }
211
+ // back substitution
212
+ const x = Array.from({ length: n }, () => new Decimal(0));
213
+ for (let i = n - 1; i >= 0; i--) {
214
+ let sum = new Decimal(0);
215
+ for (let j = i + 1; j < n; j++) sum = sum.plus(aug[i][j].mul(x[j]));
216
+ x[i] = aug[i][n].minus(sum).div(aug[i][i]);
217
+ }
218
+ return new Vector(x);
219
+ }
220
+
221
+ // QR via Householder (returns {Q, R})
222
+ qr() {
223
+ const m = this.rows, n = this.cols;
224
+ let A = this.clone().data.map(r => r.map(v => new Decimal(v)));
225
+ const Q = Matrix.identity(m).data;
226
+ for (let k = 0; k < Math.min(m, n); k++) {
227
+ const x = [];
228
+ for (let i = k; i < m; i++) x.push(A[i][k]);
229
+ let normx = new Decimal(0);
230
+ for (const xi of x) normx = normx.plus(xi.mul(xi));
231
+ normx = normx.sqrt();
232
+ if (normx.isZero()) continue;
233
+ const sign = x[0].isNegative() ? new Decimal(-1) : new Decimal(1);
234
+ const v = x.slice();
235
+ v[0] = v[0].plus(sign.mul(normx));
236
+ let vnorm = new Decimal(0);
237
+ for (const vi of v) vnorm = vnorm.plus(vi.mul(vi));
238
+ vnorm = vnorm.sqrt();
239
+ if (vnorm.isZero()) continue;
240
+ for (let i = 0; i < v.length; i++) v[i] = v[i].div(vnorm);
241
+ for (let j = k; j < n; j++) {
242
+ let dot = new Decimal(0);
243
+ for (let i = 0; i < v.length; i++) dot = dot.plus(v[i].mul(A[k + i][j]));
244
+ for (let i = 0; i < v.length; i++) A[k + i][j] = A[k + i][j].minus(new Decimal(2).mul(v[i]).mul(dot));
245
+ }
246
+ for (let j = 0; j < m; j++) {
247
+ let dot = new Decimal(0);
248
+ for (let i = 0; i < v.length; i++) dot = dot.plus(v[i].mul(Q[k + i][j]));
249
+ for (let i = 0; i < v.length; i++) Q[k + i][j] = Q[k + i][j].minus(new Decimal(2).mul(v[i]).mul(dot));
250
+ }
251
+ }
252
+ const R = Array.from({ length: m }, (_, i) => Array.from({ length: n }, (_, j) => (i <= j ? A[i][j] : new Decimal(0))));
253
+ return { Q: new Matrix(Q).transpose(), R: new Matrix(R) };
254
+ }
255
+
256
+ // simple matrix exponential via Taylor + scaling & squaring (practical default)
257
+ exp(options = {}) {
258
+ const n = this.rows;
259
+ if (n !== this.cols) throw new Error('exp requires square matrix');
260
+ const ident = Matrix.identity(n);
261
+ const normInf = (M) => {
262
+ let max = new Decimal(0);
263
+ for (let i = 0; i < M.rows; i++) {
264
+ let rowSum = new Decimal(0);
265
+ for (let j = 0; j < M.cols; j++) rowSum = rowSum.plus(M.data[i][j].abs());
266
+ if (rowSum.greaterThan(max)) max = rowSum;
267
+ }
268
+ return max;
269
+ };
270
+ const maxNorm = normInf(this);
271
+ let s = 0;
272
+ if (maxNorm.greaterThan(new Decimal(1))) {
273
+ const ratio = maxNorm.div(new Decimal(1));
274
+ s = Math.max(0, Math.ceil(Math.log2(ratio.toNumber())));
275
+ }
276
+ let A = this;
277
+ if (s > 0) A = this.mul(new Decimal(1).div(new Decimal(2).pow(s)));
278
+ const maxIter = options.maxIter || 120;
279
+ const tol = new Decimal(options.tolerance || '1e-40');
280
+ let term = ident.clone();
281
+ let result = ident.clone();
282
+ for (let k = 1; k < maxIter; k++) {
283
+ term = term.mul(A).mul(new Decimal(1).div(k));
284
+ result = result.add(term);
285
+ // smallness check
286
+ let tnorm = new Decimal(0);
287
+ for (let i = 0; i < term.rows; i++) for (let j = 0; j < term.cols; j++) tnorm = tnorm.plus(term.data[i][j].abs());
288
+ if (tnorm.lessThan(tol)) break;
289
+ }
290
+ for (let i = 0; i < s; i++) result = result.mul(result);
291
+ return result;
292
+ }
293
+ }
@@ -0,0 +1,65 @@
1
+ import Decimal from 'decimal.js';
2
+ import { Matrix } from './matrix.js';
3
+ const D = x => (x instanceof Decimal ? x : new Decimal(x));
4
+
5
+ export function translation(tx, ty) {
6
+ return Matrix.from([
7
+ [new Decimal(1), new Decimal(0), D(tx)],
8
+ [new Decimal(0), new Decimal(1), D(ty)],
9
+ [new Decimal(0), new Decimal(0), new Decimal(1)]
10
+ ]);
11
+ }
12
+
13
+ export function scale(sx, sy = null) {
14
+ if (sy === null) sy = sx;
15
+ return Matrix.from([
16
+ [D(sx), new Decimal(0), new Decimal(0)],
17
+ [new Decimal(0), D(sy), new Decimal(0)],
18
+ [new Decimal(0), new Decimal(0), new Decimal(1)]
19
+ ]);
20
+ }
21
+
22
+ export function rotate(theta) {
23
+ const t = D(theta);
24
+ const c = new Decimal(Math.cos(t.toNumber()));
25
+ const s = new Decimal(Math.sin(t.toNumber()));
26
+ return Matrix.from([
27
+ [c, s.negated(), new Decimal(0)],
28
+ [s, c, new Decimal(0)],
29
+ [new Decimal(0), new Decimal(0), new Decimal(1)]
30
+ ]);
31
+ }
32
+
33
+ export function rotateAroundPoint(theta, px, py) {
34
+ return translation(px, py).mul(rotate(theta)).mul(translation(new Decimal(px).neg(), new Decimal(py).neg()));
35
+ }
36
+
37
+ export function skew(ax, ay) {
38
+ return Matrix.from([
39
+ [new Decimal(1), D(ax), new Decimal(0)],
40
+ [D(ay), new Decimal(1), new Decimal(0)],
41
+ [new Decimal(0), new Decimal(0), new Decimal(1)]
42
+ ]);
43
+ }
44
+
45
+ export function stretchAlongAxis(ux, uy, k) {
46
+ const uxD = D(ux), uyD = D(uy), kD = D(k);
47
+ const one = new Decimal(1);
48
+ const factor = kD.minus(one);
49
+ const m00 = one.plus(factor.mul(uxD.mul(uxD)));
50
+ const m01 = factor.mul(uxD.mul(uyD));
51
+ const m10 = factor.mul(uyD.mul(uxD));
52
+ const m11 = one.plus(factor.mul(uyD.mul(uyD)));
53
+ return Matrix.from([
54
+ [m00, m01, new Decimal(0)],
55
+ [m10, m11, new Decimal(0)],
56
+ [new Decimal(0), new Decimal(0), new Decimal(1)]
57
+ ]);
58
+ }
59
+
60
+ export function applyTransform(M, x, y) {
61
+ const P = Matrix.from([[D(x)], [D(y)], [new Decimal(1)]]);
62
+ const R = M.mul(P);
63
+ const rx = R.data[0][0], ry = R.data[1][0], rw = R.data[2][0];
64
+ return [rx.div(rw), ry.div(rw)];
65
+ }
@@ -0,0 +1,51 @@
1
+ import Decimal from 'decimal.js';
2
+ import { Matrix } from './matrix.js';
3
+ const D = x => (x instanceof Decimal ? x : new Decimal(x));
4
+
5
+ export function translation(tx, ty, tz) {
6
+ return Matrix.from([
7
+ [new Decimal(1), new Decimal(0), new Decimal(0), D(tx)],
8
+ [new Decimal(0), new Decimal(1), new Decimal(0), D(ty)],
9
+ [new Decimal(0), new Decimal(0), new Decimal(1), D(tz)],
10
+ [new Decimal(0), new Decimal(0), new Decimal(0), new Decimal(1)]
11
+ ]);
12
+ }
13
+
14
+ export function scale(sx, sy = null, sz = null) {
15
+ if (sy === null) sy = sx;
16
+ if (sz === null) sz = sx;
17
+ return Matrix.from([
18
+ [D(sx), new Decimal(0), new Decimal(0), new Decimal(0)],
19
+ [new Decimal(0), D(sy), new Decimal(0), new Decimal(0)],
20
+ [new Decimal(0), new Decimal(0), D(sz), new Decimal(0)],
21
+ [new Decimal(0), new Decimal(0), new Decimal(0), new Decimal(1)]
22
+ ]);
23
+ }
24
+
25
+ // rotation around arbitrary axis (ux,uy,uz) through origin by angle theta (radians)
26
+ export function rotateAroundAxis(ux, uy, uz, theta) {
27
+ const u = [D(ux), D(uy), D(uz)];
28
+ let norm = u[0].mul(u[0]).plus(u[1].mul(u[1])).plus(u[2].mul(u[2])).sqrt();
29
+ if (norm.isZero()) throw new Error('Rotation axis cannot be zero');
30
+ u[0] = u[0].div(norm); u[1] = u[1].div(norm); u[2] = u[2].div(norm);
31
+ const t = D(theta);
32
+ const c = new Decimal(Math.cos(t.toNumber()));
33
+ const s = new Decimal(Math.sin(t.toNumber()));
34
+ const one = new Decimal(1);
35
+ const ux2 = u[0].mul(u[0]), uy2 = u[1].mul(u[1]), uz2 = u[2].mul(u[2]);
36
+ const m00 = ux2.plus(c.mul(one.minus(ux2)));
37
+ const m01 = u[0].mul(u[1]).mul(one.minus(c)).minus(u[2].mul(s));
38
+ const m02 = u[0].mul(u[2]).mul(one.minus(c)).plus(u[1].mul(s));
39
+ const m10 = u[1].mul(u[0]).mul(one.minus(c)).plus(u[2].mul(s));
40
+ const m11 = uy2.plus(c.mul(one.minus(uy2)));
41
+ const m12 = u[1].mul(u[2]).mul(one.minus(c)).minus(u[0].mul(s));
42
+ const m20 = u[2].mul(u[0]).mul(one.minus(c)).minus(u[1].mul(s));
43
+ const m21 = u[2].mul(u[1]).mul(one.minus(c)).plus(u[0].mul(s));
44
+ const m22 = uz2.plus(c.mul(one.minus(uz2)));
45
+ return Matrix.from([
46
+ [m00, m01, m02, new Decimal(0)],
47
+ [m10, m11, m12, new Decimal(0)],
48
+ [m20, m21, m22, new Decimal(0)],
49
+ [new Decimal(0), new Decimal(0), new Decimal(0), new Decimal(1)]
50
+ ]);
51
+ }
package/src/vector.js ADDED
@@ -0,0 +1,140 @@
1
+ import Decimal from 'decimal.js';
2
+ const D = x => (x instanceof Decimal ? x : new Decimal(x));
3
+
4
+ export class Vector {
5
+ constructor(components) {
6
+ if (!Array.isArray(components)) throw new Error('Vector requires array');
7
+ this.data = components.map(c => D(c));
8
+ this.length = this.data.length;
9
+ }
10
+
11
+ static from(arr) {
12
+ return new Vector(arr);
13
+ }
14
+
15
+ clone() {
16
+ return new Vector(this.data.map(v => new Decimal(v)));
17
+ }
18
+
19
+ toArray() {
20
+ return this.data.map(v => v);
21
+ }
22
+
23
+ toNumberArray() {
24
+ return this.data.map(v => v.toNumber());
25
+ }
26
+
27
+ toStringArray() {
28
+ return this.data.map(v => v.toString());
29
+ }
30
+
31
+ // elementwise add/sub
32
+ add(other) {
33
+ if (!(other instanceof Vector)) throw new Error('add expects Vector');
34
+ if (other.length !== this.length) throw new Error('shape mismatch');
35
+ return new Vector(this.data.map((v, i) => v.plus(other.data[i])));
36
+ }
37
+
38
+ sub(other) {
39
+ if (!(other instanceof Vector)) throw new Error('sub expects Vector');
40
+ if (other.length !== this.length) throw new Error('shape mismatch');
41
+ return new Vector(this.data.map((v, i) => v.minus(other.data[i])));
42
+ }
43
+
44
+ // scalar multiplication
45
+ scale(scalar) {
46
+ const s = D(scalar);
47
+ return new Vector(this.data.map(v => v.mul(s)));
48
+ }
49
+
50
+ // dot product
51
+ dot(other) {
52
+ if (!(other instanceof Vector)) throw new Error('dot expects Vector');
53
+ if (other.length !== this.length) throw new Error('shape mismatch');
54
+ return this.data.reduce((acc, v, i) => acc.plus(v.mul(other.data[i])), new Decimal(0));
55
+ }
56
+
57
+ // outer / tensor product returns Matrix-like 2D array (Decimal)
58
+ outer(other) {
59
+ if (!(other instanceof Vector)) throw new Error('outer expects Vector');
60
+ const rows = this.length, cols = other.length;
61
+ const out = Array.from({ length: rows }, (_, i) =>
62
+ Array.from({ length: cols }, (_, j) => this.data[i].mul(other.data[j]))
63
+ );
64
+ return out;
65
+ }
66
+
67
+ // cross product for 3D vectors
68
+ cross(other) {
69
+ if (this.length !== 3 || other.length !== 3) throw new Error('cross requires 3D vectors');
70
+ const [a1, a2, a3] = this.data;
71
+ const [b1, b2, b3] = other.data;
72
+ return new Vector([
73
+ a2.mul(b3).minus(a3.mul(b2)),
74
+ a3.mul(b1).minus(a1.mul(b3)),
75
+ a1.mul(b2).minus(a2.mul(b1))
76
+ ]);
77
+ }
78
+
79
+ // norm (Euclidean)
80
+ norm() {
81
+ let sum = new Decimal(0);
82
+ for (const v of this.data) sum = sum.plus(v.mul(v));
83
+ return sum.sqrt();
84
+ }
85
+
86
+ // normalize: returns a new Vector
87
+ normalize() {
88
+ const n = this.norm();
89
+ if (n.isZero()) throw new Error('Cannot normalize zero vector');
90
+ return this.scale(new Decimal(1).div(n));
91
+ }
92
+
93
+ // angle between vectors (radians)
94
+ angleBetween(other) {
95
+ const dot = this.dot(other);
96
+ const n1 = this.norm();
97
+ const n2 = other.norm();
98
+ if (n1.isZero() || n2.isZero()) throw new Error('Angle with zero vector undefined');
99
+ // clamp cosine to [-1,1] for numerical safety
100
+ let cosv = dot.div(n1.mul(n2));
101
+ // toNumber clamping fallback:
102
+ const cosNum = cosv.toNumber();
103
+ const clamped = Math.min(1, Math.max(-1, cosNum));
104
+ return new Decimal(Math.acos(clamped));
105
+ }
106
+
107
+ // projection of this onto other (vector)
108
+ projectOnto(other) {
109
+ const denom = other.dot(other);
110
+ if (denom.isZero()) throw new Error('Cannot project onto zero vector');
111
+ const coef = this.dot(other).div(denom);
112
+ return other.scale(coef);
113
+ }
114
+
115
+ // compute an orthogonal vector (for 2D returns perpendicular; for higher dims returns Gram-Schmidt complement)
116
+ orthogonal() {
117
+ if (this.length === 2) {
118
+ // perp: [-y, x]
119
+ return new Vector([this.data[1].negated(), this.data[0]]);
120
+ }
121
+ // for n>2: find a vector not colinear with this and make orthogonal via Gram-Schmidt with standard basis
122
+ for (let i = 0; i < this.length; i++) {
123
+ const ei = Array.from({ length: this.length }, (_, j) => new Decimal(j === i ? 1 : 0));
124
+ let candidate = new Vector(ei);
125
+ // check not parallel
126
+ const crossDim = Math.min(3, this.length);
127
+ // project candidate out of this
128
+ const proj = candidate.projectOnto(this);
129
+ const orth = candidate.sub(proj);
130
+ const norm = orth.norm();
131
+ if (!norm.isZero()) return orth.normalize();
132
+ }
133
+ throw new Error('Unable to find orthogonal vector');
134
+ }
135
+
136
+ // check orthogonality with other
137
+ isOrthogonalTo(other) {
138
+ return this.dot(other).isZero();
139
+ }
140
+ }
@@ -0,0 +1,18 @@
1
+ import { Matrix, Decimal } from '../src/index.js';
2
+
3
+ Decimal.set({ precision: 40 });
4
+
5
+ const A = Matrix.from([[1.234567890123456789, 2.34567890123456789], [3.4567890123456789, 4.567890123456789]]);
6
+ console.log('A:', A.toArrayOfStrings());
7
+
8
+ const det = A.determinant();
9
+ console.log('det(A):', det.toString());
10
+
11
+ const inv = A.inverse();
12
+ console.log('inv(A):', inv.toArrayOfStrings());
13
+
14
+ const x = A.solve([1, 2]);
15
+ console.log('solution x for Ax=[1,2]:', x.toArrayOfStrings());
16
+
17
+ const B = Matrix.from([[1, 0], [0, 1]]);
18
+ console.log('A * inv(A):', A.mul(inv).toArrayOfStrings());
@@ -0,0 +1,50 @@
1
+ import Decimal from 'decimal.js';
2
+ import { Matrix, Vector, Transforms2D, Transforms3D } from '../src/index.js';
3
+
4
+ Decimal.set({ precision: 80 });
5
+
6
+ // Vector operations
7
+ const v = Vector.from(['1.234567890123456789', '2.34567890123456789', '3.4567890123456789']);
8
+ const w = Vector.from(['3.333333333333333333', '4.444444444444444444', '5.555555555555555555']);
9
+ console.log('v =', v.toStringArray());
10
+ console.log('w =', w.toStringArray());
11
+ console.log('dot(v,w) =', v.dot(w).toString());
12
+ console.log('v cross w =', v.cross(w).toStringArray());
13
+ console.log('v norm =', v.norm().toString());
14
+ console.log('normalized v =', v.normalize().toStringArray());
15
+ console.log('angle(v,w) =', v.angleBetween(w).toString());
16
+
17
+ // Matrix * vector
18
+ const M = Matrix.from([[1.5, 0, 0], [0, 2.5, 0], [0, 0, 3.5]]);
19
+ const mv = M.applyToVector(v);
20
+ console.log('M * v =', mv.toStringArray());
21
+
22
+ // 2D transforms: concatenation and inverse
23
+ const T = Transforms2D.translation(2, 3);
24
+ const R = Transforms2D.rotate(Math.PI / 4);
25
+ const S = Transforms2D.scale('1.5');
26
+ const M2 = T.mul(R).mul(S);
27
+ console.log('Combined 2D M2:', M2.toArrayOfStrings());
28
+ const p = [1, 0];
29
+ const p2 = Transforms2D.applyTransform(M2, ...p);
30
+ console.log('Point', p, '->', p2.map(x => x.toString()));
31
+ const Minv = M2.inverse();
32
+ const pBack = Transforms2D.applyTransform(Minv, ...p2);
33
+ console.log('Back:', pBack.map(x => x.toString()));
34
+
35
+ // 3D example
36
+ const R3 = Transforms3D.rotateAroundAxis(1, 1, 0, Math.PI / 3);
37
+ const T3 = Transforms3D.translation('0.5', '1.2', '3.4');
38
+ const M3 = T3.mul(R3);
39
+ console.log('3D transform M3:', M3.toArrayOfStrings());
40
+
41
+ // Matrix exponential example (2x2 rot generator)
42
+ const A = Matrix.from([[0, -1], [1, 0]]);
43
+ const expA = A.exp();
44
+ console.log('exp(A) approx rotation:', expA.toArrayOfStrings());
45
+
46
+ // Solve linear system
47
+ const B = Matrix.from([[4, 7], [2, 6]]);
48
+ const bvec = [1, 1];
49
+ const x = B.solve(bvec);
50
+ console.log('Solution x for B x = b:', x.toStringArray());