Skip to content

Commit

Permalink
Matrix creation and conversion methods (#2155)
Browse files Browse the repository at this point in the history
* made dense and sparse matrices iterable, fixed #1184

* added matrixFromFunction, fixes #2153

* added tests for matrixFromFunction

* added matrixFromRows

* added matrixFromColumns

* added rows() and columns() for dense matrix

* improved sparse documentation a tiny bit

* fix linting issues

* added matrixFromRow/Column to seealso of row and column

* removed unnecessary duplication from matrixFromRows/Columns

* added babel runtime

Co-authored-by: Jos de Jong <wjosdejong@gmail.com>
  • Loading branch information
cshaa and josdejong authored May 9, 2021
1 parent e163032 commit d7a5693
Show file tree
Hide file tree
Showing 21 changed files with 14,802 additions and 578 deletions.
3 changes: 2 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
["@babel/preset-env"]
],
"plugins": [
"@babel/plugin-transform-object-assign"
"@babel/plugin-transform-object-assign",
"@babel/transform-runtime"
],
"ignore": [
"lib/**/*.js"
Expand Down
14,819 changes: 14,247 additions & 572 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"unit"
],
"dependencies": {
"@babel/runtime": "^7.13.17",
"complex.js": "^2.0.11",
"decimal.js": "^10.2.1",
"escape-latex": "^1.2.0",
Expand All @@ -37,6 +38,7 @@
"devDependencies": {
"@babel/core": "7.13.14",
"@babel/plugin-transform-object-assign": "7.12.13",
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/preset-env": "7.13.12",
"@babel/register": "7.13.14",
"babel-loader": "8.2.2",
Expand Down
6 changes: 6 additions & 0 deletions src/expression/embeddedDocs/embeddedDocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ import { splitUnitDocs } from './construction/splitUnit.js'
import { sparseDocs } from './construction/sparse.js'
import { numberDocs } from './construction/number.js'
import { matrixDocs } from './construction/matrix.js'
import { matrixFromFunctionDocs } from './function/matrix/matrixFromFunction.js'
import { matrixFromRowsDocs } from './function/matrix/matrixFromRows.js'
import { matrixFromColumnsDocs } from './function/matrix/matrixFromColumns.js'
import { indexDocs } from './construction/index.js'
import { fractionDocs } from './construction/fraction.js'
import { createUnitDocs } from './construction/createUnit.js'
Expand Down Expand Up @@ -424,6 +427,9 @@ export const embeddedDocs = {
inv: invDocs,
eigs: eigsDocs,
kron: kronDocs,
matrixFromFunction: matrixFromFunctionDocs,
matrixFromRows: matrixFromRowsDocs,
matrixFromColumns: matrixFromColumnsDocs,
map: mapDocs,
ones: onesDocs,
partitionSelect: partitionSelectDocs,
Expand Down
2 changes: 1 addition & 1 deletion src/expression/embeddedDocs/function/matrix/column.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export const columnDocs = {
'column(A, 1)',
'column(A, 2)'
],
seealso: ['row']
seealso: ['row', 'matrixFromColumns']
}
16 changes: 16 additions & 0 deletions src/expression/embeddedDocs/function/matrix/matrixFromColumns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const matrixFromColumnsDocs = {
name: 'matrixFromColumns',
category: 'Matrix',
syntax: [
'math.matrixFromColumns(...arr)',
'math.matrixFromColumns(row1, row2)',
'math.matrixFromColumns(row1, row2, row3)'
],
description: 'Create a dense matrix from vectors as individual columns.',
examples: [
'matrixFromColumns([1, 2, 3], [[4],[5],[6]])'
],
seealso: [
'matrix', 'matrixFromRows', 'matrixFromFunction', 'zeros'
]
}
22 changes: 22 additions & 0 deletions src/expression/embeddedDocs/function/matrix/matrixFromFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const matrixFromFunctionDocs = {
name: 'matrixFromFunction',
category: 'Matrix',
syntax: [
'math.matrixFromFunction(size, fn)',
'math.matrixFromFunction(size, fn, format)',
'math.matrixFromFunction(size, fn, format, datatype)',
'math.matrixFromFunction(size, format, fn)',
'math.matrixFromFunction(size, format, datatype, fn)'
],
description: 'Create a matrix by evaluating a generating function at each index.',
examples: [
'f(I) = I[1] - I[2]',
'matrixFromFunction([3,3], f)',
'g(I) = I[1] - I[2] == 1 ? 4 : 0',
'matrixFromFunction([100, 100], "sparse", g)',
'matrixFromFunction([5], random)'
],
seealso: [
'matrix', 'matrixFromRows', 'matrixFromColumns', 'zeros'
]
}
16 changes: 16 additions & 0 deletions src/expression/embeddedDocs/function/matrix/matrixFromRows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const matrixFromRowsDocs = {
name: 'matrixFromRows',
category: 'Matrix',
syntax: [
'math.matrixFromRows(...arr)',
'math.matrixFromRows(row1, row2)',
'math.matrixFromRows(row1, row2, row3)'
],
description: 'Create a dense matrix from vectors as individual rows.',
examples: [
'matrixFromRows([1, 2, 3], [[4],[5],[6]])'
],
seealso: [
'matrix', 'matrixFromColumns', 'matrixFromFunction', 'zeros'
]
}
2 changes: 1 addition & 1 deletion src/expression/embeddedDocs/function/matrix/row.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export const rowDocs = {
'row(A, 1)',
'row(A, 2)'
],
seealso: ['column']
seealso: ['column', 'matrixFromRows']
}
3 changes: 3 additions & 0 deletions src/factoriesAny.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export { createBignumber } from './type/bignumber/function/bignumber.js'
export { createComplex } from './type/complex/function/complex.js'
export { createFraction } from './type/fraction/function/fraction.js'
export { createMatrix } from './type/matrix/function/matrix.js'
export { createMatrixFromFunction } from './function/matrix/matrixFromFunction.js'
export { createMatrixFromRows } from './function/matrix/matrixFromRows.js'
export { createMatrixFromColumns } from './function/matrix/matrixFromColumns.js'
export { createSplitUnit } from './type/unit/function/splitUnit.js'
export { createUnaryMinus } from './function/arithmetic/unaryMinus.js'
export { createUnaryPlus } from './function/arithmetic/unaryPlus.js'
Expand Down
1 change: 1 addition & 0 deletions src/function/matrix/flatten.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const dependencies = ['typed', 'matrix']
export const createFlatten = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix }) => {
/**
* Flatten a multi dimensional matrix into a single dimensional matrix.
* It is guaranteed to always return a clone of the argument.
*
* Syntax:
*
Expand Down
86 changes: 86 additions & 0 deletions src/function/matrix/matrixFromColumns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { factory } from '../../utils/factory.js'

const name = 'matrixFromColumns'
const dependencies = ['typed', 'matrix', 'flatten', 'size']

export const createMatrixFromColumns = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, flatten, size }) => {
/**
* Create a dense matrix from vectors as individual columns.
* If you pass row vectors, they will be transposed (but not conjugated!)
*
* Syntax:
*
* math.matrixFromColumns(...arr)
* math.matrixFromColumns(col1, col2)
* math.matrixFromColumns(col1, col2, col3)
*
* Examples:
*
* math.matrixFromColumns([1, 2, 3], [[4],[5],[6]])
* math.matrixFromColumns(...vectors)
*
* See also:
*
* matrix, matrixFromRows, matrixFromFunction, zeros
*
* @param {...Array | ...Matrix} cols
* @return { number[][] | Matrix } if at least one of the arguments is an array, an array will be returned
*/
return typed(name, {
'...Array': function (arr) {
return _createArray(arr)
},
'...Matrix': function (arr) {
return matrix(_createArray(arr.map(m => m.toArray())))
}

// TODO implement this properly for SparseMatrix
})

function _createArray (arr) {
if (arr.length === 0) throw new TypeError('At least one column is needed to construct a matrix.')
const N = checkVectorTypeAndReturnLength(arr[0])

// create an array with empty rows
const result = []
for (let i = 0; i < N; i++) {
result[i] = []
}

// loop columns
for (const col of arr) {
const colLength = checkVectorTypeAndReturnLength(col)

if (colLength !== N) {
throw new TypeError('The vectors had different length: ' + (N | 0) + ' ≠ ' + (colLength | 0))
}

const f = flatten(col)

// push a value to each row
for (let i = 0; i < N; i++) {
result[i].push(f[i])
}
}

return result
}

function checkVectorTypeAndReturnLength (vec) {
const s = size(vec)

if (s.length === 1) { // 1D vector
return s[0]
} else if (s.length === 2) { // 2D vector
if (s[0] === 1) { // row vector
return s[1]
} else if (s[1] === 1) { // col vector
return s[0]
} else {
throw new TypeError('At least one of the arguments is not a vector.')
}
} else {
throw new TypeError('Only one- or two-dimensional vectors are supported.')
}
}
})
63 changes: 63 additions & 0 deletions src/function/matrix/matrixFromFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { factory } from '../../utils/factory.js'

