Skip to content
paulwilcox edited this page Jul 31, 2021 · 16 revisions

Home


A matrix is a structure of numbers in tabular format for which mathematical operations that are analagous to arithmetic or that are geometrical in nature are available.

Setup

Below, the fluent-data library is loaded into the variable $$ and and three arrays are created. These arrays are then pushed into three corresponding matricies. These arrays and matricies are used by the examples in this page.

let $$ = require('./dist/fluent-data.server.js');

let community_array = [
    { marker: 'Applewood Park', x: 0, y: 0 },
    { marker: 'Orangewood School', x: 10, y: 0},
    { marker: 'Kiwitown Market', x: 1, y: 10 },
    { marker: `The Millers`, x: -5, y: 0 },
    { marker: 'The Romeros', x: 0, y: -5 },
    { marker: 'The Lees', x: 5, y: 5 },
    { marker: 'The Keitas', x: 5, y: 0 },
    { marker: 'The Lebedevs', x: 15, y: 5 }
];

let plotA_array = [
    { marker: 'possible park', x: 660, y: -660 },
    { marker: 'possible school', x: -1320, y: 1320 }
];

let plotB_array = [
    { marker: 'possible park', x: 112.1830, y: 33.5425 },
    { marker: 'possible school', x: 112.3693, y: 33.6295 }
];

let community = $$(community_array).matrix('x,y', 'marker');
let plotA = $$(plotA_array).matrix('x,y', 'marker');
let plotB = $$(plotB_array).matrix('x,y', 'marker');

For reference in the expandable examples below, the contents of the matricies are as follows:

community.log(null, 'community');
community.log(null, 'plotA');
community.log(null, 'plotB');
community
┌───────────────────┬────┬────┐
│                   │ x  │ y  │
├───────────────────┼────┼────┤
│ Applewood Park    │ 0  │ 0  │
│ Orangewood School │ 10 │ 0  │
│ Kiwitown Market   │ 1  │ 10 │
│ The Millers       │ -5 │ 0  │
│ The Romeros       │ 0  │ -5 │
│ The Lees          │ 5  │ 5  │
│ The Keitas        │ 5  │ 0  │
│ The Lebedevs      │ 15 │ 5  │
└───────────────────┴────┴────┘
plotA
┌───────────────────┬────┬────┐
│                   │ x  │ y  │
├───────────────────┼────┼────┤
│ Applewood Park    │ 0  │ 0  │
│ Orangewood School │ 10 │ 0  │
│ Kiwitown Market   │ 1  │ 10 │
│ The Millers       │ -5 │ 0  │
│ The Romeros       │ 0  │ -5 │
│ The Lees          │ 5  │ 5  │
│ The Keitas        │ 5  │ 0  │
│ The Lebedevs      │ 15 │ 5  │
└───────────────────┴────┴────┘
plotB
┌───────────────────┬────┬────┐
│                   │ x  │ y  │
├───────────────────┼────┼────┤
│ Applewood Park    │ 0  │ 0  │
│ Orangewood School │ 10 │ 0  │
│ Kiwitown Market   │ 1  │ 10 │
│ The Millers       │ -5 │ 0  │
│ The Romeros       │ 0  │ -5 │
│ The Lees          │ 5  │ 5  │
│ The Keitas        │ 5  │ 0  │
│ The Lebedevs      │ 15 │ 5  │
└───────────────────┴────┴────┘

Construction

Constructing a Matrix

Perhaps the quickest method to constructing a matrix is to use the csv syntax. Simply list the properties you wish to include in the matrix in a comma separated string. This will pick up the relevant properties for inclusion into the matrix and it will set the column names. You can add an optional second string parameter to identify the property having the row names.

let community = $$(community_array).matrix(
    'x, y', // the properties to include in the matrix
    'marker' // the property describing a given row's name
);

Sometimes it is necessary to construct a matrix in a way that doesn't simply just list the property names. For such situations, an alternate constructor syntax is available that accepts a lambda function returning an array. Its use is below, as is the use of methods that can set the row and column names outside of the constructor.

let community = $$(community_array)
    .matrix(row => [row.x + 1, row.y / 2])
    .setColNames('xPlusOne, yHalf')
    .setRowNames(community_array.map(row => row.marker))

Sometimes you just want to make up a quick matrix straight from an array, and aren't so concerned with row or column names.

let community = new $$.matrix(community_array);

properties

cols

An iterator that allows you to loop the columns of a matrix. Each iteration produces a matrix representing a column in the parent matrix.

for(let col of plotA.cols)
    col.log();
┌─────────────────┬───────┐
│                 │ x     │
├─────────────────┼───────┤
│ possible park   │ 660   │
│ possible school │ -1320 │
└─────────────────┴───────┘
┌─────────────────┬──────┐
│                 │ y    │
├─────────────────┼──────┤
│ possible park   │ -660 │
│ possible school │ 1320 │
└─────────────────┴──────┘
colNames

The column names of a matrix.

console.log(community.colNames);
[ 'x', 'y' ]
data

The underlying nested array representing the core data of the matrix.

console.log(community.data);
[
  [ 0, 0 ],  [ 10, 0 ],
  [ 1, 10 ], [ -5, 0 ],
  [ 0, -5 ], [ 5, 5 ],
  [ 5, 0 ],  [ 15, 5 ]
]
nCell

Produces the count of cells in a matrix.

console.log(`commmunity has ${community.nCell} cells.`);
commmunity has 16 cells.
nCol

Produces the count of columns in a matrix.

console.log(`commmunity has ${community.nCol} columns.`);
commmunity has 2 columns.
nRow

Produces the count of rows in a matrix.

console.log(`commmunity has ${community.nRow} rows.`);
commmunity has 8 rows.
rows

An iterator that allows you to loop the rows of a matrix. Each iteration produces a matrix representing a row in the parent matrix.

for(let row of plotA.rows)
    row.log();
