Linear Algebra
SMILE’s numerical core lives in
smile.tensor:
Vector, DenseMatrix, SparseMatrix,
BandMatrix, and SymmMatrix, plus factorizations
(LU, QR, Cholesky, SVD, eigen). Heavy ops call native BLAS/LAPACK via Java FFM when
available and automatically fall back to pure-Java implementations
(JBlas and JLapack) otherwise. You can inspect availability
with NativeLibrary.isBlasAvailable() / isLapackAvailable()
or select the backend via -Dsmile.linalg.backend=auto|native|java
(or the SMILE_LINALG_BACKEND environment variable).
This page walks from scalar helpers through vectors and matrices to
decompositions. Java uses the tensor API directly; Scala adds a lazy
expression DSL (+, *, %*%,
\) on top of the same types.
Math Functions
Besides java.lang.Math,
smile.math.MathEx
covers combinatorics, norms, distances, correlations, and related helpers
used throughout the library.
import static smile.math.MathEx.*
choose(10, 3)
120.0
import smile.math.MathEx.*
choose(10, 3)
120.0
Special Functions
smile.math.special
provides beta, erf / erfc,
gamma / lgamma, and digamma.
These show up in probability, statistics, and differential equations —
for example erfc(x) = 1 - erf(x) is a special case of the
incomplete gamma function.
import smile.math.special.*
Erf.erf(1.0)
Gamma.digamma(1.0)
0.8427007929497149
-0.5772156649015328
import smile.math.*
erf(1.0)
digamma(1.0)
0.8427007929497149
-0.5772156649015328
Vectors
Most algorithms take double[] (or float[]) and
operate through MathEx. Scala lifts arrays into lazy vector
expressions so arithmetic looks like math; evaluation happens when a
concrete vector (or print) is needed.
import static smile.math.MathEx.*
double[] x = {1.0, 2.0, 3.0, 4.0}
double[] y = {4.0, 3.0, 2.0, 1.0}
// Java has no vector operator overloading; use MathEx or smile.tensor.Vector.
norm(x)
dot(x, y)
5.477225575051661
20.0
import smile.math.MathEx.*
val x = Array(1.0, 2.0, 3.0, 4.0)
val y = Array(4.0, 3.0, 2.0, 1.0)
x + y
1.5 * x - 3.0 * y
norm(x)
dot(x, y)
Array(5.0, 5.0, 5.0, 5.0)
Array(-10.5, -6.0, -1.5, 3.0)
5.477225575051661
20.0
Norms include norm / norm2 (L2),
norm1, normInf, and normFro.
standardize centers and scales to unit variance;
unitize / unitize1 scale to unit L2 / L1 norm
(in place).
import static smile.math.MathEx.*
double[] y = {4.0, 3.0, 2.0, 1.0}
unitize(y)
y
[0.7302967433402214, 0.5477225575051661, 0.3651483716701107, 0.18257418583505536]
import smile.math.MathEx.*
val y = Array(4.0, 3.0, 2.0, 1.0)
unitize(y)
y
Array(0.7302967433402214, 0.5477225575051661, 0.3651483716701107, 0.18257418583505536)
Pairwise helpers include dot, distance,
kld / jsd (divergences), cov,
cor, spearman, and kendall.
import static smile.math.MathEx.*
double[] x = {1.0, 2.0, 3.0, 4.0}
double[] y = {4.0, 3.0, 2.0, 1.0}
unitize(y)
dot(x, y)
cov(x, y)
3.651483716701107
-0.30429030972509225
import smile.math.MathEx.*
val x = Array(1.0, 2.0, 3.0, 4.0)
val y = Array(4.0, 3.0, 2.0, 1.0)
unitize(y)
dot(x, y)
cov(x, y)
3.651483716701107
-0.30429030972509225
Matrices
Build dense matrices with DenseMatrix.of,
zeros, eye, randn, and
rand. Prefer DenseMatrix over the old
smile.math.matrix.Matrix type (removed).
import smile.tensor.*
import static smile.tensor.ScalarType.Float64
double[][] A = {
{ 0.7220180, 0.07121225, 0.6881997},
{-0.2648886, -0.89044952, 0.3700456},
{-0.6391588, 0.44947578, 0.6240573}
}
double[][] B = {
{0.6881997, -0.07121225, 0.7220180},
{0.3700456, 0.89044952, -0.2648886},
{0.6240573, -0.44947578, -0.6391588}
}
double[][] C = {
{ 0.9527204, -0.2973347, 0.06257778},
{-0.2808735, -0.9403636, -0.19190231},
{ 0.1159052, 0.1652528, -0.97941688}
}
var a = DenseMatrix.of(A)
var b = DenseMatrix.of(B)
var c = DenseMatrix.of(C)
a
DenseMatrix.eye(Float64, 3)
3 x 3
0.7220 0.0712 0.6882
-0.2649 -0.8904 0.3700
-0.6392 0.4495 0.6241
3 x 3
1.0000 0.0000 0.0000
0.0000 1.0000 0.0000
0.0000 0.0000 1.0000
import smile.math.*
import smile.tensor.*
val a = DenseMatrix.of(Array(
Array( 0.7220180, 0.07121225, 0.6881997),
Array(-0.2648886, -0.89044952, 0.3700456),
Array(-0.6391588, 0.44947578, 0.6240573)
))
val b = DenseMatrix.of(Array(
Array(0.6881997, -0.07121225, 0.7220180),
Array(0.3700456, 0.89044952, -0.2648886),
Array(0.6240573, -0.44947578, -0.6391588)
))
val c = DenseMatrix.of(Array(
Array( 0.9527204, -0.2973347, 0.06257778),
Array(-0.2808735, -0.9403636, -0.19190231),
Array( 0.1159052, 0.1652528, -0.97941688)
))
a
eye(3)
3 x 3
0.7220 0.0712 0.6882
-0.2649 -0.8904 0.3700
-0.6392 0.4495 0.6241
3 x 3
1.0000 0.0000 0.0000
0.0000 1.0000 0.0000
0.0000 0.0000 1.0000
Matrix–vector products use mv (and tv for
Aᵀx). In Scala, A * x is the same product inside
the expression DSL.
import smile.tensor.*
import static smile.linalg.Transpose.NO_TRANSPOSE
var a = DenseMatrix.of(new double[][] {
{ 0.7220180, 0.07121225, 0.6881997},
{-0.2648886, -0.89044952, 0.3700456},
{-0.6391588, 0.44947578, 0.6240573}
})
var x = Vector.column(new double[]{1.0, 2.0, 3.0})
var y = Vector.column(new double[]{3.0, 2.0, 1.0})
a.mv(x) // A x
a.mv(NO_TRANSPOSE, 1.0, x, 1.5, y) // y := A x + 1.5 y
y
[2.9290414582113184, -0.9356509345036078, 2.131964578605774]
[7.4290416, 2.06434916, 3.63196466]
import smile.tensor.*
val a = DenseMatrix.of(Array(
Array( 0.7220180, 0.07121225, 0.6881997),
Array(-0.2648886, -0.89044952, 0.3700456),
Array(-0.6391588, 0.44947578, 0.6240573)
))
val x = Array(1.0, 2.0, 3.0)
val y = Array(3.0, 2.0, 1.0)
a * x
a * x + 1.5 * y
Array(2.9290414582113184, -0.9356509345036078, 2.131964578605774)
Array(7.4290416, 2.06434916, 3.63196466)
In Java, add / sub mutate the receiver
(A += B). Copy first when you need to keep the original.
Scala’s + / - return lazy expressions.
import smile.tensor.*
var a = DenseMatrix.of(new double[][] {
{ 0.7220180, 0.07121225, 0.6881997},
{-0.2648886, -0.89044952, 0.3700456},
{-0.6391588, 0.44947578, 0.6240573}
})
var b = DenseMatrix.of(new double[][] {
{0.6881997, -0.07121225, 0.7220180},
{0.3700456, 0.89044952, -0.2648886},
{0.6240573, -0.44947578, -0.6391588}
})
a.copy().add(b)
3 x 3
1.4102 0.0000 1.4102
0.1052 0.0000 0.1052
-0.0151 0.0000 -0.0151
import smile.tensor.*
val a = DenseMatrix.of(Array(
Array( 0.7220180, 0.07121225, 0.6881997),
Array(-0.2648886, -0.89044952, 0.3700456),
Array(-0.6391588, 0.44947578, 0.6240573)
))
val b = DenseMatrix.of(Array(
Array(0.6881997, -0.07121225, 0.7220180),
Array(0.3700456, 0.89044952, -0.2648886),
Array(0.6240573, -0.44947578, -0.6391588)
))
a + b // lazy MatrixExpression; force with .toMatrix if needed
1.4102 0.0000 1.4102
0.1052 0.0000 0.1052
-0.0151 0.0000 -0.0151
Scala * on matrices is element-wise. True
matrix multiplication is %*% (R-style) or Java
mm / mt / tm
(A B, A Bᵀ, Aᵀ B).
import smile.tensor.*
var a = DenseMatrix.of(new double[][] {
{ 0.7220180, 0.07121225, 0.6881997},
{-0.2648886, -0.89044952, 0.3700456},
{-0.6391588, 0.44947578, 0.6240573}
})
var b = DenseMatrix.of(new double[][] {
{0.6881997, -0.07121225, 0.7220180},
{0.3700456, 0.89044952, -0.2648886},
{0.6240573, -0.44947578, -0.6391588}
})
var c = DenseMatrix.of(new double[][] {
{ 0.9527204, -0.2973347, 0.06257778},
{-0.2808735, -0.9403636, -0.19190231},
{ 0.1159052, 0.1652528, -0.97941688}
})
a.mm(b).mm(c)
a.mt(b).mm(c) // A Bᵀ C
3 x 3
0.9984 0.0067 0.0554
-0.0257 0.9361 0.3508
-0.0495 -0.3517 0.9348
3 x 3
0.8978 -0.4369 0.0543
0.4189 0.8856 0.2006
-0.1357 -0.1574 0.9782
import smile.tensor.*
val a = DenseMatrix.of(Array(
Array( 0.7220180, 0.07121225, 0.6881997),
Array(-0.2648886, -0.89044952, 0.3700456),
Array(-0.6391588, 0.44947578, 0.6240573)
))
val b = DenseMatrix.of(Array(
Array(0.6881997, -0.07121225, 0.7220180),
Array(0.3700456, 0.89044952, -0.2648886),
Array(0.6240573, -0.44947578, -0.6391588)
))
val c = DenseMatrix.of(Array(
Array( 0.9527204, -0.2973347, 0.06257778),
Array(-0.2808735, -0.9403636, -0.19190231),
Array( 0.1159052, 0.1652528, -0.97941688)
))
a * b // element-wise
a %*% b %*% c // matrix product (chain-optimized)
a %*% b.t %*% c
0.4969 -0.0051 0.4969
-0.0980 -0.7929 -0.0980
-0.3989 -0.2020 -0.3989
0.9984 0.0067 0.0554
-0.0257 0.9361 0.3508
-0.0495 -0.3517 0.9348
0.8978 -0.4369 0.0543
0.4189 0.8856 0.2006
-0.1357 -0.1574 0.9782
transpose() materializes immediately.
On a Scala MatrixExpression, prefer lazy .t
so a product chain can fuse transpose into mt /
tm. Scala also picks an optimal parenthesization for
%*% chains (matrix-chain ordering).
import smile.tensor.*
import static smile.tensor.ScalarType.Float64
var a = DenseMatrix.randn(Float64, 300, 900)
var b = DenseMatrix.randn(Float64, 900, 150)
var c = DenseMatrix.randn(Float64, 150, 1800)
var d = DenseMatrix.randn(Float64, 1800, 30)
var r = a.mm(b).mm(c).mm(d)
r.nrow() + " x " + r.ncol()
300 x 30
import smile.math.*
import smile.util.time
val a = randn(300, 900)
val b = randn(900, 150)
val c = randn(150, 1800)
val d = randn(1800, 30)
val r = time("matrix multiplication") {
(a %*% b %*% c %*% d).toMatrix
}
s"${r.nrow} x ${r.ncol}"
matrix multiplication: … ms
300 x 30
Matrix Decomposition
DenseMatrix exposes lu, qr,
cholesky, svd, and eigen.
From those you get determinants, ranks, inverses, and linear solves.
Scala also wraps common cases as lu, qr,
inv, det, rank, and the solve
operator \.
import smile.tensor.*
var a = DenseMatrix.of(new double[][] {
{ 0.7220180, 0.07121225, 0.6881997},
{-0.2648886, -0.89044952, 0.3700456},
{-0.6391588, 0.44947578, 0.6240573}
})
var inv = a.inverse()
inv.mm(a) // ≈ I
a.lu().solve(new double[]{1.0, 2.0, 3.0}) // solve A x = b
3 x 3
1.0000 -0.0000 0.0000
-0.0000 1.0000 0.0000
0.0000 0.0000 1.0000
[-1.7252356779053744, -0.3612592362819077, 3.3004624918302046]
import smile.math.*
import smile.tensor.*
val a = DenseMatrix.of(Array(
Array( 0.7220180, 0.07121225, 0.6881997),
Array(-0.2648886, -0.89044952, 0.3700456),
Array(-0.6391588, 0.44947578, 0.6240573)
))
val b = Array(1.0, 2.0, 3.0)
a \ b // solve A x = b
inv(a)
(inv(a) %*% a).toMatrix
[-1.7252356779053744, -0.3612592362819077, 3.3004624918302046]
3 x 3
0.7220 -0.2649 -0.6392
0.0712 -0.8904 0.4495
0.6882 0.3700 0.6241
3 x 3
1.0000 0.0000 0.0000
-0.0000 1.0000 0.0000
-0.0000 0.0000 1.0000
Both lu().solve(b) and Scala’s \ solve
Ax = b (equivalently x = A⁻¹b). That is different from
the matrix–vector product a.mv(b) / a * b.