const name = 'matrixFromFunction'
const dependencies = ['typed', 'matrix', 'isZero']

export const createMatrixFromFunction = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, isZero }) => {
/**
* Create a matrix by evaluating a generating function at each index.
*
* Syntax:
*
* math.matrixFromFunction(size, fn)
* math.matrixFromFunction(size, fn, format)
* math.matrixFromFunction(size, fn, format, datatype)
* math.matrixFromFunction(size, format, fn)
* math.matrixFromFunction(size, format, datatype, fn)
*
* Examples:
*
* math.matrixFromFunction([3,3], i => i[0] - i[1]) // an antisymmetric matrix
* math.matrixFromFunction([100, 100], 'sparse', i => i[0] - i[1] === 1 ? 4 : 0) // a sparse subdiagonal matrix
* math.matrixFromFunction([5], i => math.random()) // a random vector
*
* See also:
*
* matrix, zeros
*/
return typed(name, {
'Array | Matrix, function, string, string': function (size, fn, format, datatype) {
return _create(size, fn, format, datatype)
},
'Array | Matrix, function, string': function (size, fn, format) {
return _create(size, fn, format)
},
'Array | Matrix, function': function (size, fn) {
return _create(size, fn, 'dense')
},
'Array | Matrix, string, function': function (size, format, fn) {
return _create(size, fn, format)
},
'Array | Matrix, string, string, function': function (size, format, datatype, fn) {
return _create(size, fn, format, datatype)
}
})

function _create (size, fn, format, datatype) {
let m
if (datatype !== undefined) {
m = matrix(format, datatype)
} else {
m = matrix(format)
}

m.resize(size)
m.forEach(function (_, index) {
const val = fn(index)
if (isZero(val)) return
m.set(index, val)
})

return m
}
})
75 changes: 75 additions & 0 deletions src/function/matrix/matrixFromRows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { factory } from '../../utils/factory.js'