┌───────────────┬─────┬──────┐
│               │ x   │ y    │
├───────────────┼─────┼──────┤
│ possible park │ 660 │ -660 │
└───────────────┴─────┴──────┘
┌─────────────────┬───────┬──────┐
│                 │ x     │ y    │
├─────────────────┼───────┼──────┤
│ possible school │ -1320 │ 1320 │
└─────────────────┴───────┴──────┘
rowNames

The row names of a matrix.

console.log(community.rowNames);
[
  'Applewood Park',
  'Orangewood School',
  'Kiwitown Market',
  'The Millers',
  'The Romeros',
  'The Lees',
  'The Keitas',
  'The Lebedevs'
]

Methods that return a matrix:

add

Add two matrices from each other: cell by cell.

plotA.add(plotB).log();
┌─────────────────┬────────────┬───────────┐
│                 │ x          │ y         │
├─────────────────┼────────────┼───────────┤
│ possible park   │ 772.183    │ -626.4575 │
│ possible school │ -1207.6307 │ 1353.6295 │
└─────────────────┴────────────┴───────────┘
appendCols

Append columns to a matrix.

plotA.appendCols(plotB).log();
┌─────────────────┬──────────┬─────────┐
│                 │ x        │ y       │
├─────────────────┼──────────┼─────────┤
│ possible park   │ 112.183  │ 33.5425 │
│ possible school │ 112.3693 │ 33.6295 │
└─────────────────┴──────────┴─────────┘
appendRows

Append rows to a matrix.

plotA.appendRows(plotB).log();
┌─────────────────┬──────────┬─────────┐
│                 │ x        │ y       │
├─────────────────┼──────────┼─────────┤
│ possible park   │ 660      │ -660    │
│ possible school │ -1320    │ 1320    │
│ possible park   │ 112.183  │ 33.5425 │
│ possible school │ 112.3693 │ 33.6295 │
└─────────────────┴──────────┴─────────┘
apply

Apply a function to each cell in a matrix. Or, apply a function relating each cell in two matrices of the same size.

plotA
    .apply(cell => Math.sign(cell))
    .log(null, 'signs');

plotA
    .apply(plotB, (a,b) => a > b ? 'a' : 'b')
    .log(null, 'which is bigger?');
signs
┌─────────────────┬────┬────┐
│                 │ x  │ y  │
├─────────────────┼────┼────┤
│ possible park   │ 1  │ -1 │
│ possible school │ -1 │ 1  │
└─────────────────┴────┴────┘
which is bigger?
┌─────────────────┬───┬───┐
│                 │ x │ y │
├─────────────────┼───┼───┤
│ possible park   │ a │ b │
│ possible school │ b │ a │
└─────────────────┴───┴───┘
diagonal

Takes the diagonal entries of a matrix to the exclusion of the rest. An optional boolean parameter controls whether the output is as a matrix with zeroed out non-diagonal entries, or if it is a vector with the diagonal entries listed in a single column. The default is the former.

plotA.diagonal().log();
plotA.diagonal(true).log();
┌─────────────────┬─────┬──────┐
│                 │ x   │ y    │
├─────────────────┼─────┼──────┤
│ possible park   │ 660 │ 0    │
│ possible school │ 0   │ 1320 │
└─────────────────┴─────┴──────┘
┌────┬──────┐
│    │ c0   │
├────┼──────┤
│ r0 │ 660  │
│ r1 │ 1320 │
└────┴──────┘
filter

Subsets rows and columns of a matrix based on index attributes or cell value. Remember that javascript has 0-based indexing. So there is such a thing as row 0. It's the first row.

The example below grabs rows 0, 3, and 5 of the community matrix. It repeats the third row twice and it switches the order of the columns.

community.filter([0,3,3,5], [1,0]).log(); 
┌────────────────┬───┬────┐
│                │ y │ x  │
├────────────────┼───┼────┤
│ Applewood Park │ 0 │ 0  │
│ The Millers    │ 0 │ -5 │
│ The Millers    │ 0 │ -5 │
│ The Lees       │ 5 │ 5  │
└────────────────┴───┴────┘

If only one row or column is desired, an integer can be passed instead of an array. If all rows or all columns are desired, pass null or undefined to the respective parameter.

community
    .filter(null, 1)
    .log(null, 'second column'); 

community
    .filter(5)
    .log(null, 'sixth row');

community
    .filter()
    .log(null, 'all');
second column
┌───────────────────┬────┐
│                   │ y  │
├───────────────────┼────┤
│ Applewood Park    │ 0  │
│ Orangewood School │ 0  │
│ Kiwitown Market   │ 10 │
│ The Millers       │ 0  │
│ The Romeros       │ -5 │
│ The Lees          │ 5  │
│ The Keitas        │ 0  │
│ The Lebedevs      │ 5  │
└───────────────────┴────┘
sixth row
┌──────────┬───┬───┐
│          │ x │ y │
├──────────┼───┼───┤
│ The Lees │ 5 │ 5 │
└──────────┴───┴───┘
all
┌───────────────────┬────┬────┐
│                   │ x  │ y  │
├───────────────────┼────┼────┤
│ Applewood Park    │ 0  │ 0  │
│ Orangewood School │ 10 │ 0  │
│ Kiwitown Market   │ 1  │ 10 │
│ The Millers       │ -5 │ 0  │
│ The Romeros       │ 0  │ -5 │
│ The Lees          │ 5  │ 5  │
│ The Keitas        │ 5  │ 0  │
│ The Lebedevs      │ 15 │ 5  │
└───────────────────┴────┴────┘

Here, all rows are fetched becaue the first parameter is null. All columns are returned except the first, because a negative in front of a positional indicates to exclude that column but include the rest. There is such a thing as a negative 0 in javascript, so it is not equivalent to replacing it with positive 0.

