/** * A constrained matrix type. Allows for matrix/vector operations that won't fail due to * incompatible shapes * * @since 1.0.0 */ import * as Apl from 'fp-ts/Applicative' import * as Fun from 'fp-ts/Functor' import * as FunI from 'fp-ts/FunctorWithIndex' import * as Fl from 'fp-ts/Foldable' import * as FlI from 'fp-ts/FoldableWithIndex' import * as IO from 'fp-ts/IO' import { HKT, Kind, Kind2, Kind3, Kind4, URIS, URIS2, URIS3, URIS4 } from 'fp-ts/HKT' import * as Mn from 'fp-ts/Monoid' import * as O from 'fp-ts/Option' import * as RA from 'fp-ts/ReadonlyArray' import * as Rng from 'fp-ts/Ring' import { flow, identity as id, pipe, tuple, unsafeCoerce } from 'fp-ts/function' import * as TC from './typeclasses' import * as V from './Vector' // ############# // ### Model ### // ############# /** * @since 1.0.0 * @category Model */ export interface Mat extends V.Vec> { _rows: M _cols: N } // #################### // ### Constructors ### // #################### /** * @since 1.0.0 * @category Internal */ const wrap: (ks: ReadonlyArray>) => Mat = unsafeCoerce /** * @since 1.0.0 * @category Constructors */ export const from2dVectors: (ks: V.Vec>) => Mat = unsafeCoerce /** * @since 1.0.0 * @category Constructors */ export const fromNestedTuples: { (t: []): Mat<0, 0, A> (t: [[A]]): Mat<1, 1, A> (t: [[A, A]]): Mat<1, 2, A> (t: [[A], [A]]): Mat<2, 1, A> (t: [[A, A], [A, A]]): Mat<2, 2, A> (t: [[A, A, A]]): Mat<1, 3, A> (t: [[A], [A], [A]]): Mat<3, 1, A> (t: [[A, A, A], [A, A, A]]): Mat<2, 3, A> (t: [[A, A], [A, A], [A, A]]): Mat<3, 2, A> (t: [[A, A, A], [A, A, A], [A, A, A]]): Mat<3, 3, A> (t: [[A, A, A, A]]): Mat<1, 4, A> (t: [[A], [A], [A], [A]]): Mat<4, 1, A> (t: [[A, A, A, A], [A, A, A, A]]): Mat<2, 4, A> (t: [[A, A], [A, A], [A, A], [A, A]]): Mat<4, 2, A> (t: [[A, A, A, A], [A, A, A, A], [A, A, A, A]]): Mat<3, 4, A> (t: [[A, A, A], [A, A, A], [A, A, A], [A, A, A]]): Mat<4, 3, A> (t: [[A, A, A, A], [A, A, A, A], [A, A, A, A], [A, A, A, A]]): Mat<4, 4, A> (t: [[A, A, A, A, A]]): Mat<1, 5, A> (t: [[A], [A], [A], [A], [A]]): Mat<5, 1, A> (t: [[A, A, A, A, A], [A, A, A, A, A]]): Mat<2, 5, A> (t: [[A, A], [A, A], [A, A], [A, A], [A, A]]): Mat<5, 2, A> (t: [[A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A]]): Mat<3, 5, A> (t: [[A, A, A], [A, A, A], [A, A, A], [A, A, A], [A, A, A]]): Mat<5, 3, A> (t: [[A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A]]): Mat< 4, 5, A > (t: [[A, A, A, A], [A, A, A, A], [A, A, A, A], [A, A, A, A], [A, A, A, A]]): Mat< 5, 4, A > ( t: [ [A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A] ] ): Mat<5, 5, A> (t: [[A, A, A, A, A, A]]): Mat<1, 6, A> (t: [[A], [A], [A], [A], [A], [A]]): Mat<6, 1, A> (t: [[A, A, A, A, A, A], [A, A, A, A, A, A]]): Mat<2, 6, A> (t: [[A, A], [A, A], [A, A], [A, A], [A, A], [A, A]]): Mat<6, 2, A> (t: [[A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A]]): Mat<3, 6, A> (t: [[A, A, A], [A, A, A], [A, A, A], [A, A, A], [A, A, A], [A, A, A]]): Mat<6, 3, A> ( t: [[A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A]] ): Mat<4, 6, A> ( t: [ [A, A, A, A], [A, A, A, A], [A, A, A, A], [A, A, A, A], [A, A, A, A], [A, A, A, A] ] ): Mat<6, 4, A> ( t: [ [A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A] ] ): Mat<5, 6, A> ( t: [ [A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A], [A, A, A, A, A] ] ): Mat<6, 5, A> ( t: [ [A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A], [A, A, A, A, A, A] ] ): Mat<6, 6, A> } = wrap /** * @since 1.0.0 * @category Constructors */ export const fromNestedReadonlyArrays: ( m: M, n: N ) => (as: ReadonlyArray>) => O.Option> = (m, n) => flow( V.fromReadonlyArray(m), O.chain(V.traverse(O.Applicative)(V.fromReadonlyArray(n))), O.map(from2dVectors) ) /** * @since 1.0.0 * @category Constructors */ export const fromVectorAsRow: (v: V.Vec) => Mat<1, N, A> = flow( V.of, from2dVectors ) /** * @since 1.0.0 * @category Constructors */ export const fromVectorAsColumn: (v: V.Vec) => Mat = flow( V.map(V.of), from2dVectors ) /** * @since 1.0.0 * @category Constructors */ export const makeBy: ( m: M, n: N, f: (a: [number, number]) => A ) => Mat = (m, n, f) => from2dVectors(V.makeBy(m, i => V.makeBy(n, j => f([i, j])))) /** * Constructs the identity matrix * * @since 1.0.0 * @category Constructors */ export const identity: (R: Rng.Ring) => (m: M) => Mat = R => m => makeBy(m, m, ([i, j]) => (i === j ? R.one : R.zero)) /** * @since 1.0.0 * @category Constructors */ export const repeat: ( a: A ) => (m: M, n: N) => Mat = a => (m, n) => from2dVectors(V.repeat(m, V.repeat(n, a))) /** * @since 1.0.0 * @category Constructors */ export const randMatrix: ( m: M, n: N, make: IO.IO ) => IO.IO> = (m, n, make) => pipe(V.randVec(m, V.randVec(n, make)), IO.map(from2dVectors)) /** * @since 1.0.0 * @category Constructors */ export const outerProduct: ( R: Rng.Ring ) => ( v1: V.Vec, v2: V.Vec ) => Mat = R => (v1, v2) => mul(R)(fromVectorAsColumn(v1), fromVectorAsRow(v2)) /** * Constructs a Vandermonde matrix from a vector. Note: Terms is inclusive of the first * column of ones. So a quadratic Vandermonde matrix has 3 terms. * * @since 1.1.0 * @category Constructors */ export const vand: ( terms: N ) => (t: V.Vec) => Mat = terms => flow( V.map(ti => V.makeBy(terms, i => Math.pow(ti, i))), from2dVectors ) // ##################### // ### Non-Pipeables ### // ##################### const _map: Fun.Functor3['map'] = (fa, f) => pipe(fa, map(f)) const _mapWithIndex: FunI.FunctorWithIndex3['mapWithIndex'] = ( fa, f ) => pipe(fa, mapWithIndex(f)) const _reduce: Fl.Foldable3['reduce'] = (fa, b, f) => pipe(fa, reduce(b, f)) const _foldMap: Fl.Foldable3['foldMap'] = M => (fa, f) => pipe(fa, foldMap(M)(f)) const _reduceRight: Fl.Foldable3['reduceRight'] = (fa, b, f) => pipe(fa, reduceRight(b, f)) const _reduceWithIndex: FlI.FoldableWithIndex3< URI, [number, number] >['reduceWithIndex'] = (fa, b, f) => pipe(fa, reduceWithIndex(b, f)) const _foldMapWithIndex: FlI.FoldableWithIndex3< URI, [number, number] >['foldMapWithIndex'] = M => (fa, f) => pipe(fa, foldMapWithIndex(M)(f)) const _reduceRightWithIndex: FlI.FoldableWithIndex3< URI, [number, number] >['reduceRightWithIndex'] = (fa, b, f) => pipe(fa, reduceRightWithIndex(b, f)) // ################# // ### Instances ### // ################# /** * @since 1.0.0 * @category Instances */ export const URI = 'Mat' /** * @since 1.0.0 * @category Instances */ export type URI = typeof URI declare module 'fp-ts/HKT' { interface URItoKind3 { readonly [URI]: Mat } interface URItoKind2 { readonly [URI]: Mat } } /** * @since 1.1.0 * @category Instances */ export const getSquareMonoidProduct = (R: Rng.Ring) => (m: M): Mn.Monoid> => ({ empty: identity(R)(m), concat: (a, b) => mul(R)(a, b), }) /** * @since 1.0.0 * @category Instances */ export const getAdditiveAbelianGroup = (R: Rng.Ring) => (m: M, n: N): TC.AbelianGroup> => ({ empty: repeat(R.zero)(m, n), concat: lift2(R.add), inverse: flow(V.map(V.map(b => R.sub(R.zero, b))), a => from2dVectors(a)), }) /** * @since 1.0.0 * @category Instances */ export const getBimodule: ( R: Rng.Ring ) => (m: M, n: N) => TC.Bimodule, A> = R => (m, n) => ({ ...getAdditiveAbelianGroup(R)(m, n), leftScalarMul: (r, x) => pipe( x, map(a => R.mul(r, a)) ), rightScalarMul: (x, r) => pipe( x, map(a => R.mul(r, a)) ), }) /** * @since 1.0.0 * @category Instance Operations */ export const map: (f: (a: A) => B) => (v: Mat) => Mat = f => flow(V.map(V.map(f)), a => wrap(a)) /** * @since 1.0.0 * @category Instances */ export const Functor: Fun.Functor3 = { URI, map: _map, } /** * @since 1.0.0 * @category Instance Operations */ export const mapWithIndex: ( f: (ij: [number, number], a: A) => B ) => (v: Mat) => Mat = f => flow( V.mapWithIndex((i, a) => pipe( a, V.mapWithIndex((j, b) => f([i, j], b)) ) ), a => wrap(a) ) /** * @since 1.0.0 * @category Instances */ export const FunctorWithIndex: FunI.FunctorWithIndex3 = { ...Functor, mapWithIndex: _mapWithIndex, } /** * @since 1.0.0 * @category Instance Operations */ export const reduce: ( b: B, f: (b: B, a: A) => B ) => (fa: Mat) => B = (b, f) => V.reduce(b, (b, a) => pipe(a, V.reduce(b, f))) /** * @since 1.0.0 * @category Instance Operations */ export const foldMap: ( M: Mn.Monoid ) => (f: (a: A) => M) => (fa: Mat) => M = M => f => V.foldMap(M)(a => pipe(a, V.foldMap(M)(f))) /** * @since 1.0.0 * @category Instance Operations */ export const reduceRight: ( b: A, f: (b: B, a: A) => A ) => (fa: Mat) => A = (a, f) => V.reduceRight(a, (b, a) => pipe(b, V.reduceRight(a, f))) /** * @since 1.0.0 * @category Instances */ export const Foldable: Fl.Foldable3 = { URI, reduce: _reduce, foldMap: _foldMap, reduceRight: _reduceRight, } /** * @since 1.0.0 * @category Instance Operations */ export const reduceWithIndex: ( b: B, f: (i: [number, number], b: B, a: A) => B ) => (fa: Mat) => B = (b, f) => V.reduceWithIndex(b, (i, b, a) => pipe( a, V.reduceWithIndex(b, (j, b, a) => f([i, j], b, a)) ) ) /** * @since 1.0.0 * @category Instance Operations */ export const foldMapWithIndex: ( M: Mn.Monoid ) => (f: (i: [number, number], a: A) => M) => (fa: Mat) => M = M => f => V.foldMapWithIndex(M)((i, a) => pipe( a, V.foldMapWithIndex(M)((j, a) => f([i, j], a)) ) ) /** * @since 1.0.0 * @category Instance Operations */ export const reduceRightWithIndex: ( b: A, f: (i: [number, number], b: B, a: A) => A ) => (fa: Mat) => A = (a, f) => V.reduceRightWithIndex(a, (i, a, b) => pipe( a, V.reduceRightWithIndex(b, (j, a, b) => f([i, j], a, b)) ) ) /** * @since 1.0.0 * @category Instances */ export const FoldableWithIndex: FlI.FoldableWithIndex3 = { ...Foldable, reduceWithIndex: _reduceWithIndex, foldMapWithIndex: _foldMapWithIndex, reduceRightWithIndex: _reduceRightWithIndex, } /** * @since 1.0.0 * @category Instance Operations */ export function sequence( F: Apl.Applicative4 ): ( ta: Mat> ) => Kind4> export function sequence( F: Apl.Applicative3 ): (ta: Mat>) => Kind3> export function sequence( F: Apl.Applicative2 ): (ta: Mat>) => Kind2> export function sequence( F: Apl.Applicative1 ): (ta: Mat>) => Kind> export function sequence( F: Apl.Applicative ): (ta: Mat>) => HKT> { return ta => pipe( ta, V.traverse(F)(a => pipe(a, V.traverse(F)(id))), a => F.map(a, b => wrap(b)) ) } /** * @since 1.0.0 * @category Instance Operations */ export function traverse( F: Apl.Applicative4 ): ( f: (a: A) => Kind4 ) => (ta: Mat) => Kind4> export function traverse( F: Apl.Applicative3 ): ( f: (a: A) => Kind3 ) => (ta: Mat) => Kind3> export function traverse( F: Apl.Applicative2 ): ( f: (a: A) => Kind2 ) => (ta: Mat) => Kind2> export function traverse( F: Apl.Applicative1 ): (f: (a: A) => Kind) => (ta: Mat) => Kind> export function traverse( F: Apl.Applicative ): (f: (a: A) => HKT) => (ta: Mat) => HKT> { return f => ta => pipe( ta, V.traverse(F)(a => pipe( a, V.traverse(F)(b => f(b)) ) ), a => F.map(a, b => wrap(b)) ) } /** * @since 1.0.0 * @category Instance Operations */ export function traverseWithIndex( F: Apl.Applicative4 ): ( f: (i: [number, number], a: A) => Kind4 ) => (ta: Mat) => Kind4> export function traverseWithIndex( F: Apl.Applicative3 ): ( f: (i: [number, number], a: A) => Kind3 ) => (ta: Mat) => Kind3> export function traverseWithIndex( F: Apl.Applicative2 ): ( f: (i: [number, number], a: A) => Kind2 ) => (ta: Mat) => Kind2> export function traverseWithIndex( F: Apl.Applicative1 ): ( f: (i: [number, number], a: A) => Kind ) => (ta: Mat) => Kind> export function traverseWithIndex( F: Apl.Applicative ): ( f: (i: [number, number], a: A) => HKT ) => (ta: Mat) => HKT> { return f => ta => pipe( ta, V.traverseWithIndex(F)((i, a) => pipe( a, V.traverseWithIndex(F)((j, b) => f([i, j], b)) ) ), a => F.map(a, b => wrap(b)) ) } // ################### // ### Destructors ### // ################### /** * @since 1.0.0 * @category Destructors */ export const toNestedReadonlyArrays: ( m: Mat ) => ReadonlyArray> = unsafeCoerce /** * @since 1.0.0 * @category Destructors */ export const toNestedArrays: (m: Mat) => Array> = flow( RA.map(a => a.concat()), bs => bs.concat() ) /** * @since 1.0.0 * @category Destructors */ export const shape: ( m: Mat ) => [M, N] = m => m[0] === undefined ? [0 as typeof m['_rows'], 0 as typeof m['_cols']] : [m.length as typeof m['_rows'], m[0].length as typeof m['_cols']] // ################## // ### Sub-Matrix ### // ################## /** * Map a particular row of a matrix * * @since 1.1.0 * @category Sub-Matrix */ export const mapRow: ( rowIndex: number, f: (a: A) => A ) => (m: Mat) => O.Option> = (rowIndex, f) => m => pipe( shape(m), O.fromPredicate(([rows]) => rowIndex >= 0 && rowIndex < rows), O.map(([, cols]) => { const _: (xs: ReadonlyArray, i: number) => A = (xs, i) => unsafeCoerce(xs[i]) const A = toNestedArrays(m) for (let j = 0; j < cols; ++j) { _(A, rowIndex)[j] = f(_(_(A, rowIndex), j)) } return wrap(A) }) ) /** * Reduce the rows of a matrix to a vector of opposite length * * @since 1.1.0 * @category Sub-Matrix */ export const reduceByRow: ( f: (a: V.Vec) => B ) => (m: Mat) => V.Vec = f => V.map(f) /** * Map a particular column of a matrix * * @since 1.1.0 * @category Sub-Matrix */ export const mapColumn: ( columnIndex: number, f: (a: A) => A ) => (m: Mat) => O.Option> = (columnIndex, f) => m => pipe( shape(m), O.fromPredicate(([, cols]) => columnIndex >= 0 && columnIndex < cols), O.map(([rows]) => { const _: (xs: ReadonlyArray, i: number) => A = (xs, i) => unsafeCoerce(xs[i]) const A = toNestedArrays(m) for (let i = 0; i < rows; ++i) { _(A, i)[columnIndex] = f(_(_(A, i), columnIndex)) } return wrap(A) }) ) /** * Reduce the columns of a matrix to a vector of opposite length * * @since 1.1.0 * @category Sub-Matrix */ export const reduceByColumn: ( f: (a: V.Vec) => B ) => (A: Mat) => V.Vec = f => A => { const _: (xs: ReadonlyArray, i: number) => A = (xs, i) => unsafeCoerce(xs[i]) const [m, n] = shape(A) const out = [] for (let k = 0; k < n; ++k) { const col = [] for (let i = 0; i < m; ++i) { col.push(_(_(A, i), k)) } out.push(f(unsafeCoerce(col))) } return unsafeCoerce(out) } /** * Used to extract a sub-column from a matrix, and returns a new generic `P` that * represents the length of the sub-column. * * Note: `fromIncl` is the **inclusive** column start-index, and `toExcl` is the * **exclusive** column end-index. If `toExcl` is omitted, then the extracted sub-column * will span to the last row of the matrix. * * Note: In order to preserve type safety, P cannot be inferred, and must be passed * directly as a type argument. * * If `P` is unknown, it can be declared in the parent function as an arbitrary generic * that has a numeric constraint. * * See: Decomposition > QR as an example declaring an unknown length constraint * * @since 1.1.0 * @category Sub-Matrix */ export const getSubColumn: ( col: number, fromIncl: number, toExcl?: number ) =>

