sellquiz
Version:
An open source domain-specific language for online assessment
358 lines (324 loc) • 13.4 kB
text/typescript
/******************************************************************************
* SELL - SIMPLE E-LEARNING LANGUAGE *
* *
* Copyright (c) 2019-2021 TH Köln *
* Author: Andreas Schwenk, contact@compiler-construction.com *
* *
* Partly funded by: Digitale Hochschule NRW *
* https://www.dh.nrw/kooperationen/hm4mint.nrw-31 *
* *
* GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 *
* *
* This library is licensed as described in LICENSE, which you should have *
* received as part of this distribution. *
* *
* This software is distributed on "AS IS" basis, WITHOUT WARRENTY OF ANY *
* KIND, either impressed or implied. *
******************************************************************************/
// this file implements math functions that are NOT provided by math.js
import * as math from 'mathjs';
import { sellassert } from './sellassert.js';
export class SellLinAlg {
static mat_idx(m : number, n : number, i : number, j : number) {
return i * n + j;
}
static mat_get_row_count(mathjs_matrix) {
return mathjs_matrix.size()[0];
}
static mat_get_col_count(mathjs_matrix) {
return mathjs_matrix.size()[1];
}
static mat_get_element_value(mathjs_matrix, i, j) {
return mathjs_matrix.subset(math.index(i, j));
}
static mat_submatrix(mathjs_matrix, first_row, last_row, first_col, last_col) {
let m = mathjs_matrix.size()[0];
let n = mathjs_matrix.size()[1];
if(last_row == -1) // -1 := last row
last_row = m - 1;
if(last_col == -1) // -1 := last col
last_col = n - 1;
if(first_row > last_row || first_col > last_col)
return null;
if(first_row < 0 || first_col < 0 || first_row>=m || first_col>=n)
return null;
if(last_row < 0 || last_col < 0 || last_row>=m || last_col>=n)
return null;
let resM = last_row - first_row + 1;
let resN = last_col - first_col + 1;
let res = math.zeros(resM, resN);
for(let i=first_row; i<=last_row; i++) {
for(let j=first_col; j<=last_col; j++) {
let v = mathjs_matrix.subset(math.index(i, j));
res = this.mat_set_element(res, i-first_row, j-first_col, v);
}
}
return res;
}
static mat_rank(mathjs_matrix) {
// implementation based on https://cp-algorithms.com/linear_algebra/rank-matrix.html
let epsilon = 1e-12;
let m = mathjs_matrix.size()[0];
let n = mathjs_matrix.size()[1];
let v = [];
for (let i = 0; i < m; i++)
for (let j = 0; j < n; j++)
v.push(mathjs_matrix.subset(math.index(i, j)));
let rank = 0;
let row_selected = [];
for (let k = 0; k < n; k++)
row_selected.push(false);
for (let i = 0; i < m; i++) {
let j;
for (j = 0; j < n; j++) {
if (!row_selected[j] && Math.abs(v[this.mat_idx(m, n, i, j)]) > epsilon) {
break;
}
}
if (j != n) {
rank++;
row_selected[j] = true;
for (let p = i + 1; p < m; p++)
v[this.mat_idx(m, n, p, j)] /= v[this.mat_idx(m, n, i, j)];
for (let k = 0; k < n; k++) {
if (k != j && Math.abs(v[this.mat_idx(m, n, i, k)]) > epsilon) {
for (let p = i + 1; p < m; p++) {
v[this.mat_idx(m, n, p, k)] -= v[this.mat_idx(m, n, p, j)] * v[this.mat_idx(m, n, i, k)];
}
}
}
}
}
return rank;
}
static mat_is_symmetric(mathjs_matrix) {
let m = mathjs_matrix.size()[0];
let n = mathjs_matrix.size()[1];
if(m != n)
return false;
for(let i=0; i<n; i++) {
for(let j=i+1; j<n; j++) {
let a = mathjs_matrix.subset(math.index(i, j));
let b = mathjs_matrix.subset(math.index(j, i));
if(math.abs(a-b) > 1e-14)
return false;
}
}
return true;
}
static mat_triu(mathjs_matrix) {
let m = mathjs_matrix.size()[0];
let n = mathjs_matrix.size()[1];
let res = math.zeros(m, n);
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
let v = mathjs_matrix.subset(math.index(i, j));
if (j < i)
v = 0;
res = (res as math.Matrix).subset(math.index(i, j), v);
}
}
return res;
}
static mat_norm2(mathjs_matrix) {
let m = mathjs_matrix.size()[0];
let n = mathjs_matrix.size()[1];
let res = 0.0;
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
let e_ij = (mathjs_matrix as math.Matrix).subset(math.index(i, j)) as any;
res += e_ij * e_ij;
}
}
return math.sqrt(res);
}
static mat_vecdot(mathjs_matrix_1, mathjs_matrix_2) {
let m1 = mathjs_matrix_1.size()[0];
let n1 = mathjs_matrix_1.size()[1];
let m2 = mathjs_matrix_2.size()[0];
let n2 = mathjs_matrix_2.size()[1];
if(n1!=1 || n2!=1 || m1!=m2)
sellassert(false, "mat_vecdot(..): invalid input");
let res = 0.0;
for (let i = 0; i < m1; i++) {
res += mathjs_matrix_1.subset(math.index(i, 0))
* mathjs_matrix_2.subset(math.index(i, 0))
}
return res;
}
static mat_veccross(mathjs_matrix_1, mathjs_matrix_2) {
let m1 = mathjs_matrix_1.size()[0];
let n1 = mathjs_matrix_1.size()[1];
let m2 = mathjs_matrix_2.size()[0];
let n2 = mathjs_matrix_2.size()[1];
if(n1!=1 || n2!=1 || m1!=3 || m2!=3)
sellassert(false, "mat_veccross(..): invalid input");
let u1 = this.mat_get_element_value(mathjs_matrix_1, 0, 0);
let u2 = this.mat_get_element_value(mathjs_matrix_1, 1, 0);
let u3 = this.mat_get_element_value(mathjs_matrix_1, 2, 0);
let v1 = this.mat_get_element_value(mathjs_matrix_2, 0, 0);
let v2 = this.mat_get_element_value(mathjs_matrix_2, 1, 0);
let v3 = this.mat_get_element_value(mathjs_matrix_2, 2, 0);
let res = math.zeros(3, 1);
res = (res as math.Matrix).subset(math.index(0, 0), u2*v3-u3*v2);
res = (res as math.Matrix).subset(math.index(1, 0), u3*v1-u1*v3);
res = (res as math.Matrix).subset(math.index(2, 0), u1*v2-u2*v1);
return res;
}
static mat_is_row_zero(mat_v, m, n, i) {
let epsilon = 1e-12;
for (let j = 0; j < n; j++) {
if (Math.abs(mat_v[this.mat_idx(m, n, i, j)]) > epsilon)
return false;
}
return true;
}
static mat_is_zero(mathjs_matrix) {
const EPSILON = 1e-12;
let m = mathjs_matrix.size()[0];
let n = mathjs_matrix.size()[1];
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
let v = mathjs_matrix.subset(math.index(i, j));
if (Math.abs(v) > EPSILON)
return false;
}
}
return true;
}
static linsolve(mathjs_matrix_A, mathjs_vector_b) {
let m = mathjs_matrix_A.size()[0];
let n = mathjs_matrix_A.size()[1];
return math.lusolve(mathjs_matrix_A.clone(), mathjs_vector_b.clone());
}
static mat_kernel(mathjs_matrix) {
// TODO: better use SVD; but yet no suitable implementation in JavaScript found!
//mathjs_matrix = math.matrix([[2, 1, -1],[0, 2, 3]]); // TODO: remove test!!!!!
//mathjs_matrix = math.matrix([[1, 2, 3],[0, 4, 5],[0, 0, 6]]); // TODO: remove test!!!!!
let lup = math.lup(mathjs_matrix) as any;
let u = lup["U"];
let m = u.size()[0];
let n = u.size()[1];
let v = [];
for (let i = 0; i < m; i++)
for (let j = 0; j < n; j++)
v.push((u as math.Matrix).subset(math.index(i, j)));
// get number of nonzero rows
let nz;
for (nz = 0; nz < m; nz++) {
if (this.mat_is_row_zero(v, m, n, nz))
break;
}
//alert(v)
//alert(nz)
// set upper-right to zero
for (let j = nz - 1; j >= 1; j--) {
for (let i = j - 1; i >= 0; i--) {
//alert(i + ' ' + j)
let f = v[this.mat_idx(m, n, i, j)] / v[this.mat_idx(m, n, j, j)]
//alert(f)
for (let k = 0; k < n; k++) {
v[this.mat_idx(m, n, i, k)] -= f * v[this.mat_idx(m, n, j, k)];
}
}
}
//alert(v)
// normalize := divide by pivot elements
for (let i = 0; i < nz; i++) {
let p = v[this.mat_idx(m, n, i, i)]
for (let j = 0; j < n; j++) {
v[this.mat_idx(m, n, i, j)] /= p
}
}
//alert(v)
// resulting matrix: ker-vector per column
let dest_m = n;
let dest_n = n - nz;
let dest = [];
for (let k = 0; k < dest_m * dest_n; k++)
dest.push(0.0);
for (let k = 0; k < dest_n; k++)
dest[this.mat_idx(dest_m, dest_n, dest_m - 1 - k, dest_n - 1 - k)] = -1; // "-1 - trick"
for (let i = 0; i < nz; i++) {
for (let j = 0; j < dest_n; j++) {
dest[this.mat_idx(dest_m, dest_n, i, j)] = v[this.mat_idx(m, n, i, j + nz)]
}
}
let dest_mathjs = math.zeros(dest_m, dest_n);
for (let i = 0; i < dest_m; i++) {
for (let j = 0; j < dest_n; j++) {
dest_mathjs = (dest_mathjs as math.Matrix).subset(math.index(i, j), dest[this.mat_idx(dest_m, dest_n, i, j)]);
}
}
return dest;
}
static mat_set_element(mathjs_matrix, i, j, value) {
let m = mathjs_matrix.size()[0];
let n = mathjs_matrix.size()[1];
if(i<0 || i>=m)
return null;
if(j<0 || j>=n)
return null;
mathjs_matrix = mathjs_matrix.subset(math.index(i, j), value);
return mathjs_matrix;
}
static mat_mod(mathjs_matrix, op2) {
let m = mathjs_matrix.size()[0];
let n = mathjs_matrix.size()[1];
let dest = math.zeros(m, n);
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
let v = Math.round(mathjs_matrix.subset(math.index(i, j)))
v = math.mod(v, op2);
dest = (dest as math.Matrix).subset(math.index(i, j), v);
}
}
return dest;
}
static mat_compare_numerically(mathjs_matrix_a, mathjs_matrix_b) {
const EPSILON = 1e-12;
let m_a = mathjs_matrix_a.size()[0];
let n_a = mathjs_matrix_a.size()[1];
let m_b = mathjs_matrix_b.size()[0];
let n_b = mathjs_matrix_b.size()[1];
if (m_a != m_b && n_a != n_b)
return false;
for (let i = 0; i < m_a; i++) {
for (let j = 0; j < n_a; j++) {
let va = mathjs_matrix_a.subset(math.index(i, j));
let vb = mathjs_matrix_b.subset(math.index(i, j));
if (Math.abs(va - vb) > EPSILON)
return false;
}
}
return true;
}
static mat_compare_numerically_except_scaling_factor(mathjs_matrix_a, mathjs_matrix_b) {
const EPSILON = 1e-12;
let m_a = mathjs_matrix_a.size()[0];
let n_a = mathjs_matrix_a.size()[1];
let m_b = mathjs_matrix_b.size()[0];
let n_b = mathjs_matrix_b.size()[1];
if (m_a != m_b && n_a != n_b)
return false;
let initial_f = true;
let f = 1.0;
for (let i = 0; i < m_a; i++) {
for (let j = 0; j < n_a; j++) {
let va = mathjs_matrix_a.subset(math.index(i, j));
let vb = mathjs_matrix_b.subset(math.index(i, j));
if (Math.abs(va * f - vb) > EPSILON) {
if (initial_f) {
initial_f = false;
if (Math.abs(va) < EPSILON || Math.abs(vb) < EPSILON)
return false;
f = vb / va;
} else {
return false;
}
}
}
}
return true;
}
} // end of class SellLinAlg