community.filter(null, [-0]).log(); 
┌───────────────────┬────┐
│                   │ y  │
├───────────────────┼────┤
│ Applewood Park    │ 0  │
│ Orangewood School │ 0  │
│ Kiwitown Market   │ 10 │
│ The Millers       │ 0  │
│ The Romeros       │ -5 │
│ The Lees          │ 5  │
│ The Keitas        │ 0  │
│ The Lebedevs      │ 5  │
└───────────────────┴────┘

filter also has a functional syntax. The first parameter of the input function is the row or column index. The second parameter is optional. If passed in it points to the entire row or entire column.

The first example below fetches all rows with index of 3 or less (the first four rows). The second example fetches the first column for any row with a value in that column that is positive.

community
    .filter(ix => ix <= 3, null)
    .log(null, 'first four'); 

community
    .filter((ix,row) => row[0] > 0, 0)
    .log(null, 'column 1 positives');
first four
┌───────────────────┬────┬────┐
│                   │ x  │ y  │
├───────────────────┼────┼────┤
│ Applewood Park    │ 0  │ 0  │
│ Orangewood School │ 10 │ 0  │
│ Kiwitown Market   │ 1  │ 10 │
│ The Millers       │ -5 │ 0  │
└───────────────────┴────┴────┘
column 1 positives
┌───────────────────┬────┐
│                   │ x  │
├───────────────────┼────┤
│ Orangewood School │ 10 │
│ Kiwitown Market   │ 1  │
│ The Lees          │ 5  │
│ The Keitas        │ 5  │
│ The Lebedevs      │ 15 │
└───────────────────┴────┘
log

Output matrix data to the console in a friendly table form that utilizes the existing row and column names. After logging, the method returns the matrix that called it so that the fluent syntax chain can continue.

Parameters:

  • element: An html element to print to. Use the same selector syntax as you would use in document.querySelector(). Be sure the element is one in which it makes sense to append a div to. If null (default), prints to the console.
  • caption: A string representing a title for the output. If null (default), does not print out a caption.
  • mapper: A function giving a final mapping of the rows before output. If null (default), x => x is ultimately passed. Alternatively, a number representing the multiple to which output should round numbers.
  • limit: An integer (default = 50). The maximum number of rows to be printed.
plotB
    .log(null, 'PlotB, unrounded, before apply')
    .apply(cell => cell + 1)
    .log(null, 'PlotB + 1', 1e-2);
PlotB, unrounded, before apply
┌─────────────────┬──────────┬─────────┐
│                 │ x        │ y       │
├─────────────────┼──────────┼─────────┤
│ possible park   │ 112.183  │ 33.5425 │
│ possible school │ 112.3693 │ 33.6295 │
└─────────────────┴──────────┴─────────┘
PlotB + 1
┌─────────────────┬────────┬───────┐
│                 │ x      │ y     │
├─────────────────┼────────┼───────┤
│ possible park   │ 113.18 │ 34.54 │
│ possible school │ 113.37 │ 34.63 │
└─────────────────┴────────┴───────┘
multiply

Multiply a matrix with a scalar value, a 'vector', or another matrix.

let vectorTens = new $$.matrix([[10],[10]]);

community
    .multiply(plotA)
    .log(null, 'multiplied by matrix');

community
    .multiply(vectorTens)
    .log(null, 'multiplied by vector');

community
    .multiply(10)
    .log(null, 'multiplied by scalar');
multiplied by matrix
┌───────────────────┬────────┬───────┐
│                   │ x      │ y     │
├───────────────────┼────────┼───────┤
│ Applewood Park    │ 0      │ 0     │
│ Orangewood School │ 6600   │ -6600 │
│ Kiwitown Market   │ -12540 │ 12540 │
│ The Millers       │ -3300  │ 3300  │
│ The Romeros       │ 6600   │ -6600 │
│ The Lees          │ -3300  │ 3300  │
│ The Keitas        │ 3300   │ -3300 │
│ The Lebedevs      │ 3300   │ -3300 │
└───────────────────┴────────┴───────┘
multiplied by vector
┌───────────────────┬─────┐
│                   │ c0  │
├───────────────────┼─────┤
│ Applewood Park    │ 0   │
│ Orangewood School │ 100 │
│ Kiwitown Market   │ 110 │
│ The Millers       │ -50 │
│ The Romeros       │ -50 │
│ The Lees          │ 100 │
│ The Keitas        │ 50  │
│ The Lebedevs      │ 200 │
└───────────────────┴─────┘
multiplied by scalar
┌───────────────────┬─────┬─────┐
│                   │ x   │ y   │
├───────────────────┼─────┼─────┤
│ Applewood Park    │ 0   │ 0   │
│ Orangewood School │ 100 │ 0   │
│ Kiwitown Market   │ 10  │ 100 │
│ The Millers       │ -50 │ 0   │
│ The Romeros       │ 0   │ -50 │
│ The Lees          │ 50  │ 50  │
│ The Keitas        │ 50  │ 0   │
│ The Lebedevs      │ 150 │ 50  │
└───────────────────┴─────┴─────┘
pseudoInverse

pseudoInverse produces a matrix such that when the new matrix is multiplied by the original matrix (or the other way around), the identity matrix is produced. This will produce an approximation, so it is advised that the end-result be rounded before reporting out.

Two optional parameters are available. The first, 'errorThreshold', determines when to stop the iterative process. The second, 'maxIterations', sets a custom cap on the number of iterations that are permitted before a failure is returned.

In the example below, pseudoInverse is called with possible values for these parameters commented out.

let inverted = plotB.pseudoInverse(/* 1e-16, 1000 */);
let multiplied = plotB.multiply(inverted);

inverted.log(null, 'inverted', 1e-8);
multiplied.log(null, 'multiplied', 1e-8);

plotB multiplied by its inverse in fact produces an identity matrix.