( m: Mat ) => O.Option> = (col, fromRowI, toExcl) => A => { const _: (xs: ReadonlyArray, i: number) => A = (xs, i) => unsafeCoerce(xs[i]) return pipe( shape(A), O.fromPredicate( ([rows, cols]) => col >= 0 && col < cols && fromRowI >= 0 && fromRowI < rows && (toExcl === undefined || (toExcl > fromRowI && toExcl <= rows)) ), O.map(([rows]) => { const sub = [] const toRowI = toExcl === undefined ? rows : toExcl for (let i = fromRowI; i < toRowI; ++i) { sub.push(_(_(A, i), col)) } return unsafeCoerce(sub) }) ) } /** * Used to replace a sub-column of a matrix along with a generic `P` that is the length of * the sub-column. If `P` is incompatible with matrix length `N`, or provided indices will * result in an overflow, `O.none` is returned. * * Note: `fromRowIncl` is the **inclusive** row start-index * * @since 1.1.0 * @category Sub-Matrix */ export const updateSubColumn:

( col: number, fromRowIncl: number, repl: V.Vec ) => (m: Mat) => O.Option> = (col, fromRowI, repl) => A => { const _: (xs: ReadonlyArray, i: number) => A = (xs, i) => unsafeCoerce(xs[i]) const p = V.size(repl) return pipe( shape(A), O.fromPredicate( ([rows, cols]) => col >= 0 && col < cols && fromRowI >= 0 && fromRowI + p <= rows ), O.map(() => { const Ap = toNestedArrays(A) for (let i = fromRowI; i < p + fromRowI; ++i) { _(Ap, i)[col] = _(repl, i - fromRowI) } return unsafeCoerce(Ap) }) ) } /** * Used to extract a portion of a matrix, and returns new generics `P` and `Q` that * represents the the rows / columns of the extracted sub-matrix. * * Note: `rowFromIncl` and `colFromIncl` are the **inclusive** row / column start-indices, * and `rowToExcl` and `colToExcl` are the **exclusive** row / column end-indices. If * `rowToExcl` or `colToExcl` are omitted, the extracted sub-matrix will span to the final * row / column. * * Note: In order to preserve type safety, `P` and `Q` cannot be inferred, and must be * passed directly as type arguments. * * If `P` and `Q` are unknown, they can be declared in the parent function as arbitrary * generics that have a numeric constraint. * * See: Decomposition > QR as an example declaring unknown length constraints * * @since 1.1.0 * @category Sub-Matrix */ export const getSubMatrix:

( rowFromIncl: number, colFromIncl: number, rowToExcl?: number, colToExcl?: number ) => (m: Mat) => O.Option> = (i1, j1, i2, j2) => A => { const _ = (xs: ReadonlyArray, i: number): A => unsafeCoerce(xs[i]) return pipe( shape(A), O.fromPredicate( ([rows, cols]) => i1 >= 0 && i1 < rows && j1 >= 0 && j1 < cols && (i2 === undefined || (i2 > i1 && i2 <= rows)) && (j2 === undefined || (j2 > j1 && j2 <= cols)) ), O.map(([m, n]) => { const out = [] const upI = i2 === undefined ? m : i2 const upJ = j2 === undefined ? n : j2 for (let i = i1; i < upI; ++i) { const outi = [] for (let j = j1; j < upJ; ++j) { outi.push(_(_(A, i), j)) } out.push(outi) } return unsafeCoerce(out) }) ) } /** * Used to replace a portion of a matrix with generics `P` and `Q` that are the rows / * columns of the replacement sub-matrix. If `P` is incompatible with matrix rows `M`, `Q` * is incompatible with matrix columns `N`, or if provided indices will result in an * overflow, `O.none` is returned. * * Note: `rowFromIncl` and `colFromIncl` are the **inclusive** row / column start-indices * * @since 1.1.0 * @category Sub-Matrix */ export const updateSubMatrix:

