RosettaCodeData/Task/QR-decomposition/JavaScript/qr-decomposition.js

269 lines
7.5 KiB
JavaScript
Raw Permalink Normal View History

2026-04-30 12:34:36 -04:00
class Matrix {
constructor(data) {
if (data instanceof Matrix) {
this.data = data.data.map(row => [...row]);
this.rowCount = data.rowCount;
this.columnCount = data.columnCount;
} else if (typeof data === 'number') {
const rows = data;
const cols = arguments[1];
this.rowCount = rows;
this.columnCount = cols;
this.data = Array(rows).fill(0).map(() => Array(cols).fill(0));
} else {
this.rowCount = data.length;
this.columnCount = data[0].length;
this.data = data.map(row => [...row]);
}
}
add(other) {
if (other.rowCount !== this.rowCount || other.columnCount !== this.columnCount) {
throw new Error('Incompatible matrix dimensions.');
}
const result = new Matrix(this);
for (let i = 0; i < this.rowCount; i++) {
for (let j = 0; j < this.columnCount; j++) {
result.data[i][j] = this.data[i][j] + other.data[i][j];
}
}
return result;
}
multiply(other) {
if (this.columnCount !== other.rowCount) {
throw new Error('Incompatible matrix dimensions.');
}
const result = new Matrix(this.rowCount, other.columnCount);
for (let i = 0; i < this.rowCount; i++) {
for (let j = 0; j < other.columnCount; j++) {
for (let k = 0; k < this.rowCount; k++) {
result.data[i][j] += this.data[i][k] * other.data[k][j];
}
}
}
return result;
}
transpose() {
const result = new Matrix(this.columnCount, this.rowCount);
for (let i = 0; i < this.rowCount; i++) {
for (let j = 0; j < this.columnCount; j++) {
result.data[j][i] = this.data[i][j];
}
}
return result;
}
minor(index) {
const result = new Matrix(this.rowCount, this.columnCount);
for (let i = 0; i < index; i++) {
result.setEntry(i, i, 1.0);
}
for (let i = index; i < this.rowCount; i++) {
for (let j = index; j < this.columnCount; j++) {
result.setEntry(i, j, this.data[i][j]);
}
}
return result;
}
column(index) {
const result = new Matrix(this.rowCount, 1);
for (let i = 0; i < this.rowCount; i++) {
result.setEntry(i, 0, this.data[i][index]);
}
return result;
}
scalarMultiply(value) {
if (this.columnCount !== 1) {
throw new Error('Incompatible matrix dimension.');
}
const result = new Matrix(this.rowCount, this.columnCount);
for (let i = 0; i < this.rowCount; i++) {
result.data[i][0] = this.data[i][0] * value;
}
return result;
}
unit() {
if (this.columnCount !== 1) {
throw new Error('Incompatible matrix dimensions.');
}
const magnitude = this.magnitude();
const result = new Matrix(this.rowCount, this.columnCount);
for (let i = 0; i < this.rowCount; i++) {
result.data[i][0] = this.data[i][0] / magnitude;
}
return result;
}
magnitude() {
if (this.columnCount !== 1) {
throw new Error('Incompatible matrix dimensions.');
}
let norm = 0.0;
for (let i = 0; i < this.data.length; i++) {
norm += this.data[i][0] * this.data[i][0];
}
return Math.sqrt(norm);
}
size() {
if (this.columnCount !== 1) {
throw new Error('Incompatible matrix dimensions.');
}
return this.rowCount;
}
display(title) {
console.log(title);
for (let i = 0; i < this.rowCount; i++) {
let row = '';
for (let j = 0; j < this.columnCount; j++) {
row += this.data[i][j].toFixed(4).padStart(9);
}
console.log(row);
}
console.log();
}
getEntry(row, column) {
return this.data[row][column];
}
setEntry(row, column, value) {
this.data[row][column] = value;
}
getRowCount() {
return this.rowCount;
}
getColumnCount() {
return this.columnCount;
}
}
function householder(matrix) {
const rowCount = matrix.getRowCount();
const columnCount = matrix.getColumnCount();
const versionsOfQ = [];
let z = new Matrix(matrix);
let z1 = new Matrix(rowCount, columnCount);
for (let k = 0; k < columnCount && k < rowCount - 1; k++) {
const vectorE = new Matrix(rowCount, 1);
z1 = z.minor(k);
const vectorX = z1.column(k);
let magnitudeX = vectorX.magnitude();
if (matrix.getEntry(k, k) > 0) {
magnitudeX = -magnitudeX;
}
for (let i = 0; i < vectorE.size(); i++) {
vectorE.setEntry(i, 0, i === k ? 1 : 0);
}
const e = vectorE.scalarMultiply(magnitudeX).add(vectorX).unit();
versionsOfQ.push(householderFactor(e));
z = versionsOfQ[k].multiply(z1);
}
let Q = versionsOfQ[0];
for (let i = 1; i < columnCount && i < rowCount - 1; i++) {
Q = versionsOfQ[i].multiply(Q);
}
const R = Q.multiply(matrix);
Q = Q.transpose();
return { r: R, q: Q };
}
function householderFactor(vector) {
if (vector.getColumnCount() !== 1) {
throw new Error('Incompatible matrix dimensions.');
}
const size = vector.size();
const result = new Matrix(size, size);
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
result.setEntry(i, j, -2 * vector.getEntry(i, 0) * vector.getEntry(j, 0));
}
}
for (let i = 0; i < size; i++) {
result.setEntry(i, i, result.getEntry(i, i) + 1.0);
}
return result;
}
function fitPolynomial(x, y, polynomialDegree) {
const vandermonde = new Matrix(x.getColumnCount(), polynomialDegree + 1);
for (let i = 0; i < x.getColumnCount(); i++) {
for (let j = 0; j < polynomialDegree + 1; j++) {
vandermonde.setEntry(i, j, Math.pow(x.getEntry(0, i), j));
}
}
return leastSquares(vandermonde, y.transpose());
}
function leastSquares(vandermonde, b) {
const pair = householder(vandermonde);
return solveUpperTriangular(pair.r, pair.q.transpose().multiply(b));
}
function solveUpperTriangular(r, b) {
const columnCount = r.getColumnCount();
const result = new Matrix(columnCount, 1);
for (let k = columnCount - 1; k >= 0; k--) {
let total = 0.0;
for (let j = k + 1; j < columnCount; j++) {
total += r.getEntry(k, j) * result.getEntry(j, 0);
}
result.setEntry(k, 0, (b.getEntry(k, 0) - total) / r.getEntry(k, k));
}
return result;
}
// Main execution
const data = [
[12.0, -51.0, 4.0],
[6.0, 167.0, -68.0],
[-4.0, 24.0, -41.0],
[-1.0, 1.0, 0.0],
[2.0, 0.0, 3.0]
];
// Task 1
const A = new Matrix(data);
A.display('Initial matrix A:');
const pair = householder(A);
const Q = pair.q;
const R = pair.r;
Q.display('Matrix Q:');
R.display('Matrix R:');
const result = Q.multiply(R);
result.display('Matrix Q * R:');
// Task 2
const x = new Matrix([[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]]);
const y = new Matrix([[1.0, 6.0, 17.0, 34.0, 57.0, 86.0, 121.0, 162.0, 209.0, 262.0, 321.0]]);
const polyResult = fitPolynomial(x, y, 2);
polyResult.display('Result of fitting polynomial:');