inverted
┌───┬───────────────┬─────────────────┐
│   │ possible park │ possible school │
├───┼───────────────┼─────────────────┤
│ x │ 9.5784528     │ -9.55367321     │
│ y │ -32.00535353  │ 31.95229102     │
└───┴───────────────┴─────────────────┘
multiplied
┌─────────────────┬───────────────┬─────────────────┐
│                 │ possible park │ possible school │
├─────────────────┼───────────────┼─────────────────┤
│ possible park   │ 1             │ 0               │
│ possible school │ 0             │ 1               │
└─────────────────┴───────────────┴─────────────────┘

Presently, there is an inverse method that produces the inverse. This uses the
solve method to find it. It is not advised to use it. PseudoInverse is typically more accurate. It may dissapear in the future.

reduce

Aggregate (aka: summarize or fold) a matrix.

The first parameter indicates the direction of the aggregation. You can choose 'row', 'col', or 'all'. Alternatively, you can plug in 1, 2, or 0 to get the respective directions. Finally, you can write out 'column' entirely.

The second parameter requires a two-parameter function used to indicate how the aggregation occurs. The first parameter of this function is the working aggregation. The second is the new incoming cell value to be worked into the first. It is similar in concept to javascript's Array.reduce().

The third parameter is optional. It allows the user to pass in a seed to get the aggregation started.

Below are simple additiona aggregations for each direction. The last one demonstrates the use of a seed of 100.

community
    .reduce('row', (a,b) => a + b)
    .log(null, 'row aggs'); 

community
    .reduce('col', (a,b) => a + b)
    .log(null, 'col aggs'); 

community
    .reduce('all', (a,b) => a + b)
    .log(null, 'full agg'); 

community
    .reduce('all', (a,b) => a + b, 100)
    .log(null, 'seeded full agg');
row aggs
┌───────────────────┬────┐
│                   │ c0 │
├───────────────────┼────┤
│ Applewood Park    │ 0  │
│ Orangewood School │ 10 │
│ Kiwitown Market   │ 11 │
│ The Millers       │ -5 │
│ The Romeros       │ -5 │
│ The Lees          │ 10 │
│ The Keitas        │ 5  │
│ The Lebedevs      │ 20 │
└───────────────────┴────┘
col aggs
┌────┬────┬────┐
│    │ x  │ y  │
├────┼────┼────┤
│ r0 │ 31 │ 15 │
└────┴────┴────┘
full agg
┌────┬────┐
│    │ c0 │
├────┼────┤
│ r0 │ 46 │
└────┴────┘
seeded full agg
┌────┬─────┐
│    │ c0  │
├────┼─────┤
│ r0 │ 146 │
└────┴─────┘
round

Round all cells in a matrix to a specified number of digits. In contrast to logging with a rounding parameter, the round method actually changes the state of the matrix.

plotB.round(1e-3).log(); 
┌─────────────────┬─────────┬────────┐
│                 │ x       │ y      │
├─────────────────┼─────────┼────────┤
│ possible park   │ 112.183 │ 33.542 │
│ possible school │ 112.369 │ 33.63  │
└─────────────────┴─────────┴────────┘
solve

Solves a system of linear equations. Assume a matrix A and a matrix or vector B. Given these, solve for x if A * x = B.

let repeated = $$.matrix.repeat(2,2);
let x = plotB.solve(repeated);
let test = plotB.multiply(x);

repeated.log(null, 'repeated', 1e-8);
x.log(null, 'x', 1e-8);
test.log(null, 'test', 1e-8); 

In the example below, we plug plotB and repeated into solve and get matrix x. And as can be seen in test below, plotB * x produces the same values as repeated.

repeated
┌────┬────┬────┐
│    │ c0 │ c1 │
├────┼────┼────┤
│ r0 │ 2  │ 2  │
│ r1 │ 2  │ 2  │
└────┴────┴────┘
x
┌─────────────────┬─────────────┬─────────────┐
│                 │ x           │ y           │
├─────────────────┼─────────────┼─────────────┤
│ possible park   │ 0.04955919  │ 0.04955919  │
│ possible school │ -0.10612502 │ -0.10612502 │
└─────────────────┴─────────────┴─────────────┘
test
┌─────────────────┬───┬───┐
│                 │ x │ y │
├─────────────────┼───┼───┤
│ possible park   │ 2 │ 2 │
│ possible school │ 2 │ 2 │
└─────────────────┴───┴───┘
subtract

Subtract two matrices from each other, cell by cell.

plotA.subtract(plotB).log();
┌─────────────────┬────────────┬───────────┐
│                 │ x          │ y         │
├─────────────────┼────────────┼───────────┤
│ possible park   │ 547.817    │ -693.5425 │
│ possible school │ -1432.3693 │ 1286.3705 │
└─────────────────┴────────────┴───────────┘
transform

Applies a transformation to a matrix.

It's usually more convenient than multiplication to achieve the same task because it takes care of any transpositions for you, by default it easily works with a matrix where points are represented as rows as opposed to columns (more common in business concerns), it takes care of adding and later removing the extra dimension from the target matrix for affine transforms, and it preserves the row/column names after the transformation.

The following parameters are available:

  • transformer: Required. The matrix defining the transformation. Must be a square matrix. For 'standard' transformations with pointsAreRows = true, it must have the same number of columns as the calling matrix (if pointsAreRows = false then transformation column length must equal calling matrix row length). For 'affine' transforations, add an extra dimension to the transformation matrix on both the rows and columns. Do not add a dimension to the calling matrix. That will be done for you.
  • pointsAreRows: Optional. If true, then each row of the calling matrix is taken to be a point (a pair of 2D points is [{x0,y0}{x1,y1}]). If false, then each column of the calling matrix is taken to be a point (a pair of 2D points is [{x0,x1},{y0,y1}]). The default is true.

The 'transformer' below represents a shear transform that, if applied to the 'community', would precicely move Kiwitown market just enough to the right in order to make an equilateral triangle between the park, the school, and the market.

let transformer = new $$.matrix([
    [1, 0.4],
    [0, Math.pow(3,0.5) / 2 ]
]);
community.transform(transformer).log();