( rowFromIncl: number, colFromIncl: number, repl: Mat ) => (m: Mat) => O.Option> = (i1, j1, repl) => m => { const _ = (xs: ReadonlyArray, i: number): A => unsafeCoerce(xs[i]) return pipe( tuple(shape(m), shape(repl)), O.fromPredicate( ([[rows, cols], [replRows, replCols]]) => i1 >= 0 && i1 + replRows <= rows && j1 >= 0 && j1 + replCols <= cols ), O.map(([, [i2, j2]]) => { const A = toNestedArrays(m) for (let i = i1; i < i1 + i2; ++i) { for (let j = j1; j < j1 + j2; ++j) { _(A, i)[j] = _(_(repl, i - i1), j - j1) } } return unsafeCoerce(A) }) ) } /** * Add a column at the beginning of a matrix. Due to the limitations of the typesystem, * the length parameter must be passed explicitly, and will be the number of columns of * the returned matrix. * * @since 1.1.0 * @category Sub-Matrix */ export const prependColumn = (c0: V.Vec) =>

(m: Mat): Mat => pipe( V.zipVectors(m, c0), V.map(([ci, c0i]) => V.prepend(c0i)(ci)), from2dVectors ) /** * Add a column at the end of a matrix. Due to the limitations of the typesystem, the * length parameter must be passed explicitly, and will be the number of columns of the * returned matrix. * * @since 1.1.0 * @category Sub-Matrix */ export const appendColumn = (c0: V.Vec) =>