const name = 'matrixFromRows'
const dependencies = ['typed', 'matrix', 'flatten', 'size']

export const createMatrixFromRows = /* #__PURE__ */ factory(name, dependencies, ({ typed, matrix, flatten, size }) => {
/**
* Create a dense matrix from vectors as individual rows.
* If you pass column vectors, they will be transposed (but not conjugated!)
*
* Syntax:
*
* math.matrixFromRows(...arr)
* math.matrixFromRows(row1, row2)
* math.matrixFromRows(row1, row2, row3)
*
* Examples:
*
* math.matrixFromRows([1, 2, 3], [[4],[5],[6]])
* math.matrixFromRows(...vectors)
*
* See also:
*
* matrix, matrixFromColumns, matrixFromFunction, zeros
*
* @param {...Array | ...Matrix} rows
* @return { number[][] | Matrix } if at least one of the arguments is an array, an array will be returned
*/
return typed(name, {
'...Array': function (arr) {
return _createArray(arr)
},
'...Matrix': function (arr) {
return matrix(_createArray(arr.map(m => m.toArray())))
}

// TODO implement this properly for SparseMatrix
})

function _createArray (arr) {
if (arr.length === 0) throw new TypeError('At least one row is needed to construct a matrix.')
const N = checkVectorTypeAndReturnLength(arr[0])

const result = []
for (const row of arr) {
const rowLength = checkVectorTypeAndReturnLength(row)

if (rowLength !== N) {
throw new TypeError('The vectors had different length: ' + (N | 0) + ' ≠ ' + (rowLength | 0))
}

result.push(flatten(row))
}

return result
}

function checkVectorTypeAndReturnLength (vec) {
const s = size(vec)

if (s.length === 1) { // 1D vector
return s[0]
} else if (s.length === 2) { // 2D vector
if (s[0] === 1) { // row vector
return s[1]
} else if (s[1] === 1) { // col vector
return s[0]
} else {
throw new TypeError('At least one of the arguments is not a vector.')
}
} else {
throw new TypeError('Only one- or two-dimensional vectors are supported.')
}
}
})
Loading

0 comments on commit d7a5693

Please sign in to comment.