Below, (0,0), (10,0), and (5,8.660254) forms an equilateral triangle.

┌───────────────────┬────┬────────────────────┐
│                   │ x  │ y                  │
├───────────────────┼────┼────────────────────┤
│ Applewood Park    │ 0  │ 0                  │
│ Orangewood School │ 10 │ 0                  │
│ Kiwitown Market   │ 5  │ 8.660254037844386  │
│ The Millers       │ -5 │ 0                  │
│ The Romeros       │ -2 │ -4.330127018922193 │
│ The Lees          │ 7  │ 4.330127018922193  │
│ The Keitas        │ 5  │ 0                  │
│ The Lebedevs      │ 17 │ 4.330127018922193  │
└───────────────────┴────┴────────────────────┘

Or imagine that the community was structured with x/y points represented as one point per column. Then just set pointsAreRows to false.

let tCommunity = community.filter([
    'Applewood Park',
    'Orangewood School',
    'Kiwitown Market'
]).transpose();

let transformed = tCommunity.transform(transformer, false);

tCommunity.log(null, 'tCommunity');
transformed.log(null, 'transformed');
tCommunity
┌───┬────────────────┬───────────────────┬─────────────────┐
│   │ Applewood Park │ Orangewood School │ Kiwitown Market │
├───┼────────────────┼───────────────────┼─────────────────┤
│ x │ 0              │ 10                │ 1               │
│ y │ 0              │ 0                 │ 10              │
└───┴────────────────┴───────────────────┴─────────────────┘
transformed
┌───┬────────────────┬───────────────────┬───────────────────┐
│   │ Applewood Park │ Orangewood School │ Kiwitown Market   │
├───┼────────────────┼───────────────────┼───────────────────┤
│ x │ 0              │ 10                │ 5                 │
│ y │ 0              │ 0                 │ 8.660254037844386 │
└───┴────────────────┴───────────────────┴───────────────────┘

As for affine transformations, here's an example that applies a translation such that Kiwitown market becomes the origin. Notice that an extra dimension exists for the row and column of the transforming matrix. No manipulations are needed for the community matrix to make this work:

let affineTranslator = new $$.matrix([
    [ 1, 0, -1  ],
    [ 0, 1, -10 ],
    [ 0, 1,  1  ]
])

community.transform(affineTranslator).log();
┌───────────────────┬────┬─────┐
│                   │ x  │ y   │
├───────────────────┼────┼─────┤
│ Applewood Park    │ -1 │ -10 │
│ Orangewood School │ 9  │ -10 │
│ Kiwitown Market   │ 0  │ 0   │
│ The Millers       │ -6 │ -10 │
│ The Romeros       │ -1 │ -15 │
│ The Lees          │ 4  │ -5  │
│ The Keitas        │ 4  │ -10 │
│ The Lebedevs      │ 14 │ -5  │
└───────────────────┴────┴─────┘
transpose

Swaps out rows for columns.

plotA.transpose().log(); 
┌───┬───────────────┬─────────────────┐
│   │ possible park │ possible school │
├───┼───────────────┼─────────────────┤
│ x │ 660           │ -1320           │
│ y │ -660          │ 1320            │
└───┴───────────────┴─────────────────┘
validate

Throws an error if the matrix is not in valid form. Otherwise returns the matrix calling it.

let applyAndLog = (func) => {
    try {
        plotA.apply(func).validate().log();
    }
    catch (err) {
        console.log(
            `\nInput function produced a bad matrix: \n\t${err}`
        );
    } 
}

applyAndLog(cell => cell / 10);
applyAndLog(cell => 'a' + cell);
┌─────────────────┬──────┬─────┐
│                 │ x    │ y   │
├─────────────────┼──────┼─────┤
│ possible park   │ 66   │ -66 │
│ possible school │ -132 │ 132 │
└─────────────────┴──────┴─────┘

Input function produced a bad matrix: 
	'x' in row 0 is not a finite number

Methods that return multiple matricies

decomposeQR

Returns an object with the following properties:

  • A: The original matrix before decomposition
  • Q: An orthogonal matrix (e.g. vectors are mutually perpendicular)
  • R: An upper triangular matrix.

A is 'decomposed' into L and U. So multiplying L * U will produce A.

This can be seen by the decomposition of 'plotB' below:

let qr = plotB.decomposeQR();

qr.A.log(null, 'A', 1e-8);
qr.R.log(null, 'R', 1e-8);
qr.Q.log(null, 'Q', 1e-8);

let testMult = qr.Q.multiply(qr.R).equals(plotB, 1e-8);
let testOrth = qr.Q.isOrthonormal();
console.log(`\nIt is '${testMult}' that qr.Q * qr.R = plotB`);
console.log(`It is '${testOrth}' that qr.Q is orthonormal`);
A
┌─────────────────┬──────────┬─────────┐
│                 │ x        │ y       │
├─────────────────┼──────────┼─────────┤
│ possible park   │ 112.183  │ 33.5425 │
│ possible school │ 112.3693 │ 33.6295 │
└─────────────────┴──────────┴─────────┘
R
┌────┬──────────────┬─────────────┐
│    │ x            │ y           │
├────┼──────────────┼─────────────┤
│ r0 │ 158.78250871 │ 47.4978114  │
│ r1 │ 0            │ -0.02211171 │
└────┴──────────────┴─────────────┘
Q
┌────┬────────────┬─────────────┐
│    │ c0         │ c1          │
├────┼────────────┼─────────────┤
│ r0 │ 0.70651989 │ 0.70769319  │
│ r1 │ 0.70769319 │ -0.70651989 │
└────┴────────────┴─────────────┘

It is 'true' that qr.Q * qr.R = plotB
It is 'true' that qr.Q is orthonormal
decomposeSVDcomp

Computes the compact Singular Value Decomposition of a matrix. It is 'compact' because it only calculates the values and vectors for non-zero singular values.