(m: Mat): Mat => pipe( V.zipVectors(m, c0), V.map(([ci, c0i]) => V.append(c0i)(ci)), from2dVectors ) /** * Crops a matrix to be square by removing excess rows. Returns O.none if there are more * columns than rows. * * @since 1.1.0 * @category Sub-Matrix */ export const cropRows: ( m: Mat ) => O.Option> = mat => pipe( shape(mat), O.fromPredicate(([m, n]) => m >= n), O.map(([m, n]) => pipe(mat, RA.dropRight(m - n), a => wrap(a))) ) /** * Crops a matrix to be square by removing excess columns. Returns O.none if there are * more columns than rows. * * @since 1.1.0 * @category Sub-Matrix */ export const cropColumns: ( m: Mat ) => O.Option> = mat => pipe( shape(mat), O.fromPredicate(([m, n]) => n >= m), O.map(([m, n]) => pipe(mat, V.map(RA.dropRight(n - m)), a => wrap(a))) ) /** * @since 1.0.0 * @category Sub-Matrix */ export const switchRows = (i: number, j: number) => (vs: Mat): O.Option> => i === j ? O.some(vs) : pipe( O.Do, O.apS('ir', V.get(i)(vs)), O.apS('jr', V.get(j)(vs)), O.chain(({ ir, jr }) => pipe( vs, replaceRow(i)(() => jr), O.chain(replaceRow(j)(() => ir)) ) ) ) /** * @since 1.1.0 * @category Sub-Matrix */ export const switchColumns = (i: number, j: number) => (vs: Mat): O.Option> => i === j ? O.some(vs) : pipe( O.Do, O.apS('ic', getSubColumn(i, 0)(vs)), O.apS('jc', getSubColumn(j, 0)(vs)), O.chain(({ ic, jc }) => pipe(vs, updateSubColumn(i, 0, jc), O.chain(updateSubColumn(j, 0, ic))) ) ) // ######################### // ### Matrix Operations ### // ######################### /** * Multiply two matricies with matching inner dimensions * * ```math * (A ∈ R_mn) (B ∈ R_np) = C ∈ R_mp * ``` * * Efficiency: `2mpn` flops (for numeric Ring) * * @since 1.0.0 * @category Matrix Operations */ export const mul = (R: Rng.Ring) => ( x: Mat, y: Mat ): Mat => x[0] === undefined || y[0] === undefined ? wrap([]) : pipe( repeat(R.zero)(shape(x)[0], shape(y)[1]), mapWithIndex(([i, j]) => { const _ = (rs: ReadonlyArray, i: number): A => unsafeCoerce(rs[i]) let out = R.zero for (let k = 0; k < y.length; ++k) { out = R.add(out, R.mul(_(_(x, i), k), _(_(y, k), j))) } return out }) ) /** * Transform a vector `x` into vector `b` by matrix `A` * * ```math * Ax = b * ``` * * Efficiency: `2mn` flops (for numeric Ring) * * @since 1.0.0 * @category Matrix Operations */ export const linMap = (R: Rng.Ring) => (A: Mat, x: V.Vec): V.Vec => pipe( A, V.map(Ai => { const _ = (rs: ReadonlyArray, i: number): A => unsafeCoerce(rs[i]) let out = R.zero for (let j = 0; j < Ai.length; ++j) { out = R.add(out, R.mul(_(Ai, j), _(x, j))) } return out }) ) /** * Transform a row-vector `x` into vector `b` by matrix `A` * * ```math * xA = b * ``` * * Efficiency: `2mn` flops (for numeric Ring) * * @since 1.1.0 * @category Matrix Operations */ export const linMapR = (R: Rng.Ring) => ( x: V.Vec, A: Mat ): V.Vec => { const _ = (rs: ReadonlyArray, i: number): A => unsafeCoerce(rs[i]) const [n, m] = shape(A) const out = [] for (let i = 0; i < m; ++i) { let outi = R.zero for (let j = 0; j < n; ++j) { outi = R.add(outi, R.mul(_(x, j), _(_(A, j), i))) } out.push(outi) } return unsafeCoerce(out) } /** * The sum of the diagonal elements * * Efficiency: `m` flops (for numeric Ring) * * @since 1.0.0 * @category Matrix Operations */ export const trace: (R: Rng.Ring) => (fa: Mat) => A = R => fa => { const _ = (rs: ReadonlyArray, i: number): A => unsafeCoerce(rs[i]) let out = R.zero for (let i = 0; i < fa.length; i++) { out = R.add(out, _(_(fa, i), i)) } return out } /** * @since 1.0.0 * @category Matrix Operations */ export const transpose = ( v: Mat ): Mat => { const _ = (xs: ReadonlyArray, i: number): A => unsafeCoerce(xs[i]) return v[0] === undefined ? wrap([]) : pipe( repeat(0)(shape(v)[1] as N, shape(v)[0]), mapWithIndex(([i, j]) => _(_(v, j), i)) ) } /** * @since 1.0.0 * @category Matrix Operations */ export const get: (i: number, j: number) => (m: Mat) => O.Option = ( i, j ) => flow(V.get(i), O.chain(V.get(j))) /** * @since 1.1.0 * @category Matrix Operations */ export const updateAt: ( i: number, j: number, a: A ) => (A: Mat) => O.Option> = (i, j, val) => A => pipe( shape(A), O.fromPredicate(([rows, cols]) => i >= 0 && j >= 0 && i < rows && j < cols), O.map(() => { const _ = (xs: ReadonlyArray, i: number): A => unsafeCoerce(xs[i]) const Ap = toNestedArrays(A) _(Ap, i)[j] = val return unsafeCoerce(Ap) }) ) /** * @since 1.0.0 * @category Matrix Operations */ export const lift2: ( f: (x: A, y: A) => B ) => (x: Mat, y: Mat) => Mat = f => flow(V.lift2(V.lift2(f)), from2dVectors) // ################ // ### Internal ### // ################ /** * @since 1.0.0 * @category Internal */ const replaceRow: ( m: number ) => ( f: (vm: V.Vec) => V.Vec ) => (as: Mat) => O.Option> = m => f => as => pipe( V.get(m)(as), O.chain(a => V.updateAt(m)(f(a))(as)), O.map(a => wrap(a)) )