Linear Algebra

A LinearAlgebra typeclass.

The axle-jblas spoke provides witnesses for JBLAS matrices.

The default jblas matrix toString isn't very readable, so this tutorial wraps most results in the Axle string function, invoking the cats.Show witness for those matrices.

Imports and implicits

Import JBLAS and Axle's LinearAlgebra witness for it.

import cats.implicits._

import spire.algebra.Field
import spire.algebra.NRoot

import axle._
import axle.jblas._
import axle.syntax.linearalgebra.matrixOps

implicit val fieldDouble: Field[Double] = spire.implicits.DoubleAlgebra
implicit val nrootDouble: NRoot[Double] = spire.implicits.DoubleAlgebra

implicit val laJblasDouble = axle.jblas.linearAlgebraDoubleMatrix[Double]
import laJblasDouble._

Creating Matrices

ones(2, 3).show
// res1: String = """1.000000 1.000000 1.000000
// 1.000000 1.000000 1.000000"""

ones(1, 4).show
// res2: String = "1.000000 1.000000 1.000000 1.000000"

ones(4, 1).show
// res3: String = """1.000000
// 1.000000
// 1.000000
// 1.000000"""

Creating matrices from arrays

fromColumnMajorArray(2, 2, List(1.1, 2.2, 3.3, 4.4).toArray).show
// res4: String = """1.100000 3.300000
// 2.200000 4.400000"""

fromColumnMajorArray(2, 2, List(1.1, 2.2, 3.3, 4.4).toArray).t.show
// res5: String = """1.100000 2.200000
// 3.300000 4.400000"""

val m = fromColumnMajorArray(4, 5, (1 to 20).map(_.toDouble).toArray)
// m: org.jblas.DoubleMatrix = [1.000000, 5.000000, 9.000000, 13.000000, 17.000000; 2.000000, 6.000000, 10.000000, 14.000000, 18.000000; 3.000000, 7.000000, 11.000000, 15.000000, 19.000000; 4.000000, 8.000000, 12.000000, 16.000000, 20.000000]
m.show
// res6: String = """1.000000 5.000000 9.000000 13.000000 17.000000
// 2.000000 6.000000 10.000000 14.000000 18.000000
// 3.000000 7.000000 11.000000 15.000000 19.000000
// 4.000000 8.000000 12.000000 16.000000 20.000000"""

Random matrices

val r = rand(3, 3)
// r: org.jblas.DoubleMatrix = [0.097284, 0.543659, 0.426756; 0.449587, 0.661096, 0.363255; 0.767837, 0.382935, 0.563905]

r.show
// res7: String = """0.097284 0.543659 0.426756
// 0.449587 0.661096 0.363255
// 0.767837 0.382935 0.563905"""

Matrices defined by functions

matrix(4, 5, (r, c) => r / (c + 1d)).show
// res8: String = """0.000000 0.000000 0.000000 0.000000 0.000000
// 1.000000 0.500000 0.333333 0.250000 0.200000
// 2.000000 1.000000 0.666667 0.500000 0.400000
// 3.000000 1.500000 1.000000 0.750000 0.600000"""

matrix(4, 5, 1d,
  (r: Int) => r + 0.5,
  (c: Int) => c + 0.6,
  (r: Int, c: Int, diag: Double, left: Double, right: Double) => diag).show
// res9: String = """1.000000 1.600000 2.600000 3.600000 4.600000
// 1.500000 1.000000 1.600000 2.600000 3.600000
// 2.500000 1.500000 1.000000 1.600000 2.600000
// 3.500000 2.500000 1.500000 1.000000 1.600000"""

Metadata

val x = fromColumnMajorArray(3, 1, Vector(4.0, 5.1, 6.2).toArray)
// x: org.jblas.DoubleMatrix = [4.000000; 5.100000; 6.200000]
x.show
// res10: String = """4.000000
// 5.100000
// 6.200000"""

val y = fromColumnMajorArray(3, 1, Vector(7.3, 8.4, 9.5).toArray)
// y: org.jblas.DoubleMatrix = [7.300000; 8.400000; 9.500000]
y.show
// res11: String = """7.300000
// 8.400000
// 9.500000"""

x.isEmpty
// res12: Boolean = false

x.isRowVector
// res13: Boolean = false

x.isColumnVector
// res14: Boolean = true

x.isSquare
// res15: Boolean = false

x.isScalar
// res16: Boolean = false

x.rows
// res17: Int = 3

x.columns
// res18: Int = 1

x.length
// res19: Int = 3

Accessing columns, rows, and elements

x.column(0).show
// res20: String = """4.000000
// 5.100000
// 6.200000"""

x.row(1).show
// res21: String = "5.100000"

x.get(2, 0)
// res22: Double = 6.2

