Creating and Manipulating Arrays and Matrices
This guide introduces the core containers provided by the Linear Algebra module, how to inspect them, and the day-to-day tools you will reach for when preparing data. It complements the Quickstart introduction by diving deeper into the object model and the rich API that both arrays and matrices expose.
Containers at a glance
Linear Algebra exposes two closely related container families:
- Arrays (
RealArray,ComplexArray, ...) favour element-wise semantics. Operators such as*,/, and functions likesin()act independently on each entry. - Matrices (
RealMatrix,ComplexMatrix, ...) adopt linear-algebra semantics. The binary*performs matrix multiplication and matrix-oriented routines (factorisations, solving systems, ...) live here.
We refer to both as dense containers because they store all elements explicitly in memory (as opposed to sparse representations). We will use the term dense container interchangeably with arrays and matrices in this document.
You can move freely between the two worlds:
var A = matrix([[1, 2], [3, 4]])
var elementwise = A.array() // intrinsic converter to array semantics
var same_array = array(A) // intrinsic converter to array semantics
var squared = elementwise * elementwise
var back = matrix(squared) // regain matrix semantics when needed
Every constructor accepts type specifiers that combine a container shape with a scalar domain:
- Containers:
as_array,as_matrix - Scalars:
as_bool,as_integer,as_real,as_complex
They compose through bitwise-or:
var real_grid = zeros([3, 4], as_array | as_real)
var complex_eye = identity([3, 3], as_matrix | as_complex)
var int_counts = array([[1, 2], [3, 4]], as_integer)
Factory helpers default to complex matrices, so be explicit whenever you care about the scalar domain.
When should you use arrays versus matrices? Choose arrays when you need element-wise operations (e.g., applying
functions to each element, computing element-wise products). Choose matrices when you need linear algebra operations
(e.g., matrix multiplication, solving linear systems, eigenvalue decomposition). You can always convert between them
using array() and matrix() methods, so don't worry too much about making the wrong choice initially.
Inspecting a container
Arrays and matrices shared a common set inspection facilities. The following attributes and methods enable learning more about the kind and type of a container:
| Purpose | Property | Notes |
|---|---|---|
| Scalar domain | x.is_bool, x.is_integer, x.is_real, x.is_complex | Boolean checks exposing the compile-time scalar type |
| Scalar descriptor | x.scalar_type | Returns the Aleph type tag (as_real, as_complex, ...) |
| Container descriptor | x.container_type | Distinguishes array vs matrix storage |
| Shape and size information | x.rows(), x.cols(), x.size(), x.shape() | size = product(shape); vectors are stored as column matrices |
| Container kind | x.is_array(), x.is_matrix() | Reflect element-wise vs linear-algebra semantics |
| Vector tests | x.is_vector(), x.is_column_vector(), x.is_row_vector() | Check if 1D matrices |
| Scalar test | x.is_scalar() | True for 1×1 containers |
Clarifications
The scalar kind predicates are mutually exclusive. Exactly one of is_bool, is_integer, is_real, or
is_complex is true for a given container. For instance, if is_integer is true then is_real is false. Integers
are not treated as a subtype of reals in these checks, even though they are a subset of the real numbers mathematically speaking.
var Y = ones([4, 4], as_array | as_integer)
print(Y.is_integer()) // true
print(Y.is_real()) // false
scalar_type yields a type tag like as_bool, as_integer, as_real, or as_complex, and
container_type yields a tag like as_array or as_matrix. You can use these tags with the factory helpers to
recreate containers with the same kind and scalar type.
var X = random([2, 3], as_array | as_real)
print(X.is_array()) // true
print(X.scalar_type) // as_real
print(X.container_type) // as_array
var Z = zeros([2, 3], X.container_type | X.scalar_type) // same container and scalar kind as X
shape() returns the shape of the container. For matrices, this is always a two-element list equal to [rows, cols].
This is also true for arrays at the moment, although it shall depend on dimensionality in the near future when we
introduce multi-dimensional arrays. In any case, the product of the shape elements equals size().
var A = random([2, 3], as_array | as_real)
print(A.shape()) // [2, 3]
print(A.size()) // 6
We refer loosely to matrices with a single column or row as vectors. Vectors are really n×1 matrices (column vectors),
which means they have matrix semantics (e.g., they can be multiplied by other matrices). Vector tests (is_vector,
is_column_vector, is_row_vector) therefore apply to matrices only; arrays always return false for these predicates.
var V = vector([1, 2, 3])
print(V.is_vector()) // true
print(V.is_column_vector()) // true
print(V.is_row_vector()) // false
print(V.transposed().is_row_vector()) // true
print(array(V).is_vector()) // false
In any case, one can always query whether a container is an array or a matrix with is_array() and is_matrix():
var X = random([2, 3], as_array | as_real)
print(X.is_array()) // true
print(X.is_matrix()) // false
var M = matrix(X)
print(M.is_array()) // false
print(M.is_matrix()) // true
And finally, it is sometimes useful to turn a 1×1 container into a scalar value, for instance after computing the matrix product of a row-vector and a column-vector (dot product). You can check whether the container has a single element with is_scalar() and extract the value with the value() method:
var a = vector([1, 2, 3])
var b = vector([4, 5, 6]).transposed()
var dot_product = b * a // 1x1 matrix
if (dot_product.is_scalar()) {
print(dot_product.value()) // 32
}
The last example is not the most efficient way to compute a dot product. Use the dedicated dot(a, b) function instead.
Constructing arrays and matrices
An important aspect of working with dense containers is creating them in the first place. The Linear Algebra module offers several complementary approaches to build arrays and matrices.
From literals
When learning a framework, it is often convenient to create small arrays and matrices from hard-coded values. This is the
most straightforward approach for prototyping and testing. Suppose you want to learn how the various options and
overloads of the sum function behave on different container types. You will likely want to create small test cases by
hand and then experiment summing the whole array, summing along rows or columns, etc. These small test cases will have
simple values that you can easily verify by hand to gain confidence in your understanding of the interface.
Aleph has a List container that stores a sequence of any objects. It is natural to use lists of homogeneous elements to create vectors, as shown below:
var v = [1, 2, 3]
var vec = vector(v)
You can also create a vector directly from a literal without the intermediate variable:
var vec = vector([-100..101]); // range literal
Here we use the range literal syntax start..end to create a list of integers from -100 to 100 (included).
As mentioned earlier, vectors are simply n×1 matrices, so the vector constructor is a thin wrapper over the matrix constructor.
It is therefore natural to extend the previous example to matrices by using nested lists:
var arr = array([[1, 2, 3], [4, 5, 6]], as_real)
var mat = matrix([[1, 2], [3, 4]], as_real)
Think of each set of brackets as introducing a dimension. Since the inner list represents a column (not a row),
[[1, 2, 3], [4, 5, 6]] builds a 3×2 container with the first column (1, 2, 3) and the second column (4, 5, 6).
To create a 2×3 matrix instead, you would write:
var mat = matrix([[1, 4], [2, 5], [3, 6]], as_real)
If you are used to row-major languages like Python or C, you can build matrices in row-major order and then transpose
them to get the desired layout. For instance, the previous 2×3 matrix can also be created as:
var mat = matrix([[1, 2, 3], [4, 5, 6]], as_real).transposed()
This approach is particularly helpful when copying data from other environments or when thinking about the matrix row by row feels more natural.
The matrix created with matrix([[1, 2, 3], [4, 5, 6]], as_real) is a real matrix (scalar type as_real) with 2 rows and 3 columns.
It will be printed as:
[[1, 2, 3],
[4, 5, 6]]
i.e. the same way you wrote it. This allows copying the input back into the factory function. If you would like to display the matrix row by row you can transpose it before printing:
var mat = matrix([[1, 2, 3], [4, 5, 6]], as_real)
print(mat.transpose())
Factory helpers
When you need to create containers of a specific shape filled with a pattern, the factory functions are your go-to tools. They are more convenient than building from literals when working with larger dimensions or when the initial values follow a simple rule (all zeros, all ones, random, etc.).
Each factory function accepts a shape argument and an optional type argument. The shape can be either [n] (creating
an n×1 column vector) or [rows, cols] (creating an array or matrix with the specified dimensions). The type argument combines
container and scalar type using the bitwise-or operator:
- If you specify only the scalar type (e.g.,
as_real), the container defaults to a matrix. - If you specify only the container type (e.g.,
as_array), the scalar type defaults to complex. - If you omit the
typeentirely, you get a complex matrix by default.
var col = zeros([10], as_real) // 10x1 column vector
var mat = zeros([10, 5], as_real) // 10x5 matrix
var default_z = zeros([2, 2]) // ComplexMatrix (default)
var explicit_z = zeros([2, 2], as_matrix | as_complex) // same thing
This default is convenient for quick prototyping but can be surprising if you forget to specify the type.
When writing production code, it's good practice to always specify the type explicitly.
Zeros, ones, and filled
The most common patterns are zeros(shape, type), which fills a container with 0, and ones(shape, type), which fills
it with 1. For other constants, use filled(shape, value, type):
var z = zeros([3, 4], as_matrix | as_real)
var o = ones([3, 4], as_array | as_complex)
var pi_grid = filled([2, 2], 3.14159, as_real)
Note that zeros and ones work for all scalar types (bool, integer, real, complex), while filled requires a value
compatible with the target scalar type. For instance, you cannot fill a real container with a complex value, but you can
fill an integer container with a real value (it will be truncated).
Creating uninitialized containers
The uninitialized(shape, type) function allocates memory without writing any initial values. This is the fastest way
to create a container when you plan to immediately overwrite all elements anyway, for example in a loop or through an
assignment. The contents are undefined until you set them.
var buffer = uninitialized([100, 100], as_array | as_real);
// buffer now holds 10,000 elements with undefined values
// typically you'd fill it immediately
buffer.set_constant(42.0);
If you are not yet sure what values your container will hold, zeros is the safest way to initialize it. Unlike
uninitialized, which leaves memory in an undefined state, zeros gives you a predictable starting point that won't
cause undefined behavior if accidentally read before being overwritten.
Random values
The random(shape, type) function fills a container with pseudo-random values. For real types, values are uniformly
distributed in [-1, 1]. For complex types, both the real and imaginary parts are drawn from [-1, 1].
var noise = random([5, 5], as_array | as_real)
// Each element is a random real number in [-1, 1]
Random containers are useful for testing, initialization of iterative algorithms, and generating synthetic data.
Identity matrices
The identity(shape, type) function creates an identity matrix (or rectangular matrix with ones on the diagonal and zeros
elsewhere). This is particularly useful in linear algebra operations.
var I = identity([3, 3], as_matrix | as_real) // 3x3 identity
var R = identity([3, 5], as_real) // 3x5 rectangular identity
For square matrices, the identity has ones along the main diagonal. For rectangular matrices, ones appear wherever the row index equals the column index.
Like-constructors
Once you have a reference container, you can create new containers with the same shape and type using the *_like family
of methods. These are convenience methods that clone the metadata (shape, container type, and scalar type) while filling
the new container according to different patterns:
var pattern = random([3, 2], as_array | as_real)
var uninit = pattern.uninitialized_like() // fastest, undefined values
var zeros_again = pattern.zeros_like() // filled with zeros
var ones_again = pattern.ones_like() // filled with ones
var threes = pattern.filled_like(3.0) // filled with a custom value
var rand_again = pattern.random_like() // filled with random values
var identity = pattern.identity_like() // identity pattern (diagonal ones)
These methods save you from having to manually extract and recombine the shape(), container_type, and scalar_type
properties. They are particularly useful when writing generic code that needs to allocate temporary containers matching
an input's characteristics.
The identity_like() method works on rectangular (non-square) matrices too, placing ones along the main diagonal
wherever the row and column indices are equal, just like the identity() factory function.
Setters
Once a container has been allocated (for example, with zeros, uninitialized, or any other factory function), you can
reset its contents in-place using the set_* family of methods. These methods modify the existing container without
reallocating memory, making them efficient for reusing buffers in loops or iterative algorithms.
var buffer = uninitialized([3, 3], as_array | as_real)
buffer.set_zero() // all elements now zero
buffer.set_ones() // all elements now one
buffer.set_constant(42) // all elements now 42
buffer.fill(3.14159) // fill is a synonym for set_constant
buffer.set_random() // all elements now random in [0, 1]
The set_constant(value) method and its synonym fill(value) broadcast a scalar value to all elements of the container.
This is particularly useful when initializing data to a specific non-zero, non-one value. For more details on constructing arrays
with random numbers see here.
For matrices, you can also reset the container to an identity pattern using set_identity():
var mat = zeros([4, 4], as_matrix | as_real)
mat.set_identity() // diagonal elements now 1, others 0
Vectors can use set_unit(i) to be set to a particular basis vector (all zeros except element i which is set to 1):
var v = zeros([5], as_real)
v.set_unit(2) // v is now [0, 0, 1, 0, 0]
These in-place setters are designed to work seamlessly with the like-constructors: you can allocate a container with the desired shape and type using a like-constructor, then immediately fill it with the pattern you need.
Concatenation utilities
When you need to assemble larger containers from smaller building blocks, the concatenation functions provide a clean way to combine multiple containers along different dimensions.
Concatenating along rows
The concatenate_rows() function stacks containers vertically (one on top of another), concatenating along the row
dimension. All containers must have the same number of columns:
var a0 = zeros([2, 2], as_real)
var a1 = ones([2, 2], as_real)
var combined = concatenate_rows([a0, a1, a0])
// Result is a 6x2 matrix:
// [[0,0,1,1,0,0],
// [0,0,1,1,0,0]]
Think of this as appending rows: the first container provides the first set of rows, the second provides the next set,
and so on. Concatenating n containers of shape [r, c] produces a result of shape [n*r, c].
Concatenating along columns
The concatenate_columns() function stacks containers horizontally (side by side), concatenating along the column
dimension. All containers must have the same number of rows:
var a0 = zeros([2, 2], as_real)
var a1 = ones([2, 2], as_real)
var combined = concatenate_columns([a0, a1, a0])
// Result is a 2x6 matrix:
// [[0,0],
// [0,0],
// [1,1],
// [1,1],
// [0,0],
// [0,0]]
Think of this as appending columns: the first container provides the first set of columns, the second provides the
next set, and so on. Concatenating n containers of shape [r, c] produces a result of shape [r, n*c].
Block matrix construction
The more general concatenate() function accepts a list of lists, allowing you to build block matrices with arbitrary
2D tiling patterns. This is equivalent to using concatenate_rows() with an extra level of brackets:
var a0 = zeros([2, 2], as_real)
var a1 = ones([2, 2], as_real)
// Simple horizontal stacking (equivalent to concatenate_rows)
var row_stack = concatenate([[a0, a1, a0]])
// 2D block tiling
var block_matrix = concatenate([
[a0, a1, a0], // first block column
[a1, a0, a1], // second block column
[a0, a1, a0] // third block column
])
// Result is a 6x6 matrix with 3x3 blocks
Each inner list represents a block column. All blocks in the same row must have the same number of rows, and all blocks in the same column position must have the same number of columns. The function validates these constraints and throws informative errors if dimensions are incompatible.
Replication and tiling
When you need to create a block matrix where all the blocks are identical, the replicated() function provides a more
convenient alternative to manually building lists for concatenate(). It tiles a single container multiple times along
both dimensions:
var tile = linspaced(6, 0, 5).reshaped([2, 3])
// tile is [[0, 1], [2, 3], [4, 5]]
var tiled = tile.replicated(2, 3) // 2 rows of tiles, 3 columns of tiles
// Result is a 4x9 matrix (2*2 rows, 3*3 columns)
// [[0,1,0,1],
// [2,3,2,3],
// [4,5,4,5],
// [0,1,0,1],
// [2,3,2,3],
// [4,5,4,5],
// [0,1,0,1],
// [2,3,2,3],
// [4,5,4,5]]
For vectors, a single-argument overload is available that replicates the vector along its non-singleton dimension:
var col = vector([1, 2, 3], as_real)
var repeated = col.replicated(4) // repeats the column vector 4 times along the column dimension
// Result is a 12x1 column vector: [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
var row = col.transposed()
var repeated_row = row.replicated(4) // repeats the row vector 4 times along the row dimension
// Result is a 1x12 row vector: [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
This is particularly useful when you need to broadcast a pattern across a larger container, such as initializing a grid with a repeated template or creating test data with regular structure.
Shaping and reshaping data
Dense containers provide a rich set of methods to manipulate their shape and layout. It is often convenient to initialize data in one shape and then transform it to another without manually handling index arithmetic. For instance, in the process of learning about matrix operations, you might want to reshape a flat array into a matrix or flatten a matrix into a vector for certain computations.
var flat = linspaced(6, 0, 5)
var mat = flat.reshaped([2, 3]) // reshaped to 2 rows and 3 columns
var vec = mat.flattened() // flattened back to a column vector
The following methods cover common reshaping operations.
Appending and removing data
If you're familiar with Aleph Lists, which can grow and shrink dynamically, you might wonder whether dense containers support similar operations. Unlike lists, dense containers maintain a fixed rectangular structure and cannot resize themselves dynamically. However, you can achieve similar effects by creating new containers with the desired shape.
To append a row or column, use the concatenation utilities:
var mat = matrix([[1, 2], [3, 4]], as_real)
var new_row = vector([5, 6], as_real).transposed()
var extended_mat = concatenate_rows([mat, new_row]) // appends a row
Note that you cannot append individual elements—doing so would create a non-rectangular structure, which dense containers do not support.
To remove a row or column, use slicing or block extraction to create a new container without the unwanted data:
var mat = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]], as_real)
var reduced_mat = mat[0:2, 0:2] // keeps only first 2 rows and 2 columns
Individual elements cannot be removed from within the container, as this would break the rectangular structure. If you need data structures with arbitrary insertion and deletion, consider using a different representation such as a sparse matrix.
Reshaping
reshape(shape) performs an in-place reshape that preserves the element count, while reshaped(shape) returns a
copy. Shapes can be [n] (producing a column vector) or [rows, cols]:
var seq = linspaced(6, 0, 5) // returns a matrix by default
var grid = seq.reshaped([2, 3]) // copy
grid.reshape([3, 2]) // in-place
The product of the new shape dimensions must equal the total number of elements in the container. For example, you
cannot reshape a 6-element container to [2, 2] (4 elements) or [3, 3] (9 elements)—only to shapes like [6, 1],
[3, 2], [2, 3], or [1, 6] that preserve the element count.
To create a 1D copy of the data, you can always use reshaped([grid.size()]). There is also a dedicated method for this
common use case: flatten() turns any dense container into a column vector in-place, while flattened() keeps the
original intact.
var grid = array([[1,1,1], [2,2,2]], as_real)
var flat = grid.flattened() // copy as column vector
grid.flatten() // in-place flattening
Resizing
The resize(new_rows, new_cols) method mutates the allocation (and may reallocate memory). The new size is independent
of the previous number of elements—be prepared to refill the data afterwards.
Resizing a container invalidates its contents. The new container will have undefined values until you explicitly set them.
Shifts and flips
When working with dense containers, you often need to permute or rearrange their elements in systematic ways. A permutation changes the order or position of elements without changing the underlying data values themselves. Common examples include mirroring a container along one dimension, rotating its layout, or cyclically shifting its rows or columns. The Linear Algebra module provides a suite of methods that handle these permutations efficiently without requiring manual index arithmetic:
udflip()/udflipped()flips columns (top ↔ bottom).lrflip()/lrflipped()flips rows (left ↔ right).flip(dimension)andflipped(dimension)generalise the previous two.rotate()/rotated()perform a 90° counter-clockwise rotation.shift([row_offset, col_offset])/shifted(...)apply periodic shifts (wrap-around).
These methods follow a consistent naming convention: the present tense form (e.g., reshape, transpose, rotate)
mutates the container in-place, while the past tense form (e.g., reshaped, transposed, rotated) returns a new
copy and leaves the original unchanged.
Flipping
Flipping a container mirrors its elements along a specified dimension. The most common use cases are vertical and horizontal flips:
var mat = matrix([[1, 2, 3], [4, 5, 6]], as_real)
// mat is [[1,2,3],
// [4,5,6]]
var vert = mat.udflipped() // up-down flip
// vert is [[4,5,6],
// [1,2,3]]
var horiz = mat.lrflipped() // left-right flip
// horiz is [[3,2,1],
// [6,5,4]]
The flip(dimension) method generalizes these operations: flip(0) is equivalent to udflip(), and flip(1) is
equivalent to lrflip().
Rotation
The rotate() method permutes the container's elements by performing a 90° counter-clockwise rotation of the layout:
var mat = matrix([[1, 2, 3], [4, 5, 6]], as_real)
// mat is [[1,2,3],
// [4,5,6]]
var rotated_once = mat.rotated()
// rotated_once is [[3,6],
// [2,5],
// [1,4]]
var rotated_twice = rotated_once.rotated()
// rotated_twice is [[6,5,4],
// [3,2,1]]
The rotate() method permutes the data layout within the container; it does not perform a geometric rotation of
vectors in 2D or 3D space. For spatial rotations of coordinate data, use dedicated linear algebra transformations
(rotation matrices) instead.
Shifting
The shift() method applies a periodic (wrap-around) shift along rows and columns:
var mat = matrix([[1, 2, 3], [4, 5, 6]], as_real)
// mat is [[1,2,3],
// [4,5,6]]
var shifted = mat.shifted([1, 1]) // shift by 1 row and 1 column
// shifted is [[6,4,5],
// [3,1,2]]
Positive offsets shift down and to the right, with elements wrapping around to the opposite edge. This is useful for implementing periodic boundary conditions or aligning data with circular symmetry.
Shift operations are particularly useful in signal processing and image manipulation. For example, centering a Fourier transform typically requires shifting by half the dimensions, while implementing convolution with periodic boundaries often needs various shift amounts.
Diagonal and triangular extraction
Many linear algebra algorithms work with specific structural patterns in matrices. The Linear Algebra module provides functions to extract diagonals, create diagonal matrices, and extract triangular portions of matrices. These operations are fundamental for decompositions, solving systems of equations, and working with specialized matrix structures.
Extracting diagonals
The diagonal() method extracts a diagonal from a matrix as a column vector. Without arguments, it returns the main
diagonal (index 0). You can specify an index to extract other diagonals:
var mat = matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], as_real)
// mat is [[1,2,3,4],
// [5,6,7,8],
// [9,10,11,12],
// [13,14,15,16]]
var main_diag = mat.diagonal() // [1, 6, 11, 16]
var above_diag = mat.diagonal(1) // [5, 10, 15] (one above main diagonal)
var below_diag = mat.diagonal(-1) // [2, 7, 12] (one below main diagonal)
Positive indices extract diagonals above the main diagonal, while negative indices extract diagonals below it. Remember
that the indexing follows column-major conventions, where element mat[i, j] is on diagonal j - i.
Creating diagonal matrices
The as_diagonal() method takes a vector (1D matrix) and creates a square diagonal matrix with those values on the
main diagonal:
var values = vector([1, 2, 3, 4], as_real)
var diag_mat = values.as_diagonal()
// diag_mat is [[1,0,0,0],
// [0,2,0,0],
// [0,0,3,0],
// [0,0,0,4]]
This is particularly useful when working with eigenvalues, singular values, or any situation where you need to construct a diagonal matrix from a list of values.
Triangular extraction
The upper_triangular() and lower_triangular() methods extract the upper and lower triangular portions of a matrix,
zeroing out the elements below or above the diagonal respectively:
var mat = matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], as_real)
// mat is [[1,2,3,4],
// [5,6,7,8],
// [9,10,11,12]]
var upper = mat.upper_triangular()
// upper is [[1,0,0,0],
// [5,6,0,0],
// [9,10,11,0]]
var lower = mat.lower_triangular()
// lower is [[1,2,3,4],
// [0,6,7,8],
// [0,0,11,12]]
Both methods accept an optional index argument to shift the boundary. This is useful for extracting banded matrices or creating Hessenberg forms:
var mat = matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], as_real)
var upper_hessenberg = mat.upper_triangular(-1)
// upper_hessenberg is [[1,2,0,0],
// [5,6,7,0],
// [9,10,11,12]]
// One subdiagonal is kept, forming an upper Hessenberg matrix
var strict_upper = mat.upper_triangular(1)
// strict_upper is [[0,0,0,0],
// [5,0,0,0],
// [9,10,0,0]]
// Main diagonal zeroed, only strictly upper part kept
A positive index argument shifts the zero boundary upward (keeping more elements for upper_triangular, fewer for
lower_triangular), while a negative index shifts it downward.
Testing for structure
The Linear Algebra module also provides predicate functions to test whether a matrix has diagonal or triangular structure. These methods accept an optional tolerance parameter for approximate comparisons:
var diag = identity([3, 3], as_real)
print(diag.is_diagonal()) // true
var upper = matrix([[1, 2, 3], [0, 4, 5], [0, 0, 6]], as_real)
print(upper.is_upper_triangular()) // true
var almost_lower = matrix([[1, 0, 0], [2, 3, 0], [4, 5, 1e-10]], as_real)
print(almost_lower.is_lower_triangular()) // false (strict check)
print(almost_lower.is_lower_triangular(1e-9)) // true (with tolerance)
These predicates complement the extraction functions, allowing you to verify assumptions about matrix structure before applying specialized algorithms that require diagonal or triangular inputs.
When working with matrices resulting from numerical computations (e.g., factorizations), always use a tolerance
parameter with these structure tests. Floating-point arithmetic can introduce small errors that make an algorithmically
triangular matrix fail a strict structural check. A tolerance of 1e-12 is often appropriate for
double-precision calculations.