The following parameters are available:

  • errorThreshold: Determines when the converging iterations to calculate the values stops (default is 1e-8).
  • maxIterations: Determines the maximum number of iterations to allow before it is determined that a value will not converge (default is 1000).

Returns an object with the following properties:

  • A: The original matrix before decomposition
  • L: A matrix with the 'left' singular vectors of A.
  • D: A diagonal matrix with the 'singular values' of A on the diagonal.
  • R: A matrix with the 'right' singular values of A.
  • iterations: The number of iterations required to produce the calculation.

This can be seen by the decomposition of 'community' below:

let svd = community.decomposeSVDcomp();

// for better display
let iterations = svd.iterations;
svd.iterations = undefined;

svd.A.log(null, 'A', 1e-8);
svd.L.log(null, 'L', 1e-8);
svd.D.log(null, 'D', 1e-8);
svd.R.log(null, 'R', 1e-8);
console.log(`\niterations: ${iterations}`);
A
┌───────────────────┬────┬────┐
│                   │ x  │ y  │
├───────────────────┼────┼────┤
│ Applewood Park    │ 0  │ 0  │
│ Orangewood School │ 10 │ 0  │
│ Kiwitown Market   │ 1  │ 10 │
│ The Millers       │ -5 │ 0  │
│ The Romeros       │ 0  │ -5 │
│ The Lees          │ 5  │ 5  │
│ The Keitas        │ 5  │ 0  │
│ The Lebedevs      │ 15 │ 5  │
└───────────────────┴────┴────┘
L
┌────┬─────────────┬─────────────┐
│    │ c0          │ c1          │
├────┼─────────────┼─────────────┤
│ r0 │ 0           │ 0           │
│ r1 │ 0.4388266   │ 0.32979645  │
│ r2 │ 0.22220216  │ -0.77861668 │
│ r3 │ -0.2194133  │ -0.16489822 │
│ r4 │ -0.08915975 │ 0.40579816  │
│ r5 │ 0.30857305  │ -0.24089994 │
│ r6 │ 0.2194133   │ 0.16489822  │
│ r7 │ 0.74739965  │ 0.08889651  │
└────┴─────────────┴─────────────┘
D
┌────┬────────────┬─────────────┐
│    │ c0         │ c1          │
├────┼────────────┼─────────────┤
│ c0 │ 21.1115864 │ 0           │
│ c1 │ 0          │ 11.41494282 │
└────┴────────────┴─────────────┘
R
┌────┬────────────┬─────────────┐
│    │ c0         │ c1          │
├────┼────────────┼─────────────┤
│ r0 │ 0.92643256 │ 0.37646076  │
│ r1 │ 0.37646076 │ -0.92643256 │
└────┴────────────┴─────────────┘

iterations: 20
eigen

Calculates eigenvectors and eigenvalues for a matrix.

The method takes one parameter, but in reality it acts as multiple named parameters:

Pass an object with one or more of the following properties:

  • valueThreshold: Determines when the converging iterations to calculate the eigenvalues stops (default is 1e-8).
  • valueLoopMax: Determines the maximum permitted iterations for the calculation of eigenvalues (default is 1000),
  • valueMerge: Determines the proximity of two eigenvalues required to interperet them as the same. If they are deemed the same, they are averaged out into the same value. The calcuation of eigenvectors then happens against this merged value (default is 1e-3).
  • vectorThreshold: Determines when the convertiong iterations to calculate each eigenvector stops (default is 1e-8).
  • vectorLoopMax: Determines the maximum permitted iterations for the calculation of each eigenvector (default is 1000).
  • testThreshold: A test is taken to ensure that the matrix multiplied by each eigenvector equals the eigenvector multiplied by its eigenvalue. This threshold determines how close the equality must be in order to succeed (default is 1e-6).

Alternative parameter structures include:

  • Pass threshold as a property in the object parameter to set value and vector thresholds simultaneously.
  • Pass loopMax as a property in the object parameter to set value and vector loop max settings simultaneously.
  • Pass an integer to have the same effect as passing an object with only the 'threshold' parameter set.

Returns an object with the following properties:

  • A: The matrix calling the method.
  • values: An array of the eigenvalues.
  • vectors: A square matrix holding the eigenvectors.
  • rawValues: An array of the eigenvectors before special modifications were
    applied to it.
  • iterations: An object showing the history of the iterations for different calculations. Properties are 'values', 'vector 1', 'vector 2', ..., 'vector n', with integers indicating numbers of itereations for each.

Example:

The 'transformer' below represents a shear transform that, if applied to the 'community', would precicely move Kiwitown market just enough to the right in order to make an equilateral triangle between the park, the school, and the market.

let transformer = new $$.matrix([
    [1, 0.4],
    [0, Math.pow(3,0.5) / 2 ]
]);

Using the eigen method, we can observe the eigenvectors and the eigenvalues.

let eigen = transformer.eigen({loopMax: 10000});
console.log('eigenvalues:', eigen.values, '\n');
console.log('eigenvectors: ')
eigen.vectors.log(null, null, 1e-8);
eigenvalues: [ 1, 0.8660254 ] 

eigenvectors: 
┌────┬────┬─────────────┐
│    │ c0 │ c1          │
├────┼────┼─────────────┤
│ r0 │ -1 │ -0.94822626 │
│ r1 │ 0  │ 0.31759558  │
└────┴────┴─────────────┘

Note: Stop here if you just needed to see the output for eigen. But if you need a little more help on what the eigenvectors and values actually do, read on for an applied example.

Application:

The more interesting eigen pair is vector (-0.94822626, 0.31759558) of length 1 (it will always be 1) corresponding to value 0.8660254. This means that any vector on the same line as this eigenvector will remain on the same line after transformation, but will scale by the eigenvalue.