val fiveByFive = fromColumnMajorArray(5, 5, (1 to 25).map(_.toDouble).toArray)
// fiveByFive: org.jblas.DoubleMatrix = [1.000000, 6.000000, 11.000000, 16.000000, 21.000000; 2.000000, 7.000000, 12.000000, 17.000000, 22.000000; 3.000000, 8.000000, 13.000000, 18.000000, 23.000000; 4.000000, 9.000000, 14.000000, 19.000000, 24.000000; 5.000000, 10.000000, 15.000000, 20.000000, 25.000000]

fiveByFive.show
// res23: String = """1.000000 6.000000 11.000000 16.000000 21.000000
// 2.000000 7.000000 12.000000 17.000000 22.000000
// 3.000000 8.000000 13.000000 18.000000 23.000000
// 4.000000 9.000000 14.000000 19.000000 24.000000
// 5.000000 10.000000 15.000000 20.000000 25.000000"""

fiveByFive.slice(1 to 3, 2 to 4).show
// res24: String = """12.000000 17.000000 22.000000
// 13.000000 18.000000 23.000000
// 14.000000 19.000000 24.000000"""

fiveByFive.slice(0.until(5,2), 0.until(5,2)).show
// res25: String = """1.000000 11.000000 21.000000
// 3.000000 13.000000 23.000000
// 5.000000 15.000000 25.000000"""

Negate, Transpose, Power

x.negate.show
// res26: String = """-4.000000
// -5.100000
// -6.200000"""

x.transpose.show
// res27: String = "4.000000 5.100000 6.200000"

// x.log
// x.log10

x.pow(2d).show
// res28: String = """16.000000
// 26.010000
// 38.440000"""

Mins, Maxs, Ranges, and Sorts

r.max
// res29: Double = 0.7678372997210756

r.min
// res30: Double = 0.0972843504537868

// r.ceil
// r.floor

r.rowMaxs.show
// res31: String = """0.543659
// 0.661096
// 0.767837"""

r.rowMins.show
// res32: String = """0.097284
// 0.363255
// 0.382935"""

r.columnMaxs.show
// res33: String = "0.767837 0.661096 0.563905"

r.columnMins.show
// res34: String = "0.097284 0.382935 0.363255"

rowRange(r).show
// res35: String = """0.446375
// 0.297841
// 0.384902"""

columnRange(r).show
// res36: String = "0.670553 0.278161 0.200650"

r.sortRows.show
// res37: String = """0.097284 0.426756 0.543659
// 0.363255 0.449587 0.661096
// 0.382935 0.563905 0.767837"""

r.sortColumns.show
// res38: String = """0.097284 0.382935 0.363255
// 0.449587 0.543659 0.426756
// 0.767837 0.661096 0.563905"""

r.sortRows.sortColumns.show
// res39: String = """0.097284 0.426756 0.543659
// 0.363255 0.449587 0.661096
// 0.382935 0.563905 0.767837"""

Statistics

r.rowMeans.show
// res40: String = """0.355900
// 0.491313
// 0.571559"""

r.columnMeans.show
// res41: String = "0.438236 0.529230 0.451305"

// median(r)

sumsq(r).show
// res42: String = "0.801166 0.879253 0.632064"

std(r).show
// res43: String = "0.273870 0.114016 0.083734"

cov(r).show
// res44: String = """0.038749 0.000055 -0.002697
// 0.000055 0.005796 0.004294
// -0.002697 0.004294 0.004226"""

centerRows(r).show
// res45: String = """-0.258616 0.187760 0.070856
// -0.041726 0.169783 -0.128057
// 0.196278 -0.188624 -0.007654"""

centerColumns(r).show
// res46: String = """-0.340952 0.014429 -0.024549
// 0.011350 0.131866 -0.088050
// 0.329601 -0.146295 0.112600"""

zscore(r).show
// res47: String = """-1.244941 0.126554 -0.293183
// 0.041445 1.156554 -1.051546
// 1.203496 -1.283108 1.344729"""

Principal Component Analysis

val (u, s) = pca(r)
// u: org.jblas.DoubleMatrix = [-0.996881, -0.056116, 0.055494; 0.008517, -0.775546, -0.631234; 0.078461, -0.628792, 0.773605]
// s: org.jblas.DoubleMatrix = [0.038961; 0.009281; 0.000529]

u.show
// res48: String = """-0.996881 -0.056116 0.055494
// 0.008517 -0.775546 -0.631234
// 0.078461 -0.628792 0.773605"""

s.show
// res49: String = """0.038961
// 0.009281
// 0.000529"""

Horizontal and vertical concatenation

(x aside y).show
// res50: String = """4.000000 7.300000
// 5.100000 8.400000
// 6.200000 9.500000"""

(x atop y).show
// res51: String = """4.000000
// 5.100000
// 6.200000
// 7.300000
// 8.400000
// 9.500000"""

Addition and subtraction

val z = ones(2, 3)
// z: org.jblas.DoubleMatrix = [1.000000, 1.000000, 1.000000; 1.000000, 1.000000, 1.000000]

