4 Tensors
La Linea wraps dtype-next tensors in RealTensors — thin wrappers that add type identity, custom printing, and structural equality while preserving zero-copy interop. This chapter covers the scicloj.lalinea.tensor namespace (alias t/).
(ns lalinea-book.tensors
(:require
[scicloj.lalinea.tensor :as t]
[scicloj.lalinea.linalg :as la]
[scicloj.lalinea.elementwise :as el]
[scicloj.kindly.v4.kind :as kind]
[clojure.math :as math])
(:import [org.ejml.data DMatrixRMaj]))Construction
t/matrix creates a RealTensor from nested sequences or an existing tensor:
(t/matrix [[1 2 3]
[4 5 6]])#la/R [:float64 [2 3]
[[1.000 2.000 3.000]
[4.000 5.000 6.000]]]
t/column and t/row create column [n 1] and row [1 n] vectors:
(t/column [1 2 3])#la/R [:float64 [3 1]
[[1.000]
[2.000]
[3.000]]]
(t/row [1 2 3])#la/R [:float64 [1 3]
[[1.000 2.000 3.000]]]
t/eye, t/zeros, and t/ones create standard matrices:
(t/eye 3)#la/R [:float64 [3 3]
[[1.000 0.000 0.000]
[0.000 1.000 0.000]
[0.000 0.000 1.000]]]
(t/zeros 2 3)#la/R [:float64 [2 3]
[[0.000 0.000 0.000]
[0.000 0.000 0.000]]]
t/compute-matrix constructs from a function of (i, j):
(t/compute-matrix 3 3 (fn [i j] (if (== i j) 1.0 0.0)))#la/R [:float64 [3 3]
[[1.000 0.000 0.000]
[0.000 1.000 0.000]
[0.000 0.000 1.000]]]
t/compute-tensor creates a lazy tensor of arbitrary shape:
(t/compute-tensor [3 3]
(fn [i j] (if (== i j) 1.0 0.0))
:float64)#la/R [:float64 [3 3]
[[1.000 0.000 0.000]
[0.000 1.000 0.000]
[0.000 0.000 1.000]]]
Printed form
RealTensors print as #la/R tagged literals. This representation round-trips through pr-str / read-string:
(t/matrix [[1 2] [3 4]])#la/R [:float64 [2 2]
[[1.000 2.000]
[3.000 4.000]]]
Element access
Tensors are callable — (m i j) reads element [i,j]:
(let [m (t/matrix [[10 20] [30 40]])]
[(m 0 1) (m 1 0)])[20.0 30.0]Structural operations
Shape and reshape
(t/shape (t/matrix [[1 2 3] [4 5 6]]))[2 3]t/reshape is zero-copy — it wraps the same backing array in a different shape:
(t/reshape (t/matrix [1 2 3 4 5 6]) [2 3])#la/R [:float64 [2 3]
[[1.000 2.000 3.000]
[4.000 5.000 6.000]]]
Select and submatrix
t/select slices along dimensions. It returns a view:
(let [A (t/matrix [[1 2 3]
[4 5 6]])]
(t/select A 0 :all))#la/R [:float64 [3] [1.000 2.000 3.000]]t/submatrix extracts a contiguous submatrix (always a copy):
(let [A (t/matrix [[1 2 3] [4 5 6] [7 8 9]])]
(t/submatrix A (range 2) (range 2)))#la/R [:float64 [2 2]
[[1.000 2.000]
[4.000 5.000]]]
Flatten
t/flatten reshapes to 1D:
(t/flatten (t/matrix [[1 2] [3 4]]))#la/R [:float64 [4] [1.000 2.000 3.000 4.000]]Materialization
Lazy results (from el/+, el/sqrt, etc.) recompute on every access. Call t/materialize to ensure a concrete array (no-op if already concrete), or t/clone to force a fresh copy:
(let [a (t/matrix [1 4 9])
s (el/sqrt a)
cloned (t/clone s)]
{:lazy-array? (some? (t/array-buffer s))
:cloned-array? (some? (t/array-buffer cloned))}){:lazy-array? false, :cloned-array? true}Mutation: t/mset!
t/mset! mutates a tensor element in place:
(let [m (t/matrix [[1 2] [3 4]])]
(t/mset! m 0 1 99.0)
(m 0 1))99.0Only works on tensors backed by a concrete array. Lazy tensors cannot be mutated.
Element-wise operations
The scicloj.lalinea.elementwise namespace (alias el/) provides tape-aware element-wise functions:
(let [x (t/matrix [[1 4] [9 16]])]
(el/sqrt x))#la/R [:float64 [2 2]
[[1.000 2.000]
[3.000 4.000]]]
el/+, el/-, el/scale, and el/* also work element-wise on matching-shape tensors:
(let [a (t/matrix [[1 2] [3 4]])
b (t/matrix [[10 20] [30 40]])]
(el/+ a b))#la/R [:float64 [2 2]
[[11.00 22.00]
[33.00 44.00]]]
EJML interop
t/tensor->dmat and t/dmat->tensor convert between RealTensors and EJML’s DMatrixRMaj — zero-copy both ways.
(let [m (t/matrix [[1.0 2.0] [3.0 4.0]])
dm (t/tensor->dmat m)]
{:identical? (identical? (t/->double-array m)
(.data ^DMatrixRMaj dm))
:rows (.numRows ^DMatrixRMaj dm)
:cols (.numCols ^DMatrixRMaj dm)}){:identical? true, :rows 2, :cols 2}Mutations through either view are immediately visible in the other:
(let [m (t/matrix [[1.0 0.0] [0.0 1.0]])
dm (t/tensor->dmat m)]
(.set ^DMatrixRMaj dm 0 1 99.0)
(m 0 1))99.0Matrix operations (preview)
The scicloj.lalinea.linalg namespace (alias la/) provides a functional API for matrix operations. A taste:
Matrix multiply: \(A \cdot I = A\)
(la/mmul (t/matrix [[1 2] [3 4]])
(t/eye 2))#la/R [:float64 [2 2]
[[1.000 2.000]
[3.000 4.000]]]
Inverse:
(la/invert (t/matrix [[1 2] [3 4]]))#la/R [:float64 [2 2]
[[-2.000 1.000]
[ 1.500 -0.5000]]]
Frobenius norm: \(\|A\|_F = \sqrt{\sum a_{ij}^2}\)
(la/norm (t/matrix [[1 2 3] [4 5 6]]))9.539392014169456