@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.
- package/.github/workflows/publish.yml +34 -0
- package/.github/workflows/release.yml +37 -0
- package/.github/workflows/test.yml +21 -0
- package/CLAUDE.md +60 -0
- package/LICENSE +8 -0
- package/README.md +25 -0
- package/SVG-MATRIX/README.md +22 -0
- package/SVG-MATRIX/package.json +33 -0
- package/emasoft-svg-matrix-1.0.1.tgz +0 -0
- package/npm-shrinkwrap.json +22 -0
- package/package.json +32 -0
- package/scripts/bootstrap_repo.sh +99 -0
- package/src/index.js +7 -0
- package/src/matrix.js +293 -0
- package/src/transforms2d.js +65 -0
- package/src/transforms3d.js +51 -0
- package/src/vector.js +140 -0
- package/test/example.js +18 -0
- package/test/examples.js +50 -0
|
@@ -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
|
+
}
|
package/test/example.js
ADDED
|
@@ -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());
|
package/test/examples.js
ADDED
|
@@ -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());
|