Imagine someone desired to build a theatre. It would start out as a temporary drive-in. But the community will be moving and will rebuild on a new plot of land with the coordinates produced by the transformation. In the new community, the thature will be brick-and-mortar. But the owner wants to build a signpost at the park with arrows pointing to the school and to the theatre.
If it is to remain accurate after the transformation, he'd better put his theatre along the one of the eigenvectors. The owner is not so worried about the distance of the temporary drive-in. But after transformation it should be 5 units away from the park.

Since the eigenvector of interest is unit length 1, but will scale to become 0.8660254 units, the vector (5 / 0.8660254) * (-0.94822626, 0.31759558) should remain on the same line that is mapped out by the eigenvector and should scale out to become 5 units long.

let scaleFactor = 5 / eigen.values[1];

community_array.push({ 
    marker: 'theatre', 
    x: eigen.vectors.getCell(0,1) * scaleFactor, 
    y: eigen.vectors.getCell(1,1) * scaleFactor
});

community = $$(community_array).matrix('x, y', 'marker');

Now let's build the community, transform it, and observe the new position of the theatre.

let transformed = community.transform(transformer);
community.filter('theatre').log(null, 'theatre before transform', 1e-8);
transformed.log(null, 'transformed', 1e-8);
theatre before transform
┌─────────┬─────────────┬────────────┐
│         │ x           │ y          │
├─────────┼─────────────┼────────────┤
│ theatre │ -5.47458692 │ 1.83363893 │
└─────────┴─────────────┴────────────┘
transformed
┌───────────────────┬─────────────┬─────────────┐
│                   │ x           │ y           │
├───────────────────┼─────────────┼─────────────┤
│ Applewood Park    │ 0           │ 0           │
│ Orangewood School │ 10          │ 0           │
│ Kiwitown Market   │ 5           │ 8.66025404  │
│ The Millers       │ -5          │ 0           │
│ The Romeros       │ -2          │ -4.33012702 │
│ The Lees          │ 7           │ 4.33012702  │
│ The Keitas        │ 5           │ 0           │
│ The Lebedevs      │ 17          │ 4.33012702  │
│ theatre           │ -4.74113134 │ 1.58797789  │
└───────────────────┴─────────────┴─────────────┘

Eyeballing the theatre distance seems to show that it is close to the expected 5 units. And a quick check in code confirms that:

let theatreDist = transformed.filter('theatre', null).norm('euclidian');
console.log(`theatre distance from park: ${theatreDist}`);
theatre distance from park: 5.000000021849468

Methods that describe a matrix

determinant

Returns the determinant of a matrix.

let d = plotB.determinant();
console.log(d);
3.5109532500009664

Note that the procedure technically has a parameter, 'data'. But this is used in a recursive calculation. It is not advised that it be used under circumstances outside it's internal usage.

equals

Determines whether the given matrix is equal to another matrix: cell-by-cell.

The following parameters are available:

  • other: Another matrix to compare to.
  • errorThreshold: How close in value should each cell be to determine equality (default is 0).
  • dataOnly: If false, row and column names, and potentially other properties will be included
    in the comparison. If true, only the data is compared (default is true).

Returns true or false.

let shifted = plotA.apply(cell => cell + 0.01).setColNames(['run','amok']);
console.log({
    strictEquality: plotA.equals(shifted),
    looseEquality: plotA.equals(shifted, 1e-2),
    nameEquality: plotA.equals(shifted, 1e-2, false)
});
{ strictEquality: false, looseEquality: true, nameEquality: false }
isDiagonal

Indicates whether the matrix is a diagonal or not.

The optional zeroThreshold parameter is available to allow tiny values off of the diagonal to be treated as zero in determining the result.

Returns true or false.

let d = new $$.matrix([
    [ 1, 0, 0.01  ],
    [ 0, 2, 0.001 ],
    [ 0, 0, 3     ]
])
console.log({
    dIsExactlyDiag: d.isDiagonal(),  
    dIsCloseToDiag: d.isDiagonal(1e-2)
})
{ dIsExactlyDiag: false, dIsCloseToDiag: true }
isLowerTriangular

Indicates whether the matrix is lower triangular or not. If so, it means that non-zero values are only found on the diagonal line or below it.

The optional zeroThreshold parameter is available to allow tiny values above the diagonal to be treated as zero in determining the result.

Returns true or false.

let mx = new $$.matrix([
    [ 1, 0, 0.01 ],
    [ 2, 3, 0    ],
    [ 3, 4, 5    ]
]);

console.log({
    mxIsLowerTriExactly: mx.isLowerTriangular(),   
    mxIsLowerTriApprox: mx.isLowerTriangular(1e-2)
})
{ mxIsLowerTriExactly: false, mxIsLowerTriApprox: true }
isOrthonormal

Indicates whether the matrix is orthonormal or not. If so, it means that the row and column vectors are all perpendicular to each other (ortho) and are of length one (normal).

The optional errorThreshold parameter allows some give in the calculations to test whether the matrix is orthonormal or not. The default is 1e-8.

Returns true or false.

let mx = new $$.matrix([
    [ 0.70651989,  0.70769319 + 0.01 ],
    [ 0.70769319, -0.70651989          ]
]);

console.log({
    mxIsVeryCloseToOrthonormal: mx.isOrthonormal(),
    mxIsSomewhatCloseToOrthonormal: mx.isOrthonormal(0.1)
});
{
  mxIsVeryCloseToOrthonormal: false,
  mxIsSomewhatCloseToOrthonormal: true
}
isSquare

Indicates whether the matrix is square or not.

Returns true or false.

console.log({
    communityIsSquare: community.isSquare(),
    plotAisSquare: plotA.isSquare() 
})
{ communityIsSquare: false, plotAisSquare: true }
isUpperTriangular

Indicates whether the matrix is upper triangular or not. If so, it means that non-zero values are only found on the diagonal line or above it.

The optional zeroThreshold parameter is available to allow tiny values below the diagonal to be treated as zero in determining the result.

Returns true or false.

let mx = new $$.matrix([
    [     1, 2, 3 ],
    [     0, 4, 5 ],
    [ 0.001, 0, 6 ]
]);