z.show
// res52: String = """1.000000 1.000000 1.000000
// 1.000000 1.000000 1.000000"""

Matrix addition

import spire.implicits.additiveSemigroupOps

(z + z).show
// res53: String = """2.000000 2.000000 2.000000
// 2.000000 2.000000 2.000000"""

Scalar addition (JBLAS method)

z.addScalar(1.1).show
// res54: String = """2.100000 2.100000 2.100000
// 2.100000 2.100000 2.100000"""

Matrix subtraction

import spire.implicits.additiveGroupOps

(z - z).show
// res55: String = """0.000000 0.000000 0.000000
// 0.000000 0.000000 0.000000"""

Scalar subtraction (JBLAS method)

z.subtractScalar(0.2).show
// res56: String = """0.800000 0.800000 0.800000
// 0.800000 0.800000 0.800000"""

Multiplication and Division

Scalar multiplication

z.multiplyScalar(3d).show
// res57: String = """3.000000 3.000000 3.000000
// 3.000000 3.000000 3.000000"""

Matrix multiplication

import spire.implicits.multiplicativeSemigroupOps

(z * z.transpose).show
// res58: String = """3.000000 3.000000
// 3.000000 3.000000"""

Scalar division (JBLAS method)

z.divideScalar(100d).show
// res59: String = """0.010000 0.010000 0.010000
// 0.010000 0.010000 0.010000"""

Map element values

implicit val endo = axle.jblas.endoFunctorDoubleMatrix[Double]
// endo: algebra.Endofunctor[org.jblas.DoubleMatrix, Double] = axle.jblas.package$$anon$1@239fdf0e
import axle.syntax.endofunctor.endofunctorOps

val half = ones(3, 3).map(_ / 2d)
// half: org.jblas.DoubleMatrix = [0.500000, 0.500000, 0.500000; 0.500000, 0.500000, 0.500000; 0.500000, 0.500000, 0.500000]

half.show
// res60: String = """0.500000 0.500000 0.500000
// 0.500000 0.500000 0.500000
// 0.500000 0.500000 0.500000"""

Boolean operators

(r lt half).show
// res61: String = """1.000000 0.000000 1.000000
// 1.000000 0.000000 1.000000
// 0.000000 1.000000 0.000000"""

(r le half).show
// res62: String = """1.000000 0.000000 1.000000
// 1.000000 0.000000 1.000000
// 0.000000 1.000000 0.000000"""

(r gt half).show
// res63: String = """0.000000 1.000000 0.000000
// 0.000000 1.000000 0.000000
// 1.000000 0.000000 1.000000"""

(r ge half).show
// res64: String = """0.000000 1.000000 0.000000
// 0.000000 1.000000 0.000000
// 1.000000 0.000000 1.000000"""

(r eq half).show
// res65: String = """0.000000 0.000000 0.000000
// 0.000000 0.000000 0.000000
// 0.000000 0.000000 0.000000"""

(r ne half).show
// res66: String = """1.000000 1.000000 1.000000
// 1.000000 1.000000 1.000000
// 1.000000 1.000000 1.000000"""

((r lt half) or (r gt half)).show
// res67: String = """1.000000 1.000000 1.000000
// 1.000000 1.000000 1.000000
// 1.000000 1.000000 1.000000"""

((r lt half) and (r gt half)).show
// res68: String = """0.000000 0.000000 0.000000
// 0.000000 0.000000 0.000000
// 0.000000 0.000000 0.000000"""

((r lt half) xor (r gt half)).show
// res69: String = """1.000000 1.000000 1.000000
// 1.000000 1.000000 1.000000
// 1.000000 1.000000 1.000000"""

((r lt half) not).show
// res70: String = """0.000000 1.000000 0.000000
// 0.000000 1.000000 0.000000
// 1.000000 0.000000 1.000000"""

Higher order methods

(m.map(_ + 1)).show
// res71: String = """2.000000 6.000000 10.000000 14.000000 18.000000
// 3.000000 7.000000 11.000000 15.000000 19.000000
// 4.000000 8.000000 12.000000 16.000000 20.000000
// 5.000000 9.000000 13.000000 17.000000 21.000000"""

(m.map(_ * 10)).show
// res72: String = """10.000000 50.000000 90.000000 130.000000 170.000000
// 20.000000 60.000000 100.000000 140.000000 180.000000
// 30.000000 70.000000 110.000000 150.000000 190.000000
// 40.000000 80.000000 120.000000 160.000000 200.000000"""

// m.foldLeft(zeros(4, 1))(_ + _)

(m.foldLeft(ones(4, 1))(_ mulPointwise _)).show
// res73: String = """9945.000000
// 30240.000000
// 65835.000000
// 122880.000000"""

// m.foldTop(zeros(1, 5))(_ + _)

(m.foldTop(ones(1, 5))(_ mulPointwise _)).show
// res74: String = "24.000000 1680.000000 11880.000000 43680.000000 116280.000000"