console.log({
    mxIsUpperTriExactly: mx.isUpperTriangular(),   
    mxIsUpperTriApprox: mx.isUpperTriangular(1e-2)
})
{ mxIsUpperTriExactly: false, mxIsUpperTriApprox: true }
norm

Produces the norm of a matrix.

The type parameter determines what kind of norm is produced. The default is 'frobenious', which can be aliased using 'eulcidian', 'f', or 'e'. Also available is '1' for a 1-norm, and 'infinity' or 'i' for an infinity-norm.

console.log({
    euclidianNorm: community.norm(),
    oneNorm: community.norm(1),
    infinityNorm: community.norm('i')
})
{ euclidianNorm: 24, oneNorm: 41, infinityNorm: 20 }

Static methods on the matrix class

identity

Creates an identity matrix.

The following parameters are available:

  • numRows: The number of rows to put in the matrix.
  • numCols: Optional. The number of columns to put in the matrix. If omitted, then numCols = numRows.
$$.matrix.identity(3).log(null, 'square identity');
$$.matrix.identity(3,4).log(null, 'rectangle identity'); 
square identity
┌────┬────┬────┬────┐
│    │ c0 │ c1 │ c2 │
├────┼────┼────┼────┤
│ r0 │ 1  │ 0  │ 0  │
│ r1 │ 0  │ 1  │ 0  │
│ r2 │ 0  │ 0  │ 1  │
└────┴────┴────┴────┘
rectangle identity
┌────┬────┬────┬────┬────┐
│    │ c0 │ c1 │ c2 │ c3 │
├────┼────┼────┼────┼────┤
│ r0 │ 1  │ 0  │ 0  │ 0  │
│ r1 │ 0  │ 1  │ 0  │ 0  │
│ r2 │ 0  │ 0  │ 1  │ 0  │
└────┴────┴────┴────┴────┘
ones

Creates a matrix with all ones as cell values.

The following parameters are available:

  • numRows: The number of rows to put in the matrix.
  • numCols: Optional. The number of columns to put in the matrix. If omitted, then numCols = numRows.
$$.matrix.ones(3,4).log(); 
┌────┬────┬────┬────┬────┐
│    │ c0 │ c1 │ c2 │ c3 │
├────┼────┼────┼────┼────┤
│ r0 │ 1  │ 1  │ 1  │ 1  │
│ r1 │ 1  │ 1  │ 1  │ 1  │
│ r2 │ 1  │ 1  │ 1  │ 1  │
└────┴────┴────┴────┴────┘
randomizer

A class that can craete a matrix with random values in it's entries.

Call the method setSize() and set the parameters numRows and numCols in order to set up the matrix to be a certain size.

Call the method setValues() and set the parameters lowVal and highVal to set bounds on the range randomized values can take when creating the matrix. Optionally set integers to ensure those values are only integers.

Call the method get() to create the matrix based on previous settings.

new $$.matrix.randomizer().setSize(2,3).setValues(-5,5).get().log();
new $$.matrix.randomizer().setSize(2,3).setValues(-5,5).get().log();

As can be seen below, the two identical lines above produce different matricies of random values.

┌────┬─────────────────────┬────────────────────┬──────────────────────┐
│    │ c0                  │ c1                 │ c2                   │
├────┼─────────────────────┼────────────────────┼──────────────────────┤
│ r0 │ 3.5386930574153137  │ -3.367077144616626 │ -4.323092838476703   │
│ r1 │ -0.3755470412689821 │ 2.239453636042681  │ -0.05016840292257019 │
└────┴─────────────────────┴────────────────────┴──────────────────────┘
┌────┬─────────────────────┬───────────────────┬─────────────────────┐
│    │ c0                  │ c1                │ c2                  │
├────┼─────────────────────┼───────────────────┼─────────────────────┤
│ r0 │ -1.3725213928020707 │ -2.76675330820785 │ -0.9948801834067966 │
│ r1 │ -2.4988244489280653 │ 4.834078388536142 │ 3.378161702938378   │
└────┴─────────────────────┴───────────────────┴─────────────────────┘

Below produces one with similar bounds, but will only output integers:

new $$.matrix.randomizer().setSize(2,3).setValues(-5,5,true).get().log();
┌────┬────┬────┬────┐
│    │ c0 │ c1 │ c2 │
├────┼────┼────┼────┤
│ r0 │ -4 │ 2  │ -5 │
│ r1 │ -5 │ 5  │ 3  │
└────┴────┴────┴────┘
repeat

Creates a matrix with all cell having the same value.

The following parameters are available:

  • repeater: The value to be repeated accross all cells.
  • numRows: The number of rows to put in the matrix.
  • numCols: Optional. The number of columns to put in the matrix. If omitted, then numCols = numRows.
  • diagOnly: Optional, boolean. If true, creates a diagonal matrix with the entries on the diagonal repeated.
$$.matrix.repeat(9,2,4).log(); 
┌────┬────┬────┬────┬────┐
│    │ c0 │ c1 │ c2 │ c3 │
├────┼────┼────┼────┼────┤
│ r0 │ 9  │ 9  │ 9  │ 9  │
│ r1 │ 9  │ 9  │ 9  │ 9  │
└────┴────┴────┴────┴────┘
zeroes

Creates a matrix with all zeroes as cell values.

The following parameters are available:

  • numRows: The number of rows to put in the matrix.
  • numCols: Optional. The number of columns to put in the matrix. If omitted, then numCols = numRows.
$$.matrix.zeroes(3,4).log(); 
┌────┬────┬────┬────┬────┐
│    │ c0 │ c1 │ c2 │ c3 │
├────┼────┼────┼────┼────┤
│ r0 │ 0  │ 0  │ 0  │ 0  │
│ r1 │ 0  │ 0  │ 0  │ 0  │
│ r2 │ 0  │ 0  │ 0  │ 0  │
└────┴────┴────┴────┴────┘

Clone this wiki locally